Reorganized File Structure
Added Documentation
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// MPFunctionValue.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPElementaryFunctionTerm;
|
||||
|
||||
|
||||
@interface MPElementaryFunctionTerm : MPTerm
|
||||
|
||||
- (instancetype)initWithFunctionIdentifier:(NSString *)function
|
||||
term:(MPTerm *)term; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) MPTerm *term;
|
||||
|
||||
@end
|
||||
@@ -1,112 +0,0 @@
|
||||
//
|
||||
// MPFunctionValue.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPElementaryFunctionTerm.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
#import "MPPowerFunction.h"
|
||||
#import "MPProductTerm.h"
|
||||
#import "MPToken.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPMathRules.h"
|
||||
|
||||
@implementation MPElementaryFunctionTerm {
|
||||
NSDecimalNumber *(^_function)(NSDecimalNumber *);
|
||||
}
|
||||
|
||||
- (instancetype)initWithFunctionIdentifier:(NSString *)function
|
||||
term:(MPTerm *)term
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(function != nil, @"function must not be nil.");
|
||||
NSAssert(term != nil, @"term must not be nil.");
|
||||
double (*func)(double);
|
||||
BOOL takesArc = NO;
|
||||
BOOL returnsArc = NO;
|
||||
if ([function isEqualToString:@"sin"]) {
|
||||
func = &sin;
|
||||
takesArc = YES;
|
||||
} else if ([function isEqualToString:@"cos"]) {
|
||||
func = &cos;
|
||||
takesArc = YES;
|
||||
} else if ([function isEqualToString:@"tan"]) {
|
||||
func = &tan;
|
||||
takesArc = YES;
|
||||
} else if ([function isEqualToString:@"asin"] || [function isEqualToString:@"arcsin"]) {
|
||||
func = &asin;
|
||||
returnsArc = YES;
|
||||
} else if ([function isEqualToString:@"acos"] || [function isEqualToString:@"arccos"]) {
|
||||
func = &acos;
|
||||
returnsArc = YES;
|
||||
} else if ([function isEqualToString:@"atan"] || [function isEqualToString:@"arctan"]) {
|
||||
func = &atan;
|
||||
returnsArc = YES;
|
||||
} else if ([function isEqualToString:@"lg"] || [function isEqualToString:@"log"]) {
|
||||
func = &log10;
|
||||
} else if ([function isEqualToString:@"ln"]) {
|
||||
func = &log;
|
||||
} else {
|
||||
NSAssert(true, @"function must be one of (sin, cos, tan, asin, acos, atan, lg, log, ln).");
|
||||
}
|
||||
[self setFunction:func
|
||||
takesArcValue:takesArc
|
||||
returnsArcValue:returnsArc];
|
||||
_term = term;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFunction:(double (*)(double))function
|
||||
takesArcValue:(BOOL)takesArc
|
||||
returnsArcValue:(BOOL)returnsArc
|
||||
{
|
||||
id __weak weakSelf = self;
|
||||
_function = ^(NSDecimalNumber *value){
|
||||
if (takesArc) {
|
||||
value = [weakSelf convertToRadiantsIfNecessary:value];
|
||||
}
|
||||
NSDecimalNumber *returnValue = [[NSDecimalNumber alloc] initWithDouble:function(value.doubleValue)];
|
||||
if (returnsArc) {
|
||||
returnValue = [weakSelf convertToDegreesIfNecessary:returnValue];
|
||||
}
|
||||
return returnValue;
|
||||
};
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)convertToRadiantsIfNecessary:(NSDecimalNumber *)degrees
|
||||
{
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
// * M_PI / 180
|
||||
return [[degrees decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]];
|
||||
} else {
|
||||
return degrees;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)convertToDegreesIfNecessary:(NSDecimalNumber *)radiants
|
||||
{
|
||||
if ([MPMathRules sharedRules].isUsingDegrees) {
|
||||
// * 180 / M_PI
|
||||
return [[radiants decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||
} else {
|
||||
return radiants;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDecimalNumber *value = [self.term evaluate:error];
|
||||
if (!value) {
|
||||
return nil;
|
||||
}
|
||||
return _function(value);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// MPEvaluationContext.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 12.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@class MPEvaluationContext;
|
||||
|
||||
|
||||
@interface MPEvaluationContext : NSObject
|
||||
|
||||
+ (MPEvaluationContext *)sharedContext;
|
||||
|
||||
- (void)push;
|
||||
- (void)pop;
|
||||
|
||||
- (BOOL)defineVariable:(NSString *)variable
|
||||
value:(NSDecimalNumber *)value;
|
||||
- (void)undefineVariable:(NSString *)variable;
|
||||
|
||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variable;
|
||||
|
||||
@end
|
||||
@@ -1,88 +0,0 @@
|
||||
//
|
||||
// MPEvaluationContext.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 12.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPEvaluationContext.h"
|
||||
|
||||
@interface MPEvaluationContext ()
|
||||
@property (nonatomic, strong) NSMutableArray *stack;
|
||||
@end
|
||||
|
||||
@implementation MPEvaluationContext
|
||||
|
||||
static MPEvaluationContext *sharedContext;
|
||||
+ (MPEvaluationContext *)sharedContext
|
||||
{
|
||||
if (!sharedContext) {
|
||||
sharedContext = [[MPEvaluationContext alloc] init];
|
||||
}
|
||||
return sharedContext;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (sharedContext) {
|
||||
return sharedContext;
|
||||
}
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_stack = [[NSMutableArray alloc] init];
|
||||
[self push];
|
||||
[self defineVariable:@"e"
|
||||
value:[[NSDecimalNumber alloc] initWithDouble:M_E]];
|
||||
[self defineVariable:@"π"
|
||||
value:[[NSDecimalNumber alloc] initWithDouble:M_PI]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)push
|
||||
{
|
||||
[self.stack addObject:[[NSMutableDictionary alloc] init]];
|
||||
}
|
||||
|
||||
- (void)pop
|
||||
{
|
||||
[self.stack removeLastObject];
|
||||
}
|
||||
|
||||
- (BOOL)defineVariable:(NSString *)variable
|
||||
value:(NSDecimalNumber *)value
|
||||
{
|
||||
[self undefineVariable:variable];
|
||||
if ([self isVariableDefined:variable]) {
|
||||
return NO;
|
||||
}
|
||||
NSMutableDictionary *currentBindings = self.stack.lastObject;
|
||||
currentBindings[variable] = value;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)undefineVariable:(NSString *)variable
|
||||
{
|
||||
NSMutableDictionary *currentBindings = self.stack.lastObject;
|
||||
[currentBindings removeObjectForKey:variable];
|
||||
}
|
||||
|
||||
- (BOOL)isVariableDefined:(NSString *)variable
|
||||
{
|
||||
return [self valueForVariable:variable] != nil;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variable
|
||||
{
|
||||
NSUInteger currentIndex = self.stack.count;
|
||||
NSDictionary *currentBindings;
|
||||
NSDecimalNumber *value = nil;
|
||||
while (!value && currentIndex > 0) {
|
||||
currentBindings = self.stack[--currentIndex];
|
||||
value = currentBindings[variable];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,840 +0,0 @@
|
||||
//
|
||||
// MPExpression.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 10.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@const MPIllegalElementException
|
||||
@brief Name for an exception that is raised if an invalid element is
|
||||
added to an expression.
|
||||
|
||||
@discussion This exception may be raised during initialization of an
|
||||
expression or when the expression is mutated. This exception
|
||||
always contains the @c MPIllegalElementExceptionElementKey key in
|
||||
its @c userInfo dictionary.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const MPIllegalElementException;
|
||||
|
||||
|
||||
/*!
|
||||
@const MPIllegalElementExceptionElementKey
|
||||
@brief Predefined key for the invalid element that caused an exception
|
||||
to be raised.
|
||||
|
||||
@discussion The invalid element can be of any type.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey;
|
||||
|
||||
|
||||
/*!
|
||||
@typedef MPReferenceFrame
|
||||
@brief A reference frame describes what an @em item in an expression is.
|
||||
|
||||
@discussion Reference frames have to be passed in multiple methods of the @c
|
||||
MPExpression class. You can convert between reference frames
|
||||
using the following methods:
|
||||
<pre>
|
||||
@textblock
|
||||
- convertIndexFromReferenceFrame:toReferenceFrame:
|
||||
- convertIndexFromReferenceFrame:toReferenceFrame:offset:
|
||||
@/textblock
|
||||
</pre>
|
||||
|
||||
There are three different types of items (reference frames):
|
||||
symbols, tokens and elements. A symbol is the smalles possible
|
||||
unit in an expression. Thus a token consists of one or more
|
||||
symbols. Similarly an element consists of one or more tokens.
|
||||
|
||||
@constant MPElementReferenceFrame
|
||||
Specifies that items should be interpreted as elements. An
|
||||
element is either a @c NSString object or a @c MPFunction object.
|
||||
|
||||
@constant MPSymbolReferenceFrame
|
||||
Specifies that items should be interpreted as symbols. A symbol
|
||||
can be a single character (represented by the @c NSString class)
|
||||
or a @c MPFunction object.
|
||||
|
||||
@constant MPTokenReferenceFrame
|
||||
Specifies that items should be interpreted as tokens. A token is
|
||||
a logical unit of content in an expression. This can be a single
|
||||
character (e.g. '+'), an arbitrary number of characters (e.g. a
|
||||
number) or a @c MPFunction object.
|
||||
|
||||
All tokens conform to the @c MPToken protocol.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MPReferenceFrame) {
|
||||
MPElementReferenceFrame,
|
||||
MPSymbolReferenceFrame,
|
||||
MPTokenReferenceFrame
|
||||
};
|
||||
|
||||
|
||||
|
||||
@class MPExpression, MPFunction, MPRangePath, MPParsedExpression;
|
||||
@protocol MPExpressionElement, MPToken;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPExpression
|
||||
@brief An @c MPExpression object is the base object for any mathematical
|
||||
expression.
|
||||
|
||||
@discussion Every expression consists of string elements (represented by the
|
||||
@c NSString class) and function elements (represented by the @c
|
||||
MPFunction class). Functions likewise can have expressions as
|
||||
elements (also called <i>children</i> in this context). Both
|
||||
expressions and functions are mutable. Through this organization
|
||||
expression are organized in a tree-like structure called the
|
||||
'expression tree'.
|
||||
|
||||
An expression can evaluate itself giving you either a
|
||||
result or possibly an error if the expression was not constructed
|
||||
correctly or could not be evaluated.
|
||||
*/
|
||||
@interface MPExpression : NSObject <NSCopying, NSCoding>
|
||||
|
||||
|
||||
#pragma mark Creation Methods
|
||||
/*!
|
||||
@methodgroup Creation Methods
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method init
|
||||
@brief Initlializes a newly created expression.
|
||||
|
||||
@discussion This method is a convenience initializer to initialize an
|
||||
expression with @c 0 elements.
|
||||
|
||||
@return An expression.
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
|
||||
/*!
|
||||
@method initWithElement:
|
||||
@brief Initializes a newly created expression with @c element.
|
||||
|
||||
@discussion This method is a convenience initializer to initialize an
|
||||
expression with a single element.
|
||||
|
||||
@param element
|
||||
The element to be added to the expression. The @c element will be
|
||||
copied.
|
||||
|
||||
@return An expression initialized with @c element.
|
||||
*/
|
||||
- (instancetype)initWithElement:(id<MPExpressionElement>)element;
|
||||
|
||||
|
||||
/*!
|
||||
@method initWithElements:
|
||||
@brief Initializes a newly created expression with the specified
|
||||
elements.
|
||||
|
||||
@discussion All elements must conform to the @c MPExpressionElement protocol.
|
||||
If one or more objects do not conform to that protocol an @c
|
||||
MPIllegalElementException is raised.
|
||||
|
||||
This method is the designated initializer for the @c MPExpression
|
||||
class.
|
||||
|
||||
@param elements
|
||||
The elements that should be added to the expression. Every
|
||||
element must conform to the @c MPExpressionElement protocol. Each
|
||||
element is copied and the copy is then added to the expression.
|
||||
The object in the @c elements array is not modified.
|
||||
|
||||
@return An expression containing @c elements.
|
||||
*/
|
||||
- (instancetype)initWithElements:(NSArray *)elements; /* designated initializer */
|
||||
|
||||
|
||||
#pragma mark Querying Expressions
|
||||
/*!
|
||||
@methodgroup Querying Expressions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@property parent
|
||||
@brief The receiver's parent.
|
||||
|
||||
@discussion Expressions are organized in a tree-like structure. Through this
|
||||
property an expression's containing function can be accessed.
|
||||
@warning You should never set this property manually. If you are
|
||||
implementing a custom subclass of @c MPFunction use the @c
|
||||
MPFunctionAccessorImplementation macro.
|
||||
|
||||
@return The parent of the receiver or @c nil if the receiver is the root
|
||||
expression.
|
||||
*/
|
||||
@property (nonatomic, weak) MPFunction *parent;
|
||||
|
||||
|
||||
/*!
|
||||
@method rootExpression
|
||||
@brief Returns the root expression from the receiver's expression tree.
|
||||
|
||||
@discussion The root expression is the ultimate parent of all expressions and
|
||||
functions in the expression tree. A root expression does not have
|
||||
a parent.
|
||||
|
||||
@return The root expression from the receiver's expression tree.
|
||||
*/
|
||||
- (MPExpression *)rootExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPath
|
||||
@brief Returns the index path of the receiver in the expression tree.
|
||||
|
||||
@discussion The index path is calculated by going up the expression tree
|
||||
collecting the respective index of the receiver. The indexes are
|
||||
expressed in the element reference frame. If any of the indexes
|
||||
exceed the respective receiver's bounds a @c NSRangeException is
|
||||
raised.
|
||||
|
||||
@return The index path of the receiver in the expression tree.
|
||||
*/
|
||||
- (NSIndexPath *)indexPath;
|
||||
|
||||
|
||||
/*!
|
||||
@method countItemsInReferenceFrame:
|
||||
@brief Returns the number of items in the receiver in the respective
|
||||
reference frame.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame that should be used to count the items.
|
||||
|
||||
@return The current number of items in the receiver counted in the
|
||||
specified reference frame.
|
||||
*/
|
||||
- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method itemAtIndex:referenceFrame:
|
||||
@brief Returns the item at @c anIndex.
|
||||
|
||||
@discussion The item is not copied before it is returned. So be aware that
|
||||
if you mutate a @c MPFunction object returned from this function
|
||||
the changes will be reflected in the receiver.
|
||||
|
||||
@param anIndex
|
||||
The index of the item. If the index is greater than the number of
|
||||
items for the respective reference frame an @c NSRangeException
|
||||
is raised.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame that should be used to identify the item at
|
||||
@c anIndex.
|
||||
|
||||
@return The item at @c anIndex.
|
||||
*/
|
||||
- (id)itemAtIndex:(NSUInteger)anIndex
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method elementAtIndex:referenceFrame:
|
||||
@brief Returns the element at the specified index.
|
||||
|
||||
@discussion An element is either a string or a function. If @c anIndex
|
||||
specifies a position inside a string the complete string element
|
||||
is returned.
|
||||
|
||||
This method does not return any item in any reference frame but
|
||||
only complete elements. Use @c -itemAtIndex:referenceFrame: for
|
||||
that purpose.
|
||||
|
||||
@param anIndex
|
||||
The index of the element.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c anIndex is specified in.
|
||||
|
||||
@return The item at @c anIndex.
|
||||
*/
|
||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
/*!
|
||||
@method indexOfElement:
|
||||
@brief Returns the index of @c element or @c NSNotFound if it was not
|
||||
found.
|
||||
|
||||
@param element
|
||||
The element to find.
|
||||
|
||||
@return The index of @c element expressed in the element reference frame.
|
||||
*/
|
||||
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;
|
||||
|
||||
|
||||
/*!
|
||||
@method itemsInRange:referenceFrame:
|
||||
@brief Returns an array of the items that are located in the specified
|
||||
range.
|
||||
|
||||
@discussion The objects in the returned array are not copied before they are
|
||||
returned. You should be aware of the fact that mutations to any
|
||||
returned objects will be reflected in the receiver.
|
||||
|
||||
If the @c range exceeds the receiver's bounds an @c
|
||||
NSRangeException is raised.
|
||||
|
||||
@param aRange
|
||||
The requested range within the receiver's bounds.
|
||||
|
||||
@param referenceFrame
|
||||
The referenceFrame @c aRange is expressed in.
|
||||
|
||||
@return An array of objects that conform to the @c MPExpressionElement
|
||||
protocol (that is @c NSString objects and @c MPFunction objects).
|
||||
The length of the returned array is equal to the length of the
|
||||
specified range.
|
||||
*/
|
||||
- (NSArray *)itemsInRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method allItemsInReferenceFrame:
|
||||
@brief Returns an array of all items in the receiver for the specified
|
||||
reference frame.
|
||||
|
||||
@discussion The elements in the returned array are not copied before they are
|
||||
returned so be aware that mutations to any of the returned
|
||||
objects will be reflected in the receiver.
|
||||
|
||||
@return An array of all items in the receiver.
|
||||
*/
|
||||
- (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method elementAtIndexPath:
|
||||
@brief Returns the element at the specified index path.
|
||||
|
||||
@discussion This method @em walks down the expression tree (including
|
||||
functions) using the specified index path and finds the
|
||||
corresponding element. The returned object can be an @c NSString,
|
||||
a @c MPFunction or an @c MPExpression depending on the element @c
|
||||
indexPath points to. If any of the indexes exceed the bounds of
|
||||
the respective receiver an @c NSRangeException is raised.
|
||||
|
||||
If the index path does not contain any indexes the receiver
|
||||
itself is returned.
|
||||
|
||||
@param indexPath
|
||||
The index path the required object is located at. The indexes are
|
||||
expressed in the element reference frame.
|
||||
|
||||
@return The element located at @c indexPath. The element is not copied
|
||||
before it is returned. Be aware of the fact that any mutations
|
||||
made to the returned object are reflected in the receiver.
|
||||
*/
|
||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
|
||||
/*!
|
||||
@method convertIndex:fromReferenceFrame:toReferenceFrame:
|
||||
@brief Converts an index from one reference frame to another.
|
||||
|
||||
@discussion This method ignores any offsets into items the conversion between
|
||||
reference frames can cause. If you are interested in the offset
|
||||
use @c -convertIndex:fromReferenceFrame:toReferenceFrame:offset:
|
||||
instead.
|
||||
|
||||
@param anIndex
|
||||
The index to be converted.
|
||||
|
||||
@param fromReferenceFrame
|
||||
The reference frame @c anIndex is specified in.
|
||||
|
||||
@param toReferenceFrame
|
||||
The reference frame @c anIndex should be converted to.
|
||||
|
||||
@return An index specified in the @c toReferenceFrame. If @c anIndex
|
||||
specifies a location inside an item in the @c toReferenceFrame
|
||||
the index of the corresponding item is returned.
|
||||
*/
|
||||
- (NSUInteger)convertIndex:(NSUInteger)anIndex
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method convertIndex:fromReferenceFrame:toReferenceFrame:offset:
|
||||
@brief Converts an index from one reference frame to another.
|
||||
|
||||
@discussion If @c anIndex specifies a location inside an item in the @c
|
||||
toReferenceFrame the index of the corresponding item is returned.
|
||||
|
||||
@param anIndex
|
||||
The index to be converted.
|
||||
|
||||
@param fromReferenceFrame
|
||||
The reference frame @c anIndex is specified in.
|
||||
|
||||
@param toReferenceFrame
|
||||
The reference frame @c anIndex should be converted to.
|
||||
|
||||
@param offset
|
||||
This output parameter will be set to the number of symbols that
|
||||
are between the location specified by @c anIndex and the index
|
||||
that is actually returned. The offset is specified in the symbol
|
||||
reference frame. If you are not interested in the offset pass
|
||||
@c NULL.
|
||||
|
||||
@return An index specified in the @c toReferenceFrame corresponding to
|
||||
@c anIndex.
|
||||
*/
|
||||
- (NSUInteger)convertIndex:(NSUInteger)anIndex
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
offset:(out NSUInteger *)offset;
|
||||
|
||||
|
||||
/*!
|
||||
@method convertRange:fromReferenceFrame:toReferenceFrame:
|
||||
@brief Converts a range from one reference frame to another.
|
||||
|
||||
@discussion This method just converts the location and target of the range
|
||||
separately using @c
|
||||
-convertIndex:fromReferenceFrame:toReferenceFrame:. This method
|
||||
ensures that the returned range definitely includes all items
|
||||
that were specified by @c aRange.
|
||||
|
||||
This method ignores the possibility that the returned range may
|
||||
include more than @a aRange. If you need that information use
|
||||
@c -convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:.
|
||||
|
||||
@param aRange
|
||||
The range to be converted.
|
||||
|
||||
@param fromReferenceFrame
|
||||
The reference frame @c aRange is specified in.
|
||||
|
||||
@param toReferenceFrame
|
||||
The reference frame @c aRange is to be converted to.
|
||||
|
||||
@return A range in the @c toReferenceFrame that includes the the items
|
||||
specified by @c aRange.
|
||||
*/
|
||||
- (NSRange)convertRange:(NSRange)aRange
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:
|
||||
@brief Converts a range from one reference frame to another.
|
||||
|
||||
@discussion This method just converts the location and target of the range
|
||||
separately using @c
|
||||
-convertIndex:fromReferenceFrame:toReferenceFrame:. This method
|
||||
ensures that the returned range definitely includes all items
|
||||
that were specified by @c aRange.
|
||||
|
||||
@param aRange
|
||||
The range to be converted.
|
||||
|
||||
@param fromReferenceFrame
|
||||
The reference frame @c aRange is specified in.
|
||||
|
||||
@param toReferenceFrame
|
||||
The reference frame @c aRange is to be converted to.
|
||||
|
||||
@param leadingOffset
|
||||
The offset of the location of the returned range in respect to
|
||||
the location of @c aRange.
|
||||
|
||||
@param trailingOffset
|
||||
The offset of the last index in the range in respect to the last
|
||||
index of @c aRange.
|
||||
|
||||
@return A range in the @c toReferenceFrame that includes the the items
|
||||
specified by @c aRange.
|
||||
*/
|
||||
- (NSRange)convertRange:(NSRange)aRange
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
leadingOffset:(NSUInteger *)leadingOffset
|
||||
trailingOffset:(NSUInteger *)trailingOffset;
|
||||
|
||||
|
||||
#pragma mark Mutating Expressions
|
||||
/*!
|
||||
@methodgroup Mutating Expressions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method replaceItemsInRange:referenceFrame:withElements:
|
||||
@brief Replaces the elements in the specified range with the contents of
|
||||
the @c elements array.
|
||||
|
||||
@discussion This is the most primitive mutation method of @c MPExpression.
|
||||
Every other mutating method utlimately must call this method.
|
||||
|
||||
After the receiver has been mutated the integrety of the receiver
|
||||
is restored. That basically means that subsequent strings are
|
||||
joined and empty strings removed. After restoring integrity the
|
||||
receiver sends a @c
|
||||
-changedElementsInIndexedRangePath:replacementLength: to
|
||||
itself. For more information see the documentation on that
|
||||
method.
|
||||
|
||||
@param aRange
|
||||
The @c range of symbols (including functions) to replace.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c aRange is specified in.
|
||||
|
||||
@param elements
|
||||
The elements that should replace the symbols specified by @c
|
||||
range.
|
||||
*/
|
||||
- (void)replaceItemsInRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
withElements:(NSArray *)elements;
|
||||
|
||||
|
||||
/*!
|
||||
@method changedElementsInRangePath:replacementLength:
|
||||
@brief Called after the receiver has been mutated.
|
||||
|
||||
@discussion This method does nothing more than notify it's parent that it has
|
||||
been mutated at the receiver's index. If you need to know about
|
||||
changes in an expression you should override this method instead
|
||||
of @c -replaceSymbolsInRange:withElements: because this method
|
||||
gives you information about the number of elements changed during
|
||||
the mutation.
|
||||
|
||||
@param rangePath
|
||||
The range path at which the receiver was changed starting at the
|
||||
receiver. The range addressed by @c rangePath is expressed in the
|
||||
element reference frame.
|
||||
|
||||
@param replacementLength
|
||||
The number of elements replacing the elements specified by @c
|
||||
rangePath (also specified in the element reference frame).
|
||||
*/
|
||||
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
|
||||
replacementLength:(NSUInteger)replacementLength;
|
||||
|
||||
|
||||
/*!
|
||||
@method subexpressionFromIndex:referenceFrame:
|
||||
@brief Creates an expression from the items in the receiver from the
|
||||
specified index to the end.
|
||||
|
||||
@discussion The items from the receiver are copied. Mutations to the returned
|
||||
expression will not change the receiver.
|
||||
|
||||
@param from
|
||||
The index from which to start constructing the subexpression.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c from is specified in.
|
||||
|
||||
@return An expression containing the items from the specified index to
|
||||
the end of the receiver.
|
||||
*/
|
||||
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method subexpressionToIndex:referenceFrame:
|
||||
@brief Creates an expression from the items in the receiver from the
|
||||
first item to the end.
|
||||
|
||||
@discussion The items from the receiver are copied. Mutations to the returned
|
||||
expression will not change the receiver.
|
||||
|
||||
@param to
|
||||
The index of the first element not to include in the newly
|
||||
constructed subexpression.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c to is specified in.
|
||||
|
||||
@return An expression containing the items from the first item to the one
|
||||
at the specified index.
|
||||
*/
|
||||
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method subexpressionWithRange:referenceFrame:
|
||||
@brief Creates an expression from the items in the receiver within the
|
||||
specified range.
|
||||
|
||||
@discussion The items from the receiver are copied. Mutations to the returned
|
||||
expression will not change the receiver.
|
||||
|
||||
@param aRange
|
||||
Specifies the items to be included in the newly created
|
||||
subexpression.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c aRange is specified in.
|
||||
|
||||
@return An expression containing the items in @c aRange.
|
||||
*/
|
||||
- (MPExpression *)subexpressionWithRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
#pragma mark Evaluating Expressions
|
||||
/*!
|
||||
@methodgroup Evaluating Expressions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method evaluateWitErrors:
|
||||
@brief Evaluates the receiver.
|
||||
|
||||
@discussion This is a convenience method for evaluating an expression. If you
|
||||
want more control over the evaluation process use @c -parse
|
||||
instead.
|
||||
|
||||
@param errors
|
||||
If the receiver (or any of its elements) contain syntax errors or
|
||||
can not be evaluated this parameter is set to an appropriate
|
||||
value. All items in the array are @c NSError instances. This
|
||||
parameter is never set to an empty array.
|
||||
Pass @c NULL if you are not interested in any errors that might
|
||||
occur.
|
||||
|
||||
@return The result of the evaluation or @c nil of the receiver could not
|
||||
be evaluated. In that case the @c error parameter is set to an
|
||||
appropriate value.
|
||||
*/
|
||||
- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors;
|
||||
|
||||
|
||||
/*!
|
||||
@method parse:
|
||||
@brief Parses the receiver.
|
||||
|
||||
@discussion This is a convenience method that calls @c
|
||||
-parseExpectingVariable:errors: with @c NO as the first argument.
|
||||
|
||||
@param errors
|
||||
If the receiver (or any of its elements) contain syntax errors
|
||||
this parameter is set to an appropriate value. All items in the
|
||||
array are @c NSError instances. This parameter is never set to an
|
||||
empty array.
|
||||
Pass @c NULL if you are not interested in any errors that might
|
||||
occur.
|
||||
|
||||
@return A @c MPParsedExpression object that represents the receiver and
|
||||
can be evaluated or @c nil if an error occurs. In that case the
|
||||
@c errors parameter is set to an array containing at least one
|
||||
@c NSError instance.
|
||||
*/
|
||||
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
|
||||
|
||||
|
||||
/*!
|
||||
@method parseExpectingVariable:errors:
|
||||
@brief Parses the receiver.
|
||||
|
||||
@param flag
|
||||
If @c YES the receiver must (exept for whitespaces) begin with a
|
||||
single letter followed by an equals sign. If it doesn't the
|
||||
expression is considered invalid. Specify @c NO If the expression
|
||||
should be evaluatable by itself.
|
||||
|
||||
@param errors
|
||||
If the receiver (or any of its elements) contain syntax errors
|
||||
this parameter is set to an appropriate value. All items in the
|
||||
array are @c NSError instances. This parameter is never set to an
|
||||
empty array.
|
||||
Pass @c NULL if you are not interested in any errors that might
|
||||
occur.
|
||||
|
||||
@return A @c MPParsedExpression object that represents the receiver and
|
||||
can be evaluated or @c nil if an error occurs. In that case the
|
||||
@c errors parameter is set to an array containing at least one
|
||||
@c NSError instance.
|
||||
*/
|
||||
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
|
||||
errors:(NSArray *__autoreleasing *)errors;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@category MPExpression (MPExpressionConvenience)
|
||||
@brief This category defines convenience methods for the @c MPExpression
|
||||
class.
|
||||
|
||||
@discussion All convenience methods are completely defined in terms of other
|
||||
methods of the @c MPExpression class.
|
||||
*/
|
||||
@interface MPExpression (MPExpressionConvenience)
|
||||
|
||||
#pragma mark Querying Expressions
|
||||
/*!
|
||||
@methodgroup Querying Expressions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method countElements
|
||||
@brief Returns the number of elements in the receiver.
|
||||
|
||||
@return The number of elements in the receiver expressed in the element
|
||||
reference frame.
|
||||
*/
|
||||
- (NSUInteger)countElements;
|
||||
|
||||
|
||||
/*!
|
||||
@method countSymbols
|
||||
@brief Returns the number of symbols in the receiver.
|
||||
|
||||
@return The number of symbols in the receiver expressed in the symbol
|
||||
reference frame.
|
||||
*/
|
||||
- (NSUInteger)countSymbols;
|
||||
|
||||
|
||||
/*!
|
||||
@method countTokens
|
||||
@brief Returns the number of tokens in the receiver.
|
||||
|
||||
@return The number of tokens in the receiver expressed in the token
|
||||
reference frame.
|
||||
*/
|
||||
- (NSUInteger)countTokens;
|
||||
|
||||
|
||||
/*!
|
||||
@method elementAtIndex:
|
||||
@brief Returns the element at the specified index.
|
||||
|
||||
@param anIndex
|
||||
The index of the element specified in the element reference
|
||||
frame.
|
||||
|
||||
@return The element at @c anIndex.
|
||||
*/
|
||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex;
|
||||
|
||||
|
||||
/*!
|
||||
@method symbolAtIndex:
|
||||
@brief Returns the symbol at the specified index.
|
||||
|
||||
@param anIndex
|
||||
The index of the symbol specified in the symbol reference frame.
|
||||
|
||||
@return The symbol at @c anIndex.
|
||||
*/
|
||||
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)anIndex;
|
||||
|
||||
|
||||
/*!
|
||||
@method tokenAtIndex:
|
||||
@brief Returns the token at the specified index.
|
||||
|
||||
@param anIndex
|
||||
The index of the token specified in the token reference frame.
|
||||
|
||||
@return The token at @c anIndex.
|
||||
*/
|
||||
- (id<MPToken>)tokenAtIndex:(NSUInteger)anIndex;
|
||||
|
||||
|
||||
#pragma mark Mutating Expressions
|
||||
/*!
|
||||
@methodgroup Mutating Expressions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method appendElement:
|
||||
@brief Appends @c anElement to the receiver.
|
||||
|
||||
@param anElement
|
||||
The element to append to the receiver.
|
||||
*/
|
||||
- (void)appendElement:(id<MPExpressionElement>)anElement;
|
||||
|
||||
|
||||
/*!
|
||||
@method appendElements:
|
||||
@brief Appends the objects from @c elements to the receiver.
|
||||
|
||||
@param elements
|
||||
The elements to append to the receiver.
|
||||
*/
|
||||
- (void)appendElements:(NSArray *)elements;
|
||||
|
||||
|
||||
/*!
|
||||
@method insertElement:atIndex:referenceFrame:
|
||||
@brief Inserts @c anElement at the specified index.
|
||||
|
||||
@param anElement
|
||||
The element to be inserted.
|
||||
|
||||
@param index
|
||||
The index where to insert @c anElement.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c index is specified in.
|
||||
*/
|
||||
- (void)insertElement:(id<MPExpressionElement>)anElement
|
||||
atIndex:(NSUInteger)index
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method insertElements:atIndex:referenceFrame:
|
||||
@brief Inserts @c elements at the specified index.
|
||||
|
||||
@param elements
|
||||
The elements to be inserted.
|
||||
|
||||
@param index
|
||||
The index where to insert @c elements.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c index is specified in.
|
||||
*/
|
||||
- (void)insertElements:(NSArray *)elements
|
||||
atIndex:(NSUInteger)index
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method deleteElementsInRange:
|
||||
@brief Removes the elements specified by @c range from the receiver.
|
||||
|
||||
@param range
|
||||
The range of items to remove from the receiver.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame @c range is specified in.
|
||||
*/
|
||||
- (void)deleteElementsInRange:(NSRange)range
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
@end
|
||||
@@ -1,835 +0,0 @@
|
||||
//
|
||||
// MPExpression.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 10.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpression.h"
|
||||
|
||||
#import "MPExpressionElement.h"
|
||||
#import "MPFunction.h"
|
||||
|
||||
#import "MPRangePath.h"
|
||||
|
||||
#import "MPExpressionTokenizer.h"
|
||||
#import "MPToken.h"
|
||||
#import "MPExpressionParser.h"
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
|
||||
|
||||
NSString *const MPIllegalElementException = @"Illegal Element Exception";
|
||||
NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey";
|
||||
|
||||
|
||||
@interface MPExpression () {
|
||||
NSMutableArray * _elements;
|
||||
}
|
||||
|
||||
/*!
|
||||
@method tokens
|
||||
@brief Private method. Returns an array containing all tokens from the
|
||||
receiver.
|
||||
|
||||
@return All items in the @c MPTokenReferenceFrame.
|
||||
*/
|
||||
- (NSArray *)tokens;
|
||||
|
||||
|
||||
/*!
|
||||
@method validateElements:
|
||||
@brief Private method. Checks whether all objects in the specified array
|
||||
are valid expression elements.
|
||||
|
||||
@discussion If an object is not valid a @c MPIllegalElementException is
|
||||
raised.
|
||||
|
||||
@param elements
|
||||
The array of objects to be validated.
|
||||
*/
|
||||
- (void)validateElements:(NSArray *)elements;
|
||||
|
||||
|
||||
/*!
|
||||
@method fixElements
|
||||
@brief Private method. Restores consistency in the receiver after a
|
||||
change was made.
|
||||
*/
|
||||
- (void)fixElements;
|
||||
|
||||
|
||||
/*!
|
||||
@method _replaceSymbolsInRange:withElements:
|
||||
@brief Private method. Replaces the symbols in the specified range with
|
||||
the elements from the @c elements array.
|
||||
|
||||
@discussion This is the most primitive mutation method of the @c MPExpression
|
||||
class.
|
||||
|
||||
@param range
|
||||
The range of symbols to be replaced. The range is specified in
|
||||
the symbol reference frame. If the range exceeds the receiver's
|
||||
bounds a @c NSRangeException is raised.
|
||||
|
||||
@param elements
|
||||
The elements that should replace the symbols in the specified
|
||||
range.
|
||||
*/
|
||||
- (void)_replaceSymbolsInRange:(NSRange)range
|
||||
withElements:(NSArray *)elements;
|
||||
|
||||
|
||||
/*!
|
||||
@method _splitElementsAtLocation:insertionIndex:
|
||||
@brief Splits the receiver's elements at the specified @c location.
|
||||
|
||||
@discussion The split location can be inside a string element. In that case
|
||||
the string is replaced with two other smaller strings.
|
||||
|
||||
Splitting the elements destroys the receiver's integrity. The
|
||||
receiver of this message must also receive a @c -fixElements
|
||||
message soon afterwards.
|
||||
|
||||
@param location
|
||||
The index where to split the elements. Specified in the symbol
|
||||
reference frame.
|
||||
|
||||
@param insertionIndex
|
||||
Splitting elements may shift the indexes of the following
|
||||
elements. This parameter is set to the index (specified in the
|
||||
element reference frame) that should be used to insert a new
|
||||
element at the location where the elements were previously
|
||||
splitted.
|
||||
|
||||
@return @c YES if a string element was split into two smaller strings, @c
|
||||
NO if the split @c location corresponds to a location between two
|
||||
elements.
|
||||
*/
|
||||
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
|
||||
insertionIndex:(NSUInteger *)insertionIndex;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPExpression {
|
||||
NSArray *_tokenCache;
|
||||
|
||||
NSRange _editedRange;
|
||||
BOOL _didSplitStartOnEditing;
|
||||
BOOL _didSplitEndOnEditing;
|
||||
NSUInteger _replacementLength;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Creation Methods
|
||||
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithElements:@[]];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithElement:(id<MPExpressionElement>)element
|
||||
{
|
||||
return [self initWithElements:@[element]];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithElements:(NSArray *)elements
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self validateElements:elements];
|
||||
_elements = [[NSMutableArray alloc] initWithArray:elements
|
||||
copyItems:YES];
|
||||
[self fixElements];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Private Methods
|
||||
|
||||
|
||||
- (NSArray *)tokens
|
||||
{
|
||||
if (!_tokenCache) {
|
||||
_tokenCache = [MPExpressionTokenizer tokenizeExpression:self];
|
||||
}
|
||||
return _tokenCache;
|
||||
}
|
||||
|
||||
|
||||
- (void)validateElements:(NSArray *)elements
|
||||
{
|
||||
for (id element in elements) {
|
||||
if (![element conformsToProtocol:@protocol(MPExpressionElement)]) {
|
||||
@throw [NSException exceptionWithName:MPIllegalElementException
|
||||
reason:@"Elements must conform to the MPExpressionElement protocol."
|
||||
userInfo:@{MPIllegalElementExceptionElementKey: element}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)fixElements
|
||||
{
|
||||
for (NSUInteger index = 0; index < _elements.count; index++) {
|
||||
id<MPExpressionElement> next = index+1 < _elements.count ? _elements[index+1] : nil;
|
||||
id<MPExpressionElement> current = _elements[index];
|
||||
if ([current isString]) {
|
||||
if (current.length == 0) {
|
||||
[_elements removeObjectAtIndex:index];
|
||||
if (index >= _editedRange.location && index < NSMaxRange(_editedRange)) {
|
||||
--_replacementLength;
|
||||
}
|
||||
--index;
|
||||
} else if ([next isString]) {
|
||||
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
|
||||
[_elements replaceObjectsInRange:NSMakeRange(index, 2)
|
||||
withObjectsFromArray:@[new]];
|
||||
NSUInteger maxReplacementIndex = _editedRange.location + _replacementLength;
|
||||
if (index == _editedRange.location-1 && !_didSplitStartOnEditing) {
|
||||
--_editedRange.location;
|
||||
++_editedRange.length;
|
||||
if (index == maxReplacementIndex-1 && !_didSplitEndOnEditing) {
|
||||
++_editedRange.length;
|
||||
++_replacementLength;
|
||||
}
|
||||
} else if (index >= _editedRange.location && index < maxReplacementIndex - 1) {
|
||||
--_replacementLength;
|
||||
} else if (index == maxReplacementIndex-1 && !_didSplitEndOnEditing) {
|
||||
++_editedRange.length;
|
||||
}
|
||||
--index;
|
||||
}
|
||||
} else {
|
||||
[(MPFunction *)current setParent:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Querying Expressions
|
||||
|
||||
|
||||
- (MPExpression *)rootExpression
|
||||
{
|
||||
if (self.parent == nil) {
|
||||
return self;
|
||||
}
|
||||
return [self.parent rootExpression];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPath
|
||||
{
|
||||
if (self.parent) {
|
||||
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
||||
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
|
||||
} else {
|
||||
return [[NSIndexPath alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
switch (referenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
return _elements.count;
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
for (id<MPExpressionElement> element in _elements) {
|
||||
count += element.length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
return self.tokens.count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (id)itemAtIndex:(NSUInteger)anIndex
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
switch (referenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
return _elements[anIndex];
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
{
|
||||
NSUInteger offsetInElement;
|
||||
NSUInteger elementIndex = [self convertIndex:anIndex
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
offset:&offsetInElement];
|
||||
id<MPExpressionElement> element = [self elementAtIndex:elementIndex];
|
||||
if ([element isFunction]) {
|
||||
return element;
|
||||
} else {
|
||||
return [((NSString *)element) substringWithRange:NSMakeRange(offsetInElement, 1)];
|
||||
}
|
||||
}
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
return self.tokens[anIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
NSUInteger elementIndex = [self convertIndex:anIndex
|
||||
fromReferenceFrame:referenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame];
|
||||
return _elements[elementIndex];
|
||||
}
|
||||
|
||||
|
||||
#warning If multiple equal expressions exist errors may occur...
|
||||
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
|
||||
{
|
||||
return [_elements indexOfObject:element];
|
||||
}
|
||||
|
||||
|
||||
- (NSArray *)itemsInRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
MPExpression *subexpression = [self subexpressionWithRange:aRange
|
||||
referenceFrame:referenceFrame];
|
||||
return [subexpression allItemsInReferenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
switch (referenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
return _elements;
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
{
|
||||
NSMutableArray *symbols = [[NSMutableArray alloc] init];
|
||||
for (id<MPExpressionElement> element in _elements) {
|
||||
if ([element isString]) {
|
||||
for (NSUInteger i = 0; i < [element length]; i++) {
|
||||
NSString *ichar = [NSString stringWithFormat:@"%c", [((NSString *)element) characterAtIndex:i]];
|
||||
[symbols addObject:ichar];
|
||||
}
|
||||
} else {
|
||||
[symbols addObject:element];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
return self.tokens;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.length == 0) {
|
||||
return self;
|
||||
}
|
||||
id<MPExpressionElement> element = _elements[[indexPath indexAtPosition:0]];
|
||||
if (indexPath.length == 1) {
|
||||
return element;
|
||||
}
|
||||
if ([element isFunction]) {
|
||||
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
|
||||
}
|
||||
// TODO: Raise appropriate exeption.
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)convertIndex:(NSUInteger)anIndex
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
{
|
||||
return [self convertIndex:anIndex
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
offset:NULL];
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)convertIndex:(NSUInteger)anIndex
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
offset:(NSUInteger *)offset
|
||||
{
|
||||
if (fromReferenceFrame == toReferenceFrame || anIndex == 0) {
|
||||
if (offset) {
|
||||
*offset = 0;
|
||||
}
|
||||
return anIndex;
|
||||
}
|
||||
|
||||
NSUInteger symbolIndex __block = 0;
|
||||
switch (fromReferenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
[_elements enumerateObjectsUsingBlock:^(id<MPExpressionElement> obj, NSUInteger idx, BOOL *stop) {
|
||||
symbolIndex += obj.length;
|
||||
*stop = idx >= anIndex - 1;
|
||||
}];
|
||||
break;
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
symbolIndex = anIndex;
|
||||
break;
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
[self.tokens enumerateObjectsUsingBlock:^(id<MPToken> obj, NSUInteger idx, BOOL *stop) {
|
||||
symbolIndex += obj.range.length;
|
||||
*stop = idx >= anIndex - 1;
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
switch (toReferenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
{
|
||||
NSUInteger totalLength = 0;
|
||||
NSUInteger elementIndex = 0;
|
||||
id<MPExpressionElement> element;
|
||||
while (totalLength < symbolIndex) {
|
||||
element = _elements[elementIndex++];
|
||||
totalLength += element.length;
|
||||
}
|
||||
--elementIndex;
|
||||
NSUInteger offsetInElement = element.length - totalLength + symbolIndex;
|
||||
if (offsetInElement == element.length) {
|
||||
offsetInElement = 0;
|
||||
elementIndex++;
|
||||
}
|
||||
if (offset) {
|
||||
*offset = offsetInElement;
|
||||
}
|
||||
return elementIndex;
|
||||
}
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
if (offset) {
|
||||
*offset = 0;
|
||||
}
|
||||
return symbolIndex;
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
{
|
||||
NSUInteger totalLength = 0;
|
||||
NSUInteger tokenIndex = 0;
|
||||
id<MPToken> token;
|
||||
while (totalLength < symbolIndex) {
|
||||
token = self.tokens[tokenIndex++];
|
||||
totalLength += token.range.length;
|
||||
}
|
||||
--tokenIndex;
|
||||
NSUInteger offsetInToken = token.range.length - totalLength + symbolIndex;
|
||||
if (offsetInToken == token.range.length) {
|
||||
offsetInToken = 0;
|
||||
tokenIndex++;
|
||||
}
|
||||
if (offset) {
|
||||
*offset = offsetInToken;
|
||||
}
|
||||
return tokenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (NSRange)convertRange:(NSRange)aRange
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
{
|
||||
return [self convertRange:aRange
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
leadingOffset:NULL
|
||||
trailingOffset:NULL];
|
||||
}
|
||||
|
||||
|
||||
- (NSRange)convertRange:(NSRange)aRange
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
leadingOffset:(NSUInteger *)leadingOffset
|
||||
trailingOffset:(NSUInteger *)trailingOffset
|
||||
{
|
||||
NSUInteger start = [self convertIndex:aRange.location
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
offset:leadingOffset];
|
||||
NSUInteger end = [self convertIndex:NSMaxRange(aRange)
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
offset:trailingOffset];
|
||||
return NSMakeRange(start, end - start);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Mutating Expressions
|
||||
|
||||
|
||||
- (void)replaceItemsInRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
withElements:(NSArray *)elements
|
||||
{
|
||||
NSUInteger start = [self convertIndex:aRange.location
|
||||
fromReferenceFrame:referenceFrame
|
||||
toReferenceFrame:MPSymbolReferenceFrame];
|
||||
NSUInteger end = [self convertIndex:NSMaxRange(aRange)
|
||||
fromReferenceFrame:referenceFrame
|
||||
toReferenceFrame:MPSymbolReferenceFrame];
|
||||
[self _replaceSymbolsInRange:NSMakeRange(start, end - start)
|
||||
withElements:elements];
|
||||
}
|
||||
|
||||
|
||||
- (void)_replaceSymbolsInRange:(NSRange)range
|
||||
withElements:(NSArray *)elements
|
||||
{
|
||||
if (NSMaxRange(range) > [self countItemsInReferenceFrame:MPSymbolReferenceFrame]) {
|
||||
@throw [NSException exceptionWithName:NSRangeException
|
||||
reason:@"Range out of bounds of expression"
|
||||
userInfo:nil];
|
||||
}
|
||||
[self validateElements:elements];
|
||||
|
||||
// Locate the position, split the elements
|
||||
NSUInteger startIndex; // startIndex is inclusive
|
||||
BOOL didSplitStart = NO;
|
||||
if (_elements.count == 0) {
|
||||
startIndex = 0;
|
||||
} else {
|
||||
didSplitStart = [self _splitElementsAtLocation:range.location
|
||||
insertionIndex:&startIndex];
|
||||
}
|
||||
NSUInteger endIndex; // endIndex is exclusive
|
||||
BOOL didSplitEnd = [self _splitElementsAtLocation:NSMaxRange(range)
|
||||
insertionIndex:&endIndex];
|
||||
|
||||
// Perform the replacement
|
||||
NSMutableArray *newElements = [[NSMutableArray alloc] initWithArray:elements
|
||||
copyItems:YES];
|
||||
NSArray *removedElements = [_elements subarrayWithRange:NSMakeRange(startIndex, endIndex-startIndex)];
|
||||
[_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
|
||||
withObjectsFromArray:newElements];
|
||||
for (id<MPExpressionElement> element in removedElements) {
|
||||
if ([element isFunction]) {
|
||||
((MPFunction *)element).parent = nil;
|
||||
}
|
||||
}
|
||||
|
||||
_tokenCache = nil;
|
||||
|
||||
NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0);
|
||||
NSUInteger editLength = endIndex - startIndex;
|
||||
if (range.length == 0 && didSplitStart) {
|
||||
editLength = 1;
|
||||
}
|
||||
|
||||
_editedRange = NSMakeRange(editLocation, editLength);
|
||||
_didSplitStartOnEditing = didSplitStart;
|
||||
_didSplitEndOnEditing = didSplitEnd;
|
||||
_replacementLength = elements.count;
|
||||
if (didSplitStart) {
|
||||
_replacementLength++;
|
||||
if (range.length == 0) {
|
||||
_replacementLength++;
|
||||
}
|
||||
}
|
||||
if (didSplitEnd) {
|
||||
_replacementLength++;
|
||||
}
|
||||
|
||||
[self fixElements];
|
||||
[self changedElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange]
|
||||
replacementLength:_replacementLength];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
|
||||
insertionIndex:(NSUInteger *)insertionIndex
|
||||
{
|
||||
if (location == 0) {
|
||||
*insertionIndex = 0;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSUInteger splitOffset;
|
||||
NSUInteger splitElementIndex = [self convertIndex:location
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
offset:&splitOffset];
|
||||
if (splitOffset != 0) {
|
||||
NSString *splitElement = (NSString *)_elements[splitElementIndex];
|
||||
NSString *leftPart = [splitElement substringToIndex:splitOffset];
|
||||
NSString *rightPart = [splitElement substringFromIndex:splitOffset];
|
||||
[_elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1)
|
||||
withObjectsFromArray:@[leftPart, rightPart]];
|
||||
++splitElementIndex;
|
||||
}
|
||||
*insertionIndex = splitElementIndex;
|
||||
return splitOffset != 0;
|
||||
}
|
||||
|
||||
|
||||
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
|
||||
replacementLength:(NSUInteger)replacementLength
|
||||
{
|
||||
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
||||
MPRangePath *newPath = MPMakeRangePath([rangePath.location indexPathByPreceedingIndex:selfIndex], rangePath.length);
|
||||
[self.parent didChangeElementsInRangePath:newPath
|
||||
replacementLength:replacementLength];
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(from, [self countItemsInReferenceFrame:referenceFrame] - from)
|
||||
referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(0, to)
|
||||
referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)subexpressionWithRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
MPExpression *subexpression = [self copy];
|
||||
NSRange preceedingRange = NSMakeRange(0, aRange.location);
|
||||
NSUInteger firstOut = NSMaxRange(aRange);
|
||||
NSRange exceedingRange = NSMakeRange(firstOut, [self countItemsInReferenceFrame:referenceFrame] - firstOut);
|
||||
[subexpression deleteElementsInRange:exceedingRange
|
||||
referenceFrame:referenceFrame];
|
||||
[subexpression deleteElementsInRange:preceedingRange
|
||||
referenceFrame:referenceFrame];
|
||||
return subexpression;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Evaluating Expressions
|
||||
|
||||
|
||||
- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
NSArray *parsingErrors;
|
||||
MPParsedExpression *parsedExpression = [self parse:&parsingErrors];
|
||||
NSError *evaluationError;
|
||||
NSDecimalNumber *result = parsedExpression ? [parsedExpression evaluate:&evaluationError] : nil;
|
||||
if (errors && (parsedExpression || evaluationError)) {
|
||||
NSMutableArray *localErrors = [[NSMutableArray alloc] initWithCapacity:parsingErrors.count+1];
|
||||
if (parsingErrors) {
|
||||
[localErrors addObjectsFromArray:parsingErrors];
|
||||
}
|
||||
if (evaluationError) {
|
||||
[localErrors addObject:evaluationError];
|
||||
}
|
||||
*errors = localErrors;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
return [self parseExpectingVariable:NO
|
||||
errors:errors];
|
||||
}
|
||||
|
||||
|
||||
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
|
||||
errors:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
return [[[MPExpressionParser alloc] initWithExpression:self] parseExpectingVariableDefinition:flag
|
||||
errors:errors];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Basic NSObject Methods
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
#warning Bad Implementation
|
||||
NSMutableString *description = [[NSMutableString alloc] init];
|
||||
NSUInteger index = 0;
|
||||
for (id element in _elements) {
|
||||
if ([element isString]) {
|
||||
NSMutableString *correctedSymbol = [[element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
|
||||
// Prefix operator
|
||||
if (element != _elements[0]) {
|
||||
unichar prefix = [correctedSymbol characterAtIndex:0];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
|
||||
[correctedSymbol insertString:@"*"
|
||||
atIndex:0];
|
||||
}
|
||||
}
|
||||
// Suffix operator
|
||||
if (element != [_elements lastObject]) {
|
||||
unichar suffix = [correctedSymbol characterAtIndex:correctedSymbol.length-1];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:suffix]) {
|
||||
[correctedSymbol appendString:@"*"];
|
||||
}
|
||||
}
|
||||
[description appendString:correctedSymbol];
|
||||
} else if (index > 0 && [_elements[index-1] isKindOfClass:[MPFunction class]]) {
|
||||
[description appendFormat:@"*%@", [element description]];
|
||||
} else {
|
||||
[description appendString:[element description]];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [_elements hash];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:_elements];
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
// TODO: Test Coding
|
||||
return [self initWithElements:[aDecoder decodeObject]];
|
||||
}
|
||||
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_elements];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPExpression (MPExpressionConvenience)
|
||||
|
||||
#pragma mark Querying Expressions
|
||||
|
||||
|
||||
- (NSUInteger)countElements
|
||||
{
|
||||
return [self countItemsInReferenceFrame:MPElementReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)countSymbols
|
||||
{
|
||||
return [self countItemsInReferenceFrame:MPSymbolReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)countTokens
|
||||
{
|
||||
return [self countItemsInReferenceFrame:MPTokenReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
|
||||
{
|
||||
return [self itemAtIndex:anIndex
|
||||
referenceFrame:MPElementReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)anIndex
|
||||
{
|
||||
return [self itemAtIndex:anIndex
|
||||
referenceFrame:MPSymbolReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (id<MPToken>)tokenAtIndex:(NSUInteger)anIndex
|
||||
{
|
||||
return [self itemAtIndex:anIndex
|
||||
referenceFrame:MPTokenReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Mutating Expressions
|
||||
|
||||
|
||||
- (void)appendElement:(id<MPExpressionElement>)anElement
|
||||
{
|
||||
[self appendElements:@[anElement]];
|
||||
}
|
||||
|
||||
|
||||
- (void)appendElements:(NSArray *)elements
|
||||
{
|
||||
[self replaceItemsInRange:NSMakeRange([self countItemsInReferenceFrame:MPSymbolReferenceFrame], 0)
|
||||
referenceFrame:MPSymbolReferenceFrame
|
||||
withElements:elements];
|
||||
}
|
||||
|
||||
|
||||
- (void)insertElement:(id<MPExpressionElement>)anElement
|
||||
atIndex:(NSUInteger)index
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
[self insertElements:@[anElement]
|
||||
atIndex:index
|
||||
referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (void)insertElements:(NSArray *)elements
|
||||
atIndex:(NSUInteger)index
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
[self replaceItemsInRange:NSMakeRange(index, 0)
|
||||
referenceFrame:referenceFrame
|
||||
withElements:elements];
|
||||
}
|
||||
|
||||
|
||||
- (void)deleteElementsInRange:(NSRange)range
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
[self replaceItemsInRange:range
|
||||
referenceFrame:referenceFrame
|
||||
withElements:@[]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,57 +0,0 @@
|
||||
//
|
||||
// MPExpressionElement.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 10.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@protocol MPExpressionElement
|
||||
@brief This protocol defines the functionality an element in an
|
||||
expression must have.
|
||||
*/
|
||||
@protocol MPExpressionElement <NSObject, NSCopying, NSCoding>
|
||||
|
||||
|
||||
/*!
|
||||
@method isString
|
||||
@brief Returns wether the receiver is a string.
|
||||
|
||||
@discussion A string is defined by being an instance of @c NSString. If this
|
||||
method returns @c YES you can be sure it is an @c NSString
|
||||
instance.
|
||||
|
||||
@return @c YES if the receiver is a string, @c NO otherwise.
|
||||
*/
|
||||
- (BOOL)isString;
|
||||
|
||||
|
||||
/*!
|
||||
@method isFunction
|
||||
@brief Returns wether the receiver is a function.
|
||||
|
||||
@discussion A function is defined by being an instance of @c MPFunction. If
|
||||
this method returns @c YES you can be sure it is a @c MPFunction
|
||||
instance.
|
||||
|
||||
@return @c YES if the receiver is a function, @c NO otherwise.
|
||||
*/
|
||||
- (BOOL)isFunction;
|
||||
|
||||
|
||||
/*!
|
||||
@method length
|
||||
@brief Calculates the length of the receiver.
|
||||
|
||||
@discussion The length of a @c MPExpressionElement is the number of symbols
|
||||
it consists of. For strings this is the number of characters in
|
||||
it. Functions have a length of @c 1.
|
||||
|
||||
@return The receiver's length.
|
||||
*/
|
||||
- (NSUInteger)length;
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPExpressionLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPLayout.h"
|
||||
|
||||
|
||||
|
||||
@class MPExpressionLayout, MPExpression, MPFunctionLayout;
|
||||
|
||||
@interface MPExpressionLayout : MPLayout
|
||||
|
||||
- (instancetype)initWithExpression:(MPExpression *)expression
|
||||
parent:(MPFunctionLayout *)parent;
|
||||
|
||||
@property (readonly, nonatomic, weak) MPExpression *expression;
|
||||
|
||||
@end
|
||||
@@ -1,297 +0,0 @@
|
||||
//
|
||||
// MPExpressionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionLayout.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionElement.h"
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
#import "MPPowerFunctionLayout.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
@interface MPExpressionLayout (MPLineGeneration)
|
||||
|
||||
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpressionLayout (MPLineGeneration)
|
||||
|
||||
- (CTLineRef)lineForElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
id element = [self.expression elementAtIndex:index];
|
||||
if (![element isString]) {
|
||||
return NULL;
|
||||
}
|
||||
NSRange tokensRange = [self.expression convertRange:NSMakeRange(index, 1)
|
||||
fromReferenceFrame:MPElementReferenceFrame
|
||||
toReferenceFrame:MPTokenReferenceFrame];
|
||||
NSArray *tokens = [self.expression itemsInRange:tokensRange
|
||||
referenceFrame:MPTokenReferenceFrame];
|
||||
id lineObject = [self cachableObjectForIndex:index generator:^id{
|
||||
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] init];
|
||||
for (id<MPToken> token in tokens) {
|
||||
NSFont *font;
|
||||
if (token.tokenType == MPElementaryFunctionToken) {
|
||||
font = [self specialFontWithSize:self.contextInferredFontSize];
|
||||
} else {
|
||||
font = [self normalFontWithSize:self.contextInferredFontSize];
|
||||
}
|
||||
NSAttributedString *tokenText = [[NSAttributedString alloc] initWithString:token.stringValue
|
||||
attributes:@{NSFontAttributeName: font}];
|
||||
[text appendAttributedString:tokenText];
|
||||
}
|
||||
CFAttributedStringRef attributedString = CFBridgingRetain(text);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
|
||||
CFRelease(attributedString);
|
||||
return CFBridgingRelease(line);
|
||||
}];
|
||||
return (__bridge CTLineRef)lineObject;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpressionLayout
|
||||
|
||||
# pragma mark Creation Methods
|
||||
- (instancetype)initWithExpression:(MPExpression *)expression
|
||||
parent:(MPFunctionLayout *)parent
|
||||
{
|
||||
self = [super initWithParent:parent];
|
||||
if (self) {
|
||||
_expression = expression;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Cache Methods
|
||||
- (NSUInteger)numberOfChildren
|
||||
{
|
||||
return self.expression.countElements;
|
||||
}
|
||||
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
id cachedObject = [self cachableObjectForIndex:index generator:^id{
|
||||
MPFunction *function = (MPFunction *)[self.expression elementAtIndex:index];
|
||||
MPFunctionLayout *layout = [MPFunctionLayout functionLayoutForFunction:function
|
||||
parent:self];
|
||||
layout.flipped = self.flipped;
|
||||
layout.usesSmallSize = self.usesSmallSize;
|
||||
return layout;
|
||||
}];
|
||||
if ([cachedObject isKindOfClass:[MPLayout class]]) {
|
||||
return cachedObject;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSRect)boundsOfElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
id symbol = [self.expression elementAtIndex:index];
|
||||
if ([symbol isString]) {
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
CGRect bounds = CTLineGetBoundsWithOptions(line, 0);
|
||||
CFRelease(line);
|
||||
return bounds;
|
||||
} else {
|
||||
return [self childLayoutAtIndex:index].bounds;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Drawing Methods
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
if (self.expression.countElements == 0) {
|
||||
return NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
|
||||
}
|
||||
CGFloat x = 0, y = 0, width = 0, height = 0;
|
||||
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
width += elementBounds.size.width;
|
||||
height = MAX(height, elementBounds.size.height);
|
||||
y = MIN(y, elementBounds.origin.y);
|
||||
}
|
||||
return NSMakeRect(x, y, width, height);
|
||||
}
|
||||
|
||||
- (NSRect)boundingRectForRange:(NSRange)range
|
||||
{
|
||||
NSUInteger startOffset;
|
||||
NSUInteger startElementIndex = [self.expression convertIndex:range.location
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
offset:&startOffset];
|
||||
// Calculate x position
|
||||
CGFloat x = 0, width = 0;
|
||||
for (NSUInteger index = 0; index < startElementIndex; index++) {
|
||||
x += [self boundsOfElementAtIndex:index].size.width;
|
||||
}
|
||||
|
||||
if (startOffset > 0) {
|
||||
CTLineRef line = [self lineForElementAtIndex:startElementIndex];
|
||||
CFRetain(line);
|
||||
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
|
||||
x += xOffset;
|
||||
width += CTLineGetBoundsWithOptions(line, 0).size.width - xOffset;
|
||||
CFRelease(line);
|
||||
} else if (startElementIndex < self.expression.countElements) { // Otherwise the selection is after the last symbol
|
||||
width += [self boundsOfElementAtIndex:startElementIndex].size.width;
|
||||
}
|
||||
|
||||
// If we search the caret position we are done
|
||||
if (range.length == 0) {
|
||||
return NSMakeRect(x, self.bounds.origin.y, 0, self.bounds.size.height);
|
||||
}
|
||||
|
||||
NSUInteger endOffset;
|
||||
NSUInteger endElementIndex = [self.expression convertIndex:NSMaxRange(range)
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
offset:&endOffset];
|
||||
|
||||
// Selection is inside of one string element
|
||||
if (startElementIndex == endElementIndex) {
|
||||
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
|
||||
CFRetain(line);
|
||||
CGFloat xStart = CTLineGetOffsetForStringIndex(line, startOffset, NULL);
|
||||
CGFloat xEnd = CTLineGetOffsetForStringIndex(line, endOffset, NULL);
|
||||
width = xEnd - xStart;
|
||||
CFRelease(line);
|
||||
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
|
||||
}
|
||||
|
||||
// Calculate width
|
||||
for (NSUInteger index = startElementIndex + 1; index < endElementIndex; index++) {
|
||||
width += [self boundsOfElementAtIndex:index].size.width;
|
||||
}
|
||||
if (endOffset > 0) {
|
||||
CTLineRef line = [self lineForElementAtIndex:endElementIndex];
|
||||
CFRetain(line);
|
||||
width += CTLineGetOffsetForStringIndex(line, endOffset, NULL);
|
||||
CFRelease(line);
|
||||
}
|
||||
return NSMakeRect(x, self.bounds.origin.y, width, self.bounds.size.height);
|
||||
}
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
CGFloat x = 0;
|
||||
for (NSUInteger i = 0; i < index; i++) {
|
||||
x += [self boundsOfElementAtIndex:i].size.width;
|
||||
}
|
||||
return NSMakePoint(x, 0);
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
|
||||
{
|
||||
NSUInteger currentPosition = 0;
|
||||
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index];
|
||||
elementBounds.origin.x += elementOffset.x;
|
||||
elementBounds.origin.y += elementOffset.y;
|
||||
|
||||
// Only the horizontal location is to consider for hit testing
|
||||
elementBounds.size.height = CGFLOAT_MAX;
|
||||
|
||||
id<MPExpressionElement> element = [self.expression elementAtIndex:index];
|
||||
if (NSMouseInRect(point, elementBounds, self.flipped)) {
|
||||
NSPoint pointInElement = NSMakePoint(point.x - elementOffset.x, point.y + elementOffset.y);
|
||||
if ([element isString]) {
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
CFIndex localIndex = CTLineGetStringIndexForPosition(line, pointInElement);
|
||||
CFRelease(line);
|
||||
return [NSIndexPath indexPathWithIndex:currentPosition+localIndex];
|
||||
} else {
|
||||
NSIndexPath *subPath = [[self childLayoutAtIndex:index] indexPathForMousePoint:pointInElement];
|
||||
if (subPath.length == 1) {
|
||||
// A single index is used to communicate back wether the
|
||||
// selection should be before or after the function.
|
||||
// A 0 means before, a 1 means after.
|
||||
return [NSIndexPath indexPathWithIndex:currentPosition + [subPath indexAtPosition:0]];
|
||||
} else {
|
||||
return [subPath indexPathByPreceedingIndex:index];
|
||||
}
|
||||
}
|
||||
}
|
||||
currentPosition += element.length;
|
||||
}
|
||||
if (point.x < self.bounds.size.width / 2) {
|
||||
return [NSIndexPath indexPathWithIndex:0];
|
||||
} else {
|
||||
return [NSIndexPath indexPathWithIndex:self.expression.countSymbols];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)drawsChildrenManually
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
// Get the current context
|
||||
CGContextRef context =
|
||||
(CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
||||
CGContextSaveGState(context);
|
||||
|
||||
[[NSColor textColor] set];
|
||||
if (self.expression.countElements == 0) {
|
||||
CGContextRestoreGState(context);
|
||||
NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, kMPEmptyBoxDrawingYOrigin, kMPEmptyBoxDrawingWidth, kMPEmptyBoxDrawingHeight)];
|
||||
path.lineWidth = 0.5;
|
||||
[path stroke];
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the text matrix
|
||||
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
|
||||
|
||||
// Track the x position and the bounds of the last element
|
||||
CGFloat x = 0;
|
||||
NSRect lastElementBounds = NSMakeRect(0, kMPEmptyBoxYOrigin, kMPEmptyBoxWidth, kMPEmptyBoxHeight);
|
||||
|
||||
for (NSUInteger index = 0; index < self.expression.countElements; index++) {
|
||||
// The current element
|
||||
id element = [self.expression elementAtIndex:index];
|
||||
NSRect elementBounds = [self boundsOfElementAtIndex:index];
|
||||
|
||||
if ([element isString]) {
|
||||
// Get the line to draw
|
||||
CTLineRef line = [self lineForElementAtIndex:index];
|
||||
CFRetain(line);
|
||||
|
||||
// Move to the appropriate position
|
||||
CGContextSetTextPosition(context, x, 0);
|
||||
|
||||
// Perform the drawing
|
||||
CTLineDraw(line, context);
|
||||
|
||||
CFRelease(line);
|
||||
} else {
|
||||
// Let the child layout draw itself
|
||||
MPLayout *layout = [self childLayoutAtIndex:index];
|
||||
if ([layout isKindOfClass:[MPPowerFunctionLayout class]]) {
|
||||
((MPPowerFunctionLayout *)layout).baseBounds = lastElementBounds;
|
||||
}
|
||||
[layout drawAtPoint:NSMakePoint(x, 0)];
|
||||
}
|
||||
x += elementBounds.size.width;
|
||||
lastElementBounds = elementBounds;
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,24 +0,0 @@
|
||||
//
|
||||
// MPExpressionParser.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 16.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@class MPExpressionParser, MPExpression, MPParsedExpression;
|
||||
|
||||
|
||||
@interface MPExpressionParser : NSObject
|
||||
|
||||
@property (readonly, nonatomic, strong) MPExpression *expression;
|
||||
|
||||
- (instancetype)initWithExpression:(MPExpression *)expression;
|
||||
|
||||
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
|
||||
- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
|
||||
errors:(NSArray *__autoreleasing *)errors;
|
||||
|
||||
@end
|
||||
@@ -1,431 +0,0 @@
|
||||
//
|
||||
// MPExpressionParser.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 16.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionParser.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPTerm.h"
|
||||
#import "MPToken.h"
|
||||
#import "MPSumTerm.h"
|
||||
#import "MPProductTerm.h"
|
||||
#import "MPFactorialTerm.h"
|
||||
#import "MPElementaryFunctionTerm.h"
|
||||
#import "MPFunctionTerm.h"
|
||||
#import "MPPowerTerm.h"
|
||||
#import "MPParsedExpression.h"
|
||||
#import "MPNegatedTerm.h"
|
||||
|
||||
#import "MPNumber.h"
|
||||
#import "MPVariable.h"
|
||||
#import "MPFactorialTerm.h"
|
||||
|
||||
#define success() state = 0
|
||||
#define fail() self.errorTokenIndex = self.currentTokenIndex; state = -1
|
||||
|
||||
@interface MPExpressionParser ()
|
||||
|
||||
@property (nonatomic, strong) NSArray *tokens;
|
||||
@property (nonatomic) NSUInteger currentTokenIndex;
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *errors;
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *summands;
|
||||
@property (nonatomic) BOOL summandNegative;
|
||||
@property (nonatomic, strong) NSMutableArray *factors;
|
||||
@property (nonatomic) BOOL factorNegative;
|
||||
@property (nonatomic, strong) NSMutableArray *functionStack;
|
||||
@property (nonatomic, strong) NSMutableArray *valueGroup;
|
||||
@property (nonatomic, strong) MPTerm *value;
|
||||
|
||||
@property (nonatomic) NSUInteger errorTokenIndex;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpressionParser
|
||||
|
||||
- (instancetype)initWithExpression:(MPExpression *)expression
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_tokens = [expression allItemsInReferenceFrame:MPTokenReferenceFrame];
|
||||
_expression = expression;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
return [self parseExpectingVariableDefinition:NO
|
||||
errors:errors];
|
||||
}
|
||||
|
||||
- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag
|
||||
errors:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
MPParsedExpression *result = [[MPParsedExpression alloc] init];
|
||||
self.currentTokenIndex = 0;
|
||||
|
||||
BOOL hasVariableDefinition = NO;
|
||||
NSString *variableName = nil;
|
||||
[self skipWhitespaces];
|
||||
if (self.currentToken.tokenType == MPVariableToken) {
|
||||
variableName = self.currentToken.stringValue;
|
||||
[self nextToken];
|
||||
[self skipWhitespaces];
|
||||
if (self.currentToken.tokenType == MPEqualsToken) {
|
||||
[self nextToken];
|
||||
hasVariableDefinition = YES;
|
||||
}
|
||||
}
|
||||
if (flag && hasVariableDefinition) {
|
||||
result.definedVariable = variableName;
|
||||
} else if (flag) {
|
||||
[self addErrorWithCode:3 localizedDescription:NSLocalizedString(@"Expected Variable Definition.", nil) useRange:NO];
|
||||
if (errors) {
|
||||
*errors = self.errors;
|
||||
}
|
||||
return nil;
|
||||
} else if (hasVariableDefinition) {
|
||||
[self addErrorWithCode:4 localizedDescription:NSLocalizedString(@"Unexpected Variable Definition.", nil) useRange:NO];
|
||||
if (errors) {
|
||||
*errors = self.errors;
|
||||
}
|
||||
return nil;
|
||||
} else {
|
||||
self.currentTokenIndex = 0;
|
||||
}
|
||||
|
||||
[self skipWhitespaces];
|
||||
if (self.currentTokenIndex >= self.tokens.count) {
|
||||
[self addErrorWithCode:5 localizedDescription:NSLocalizedString(@"Empty Expression.", nil) useRange:NO];
|
||||
if (errors) {
|
||||
*errors = self.errors;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
[self runParserMachine];
|
||||
if ([self errorOccured]) {
|
||||
if (errors) {
|
||||
*errors = self.errors;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
result.term = [[MPSumTerm alloc] initWithSummands:self.summands];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)runParserMachine
|
||||
{
|
||||
NSInteger state = 1;
|
||||
NSInteger backtraceState = 0;
|
||||
NSUInteger backtraceTokenIndex = self.currentTokenIndex;
|
||||
BOOL alternateRoute = NO;
|
||||
while (state != 0) {
|
||||
switch (state) {
|
||||
case -1:
|
||||
state = backtraceState;
|
||||
self.currentTokenIndex = backtraceTokenIndex;
|
||||
alternateRoute = YES;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
[self skipWhitespaces];
|
||||
if (self.currentToken.tokenType == MPOperatorListToken) {
|
||||
self.factorNegative = [self parseOperatorList:self.currentToken];
|
||||
[self nextToken];
|
||||
state = 2;
|
||||
} else {
|
||||
self.factorNegative = NO;
|
||||
state = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
[self skipWhitespaces];
|
||||
if (self.currentToken.tokenType == MPElementaryFunctionToken) {
|
||||
[self.functionStack addObject:self.currentToken.stringValue];
|
||||
[self nextToken];
|
||||
state = 2;
|
||||
} else {
|
||||
state = 3;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
[self skipWhitespaces];
|
||||
if (self.currentToken.tokenType == MPGenericFunctionToken) {
|
||||
NSArray *errors;
|
||||
self.value = [[MPFunctionTerm alloc] initWithFunction:(MPFunction *)self.currentToken
|
||||
errors:&errors];
|
||||
if (!self.value) {
|
||||
[self.errors addObjectsFromArray:errors];
|
||||
}
|
||||
[self nextToken];
|
||||
[self runSuffixMachine];
|
||||
if (self.value) {
|
||||
[self.valueGroup addObject:self.value];
|
||||
}
|
||||
state = 6;
|
||||
} else {
|
||||
self.value = nil;
|
||||
state = 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (self.currentToken.tokenType == MPNumberToken) {
|
||||
self.value = [[MPNumber alloc] initWithNumber:[self parseNumber:self.currentToken]];
|
||||
[self nextToken];
|
||||
[self runSuffixMachine];
|
||||
[self.valueGroup addObject:self.value];
|
||||
state = 5;
|
||||
} else if (self.currentToken.tokenType == MPVariableToken) {
|
||||
self.value = [[MPVariable alloc] initWithVariableName:[self parseVariable:self.currentToken]];
|
||||
[self nextToken];
|
||||
[self runSuffixMachine];
|
||||
[self.valueGroup addObject:self.value];
|
||||
state = 5;
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if (alternateRoute) {
|
||||
alternateRoute = NO;
|
||||
state = 6;
|
||||
} else {
|
||||
backtraceState = 5;
|
||||
backtraceTokenIndex = self.currentTokenIndex;
|
||||
state = 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
[self collapseFactor];
|
||||
[self skipWhitespaces];
|
||||
backtraceState = 6;
|
||||
backtraceTokenIndex = self.currentTokenIndex;
|
||||
if (!alternateRoute && self.currentToken.tokenType == MPOperatorListToken) {
|
||||
[self collapseSummand];
|
||||
self.summandNegative = [self parseOperatorList:self.currentToken];
|
||||
[self nextToken];
|
||||
state = 1;
|
||||
} else {
|
||||
state = 7;
|
||||
}
|
||||
break;
|
||||
|
||||
case 7:
|
||||
if (alternateRoute) {
|
||||
[self collapseSummand];
|
||||
alternateRoute = NO;
|
||||
success();
|
||||
} else {
|
||||
backtraceState = 7;
|
||||
backtraceTokenIndex = self.currentTokenIndex;
|
||||
state = 8;
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
[self skipWhitespaces];
|
||||
if (self.currentToken.tokenType == MPMultiplicationSymbolToken) {
|
||||
[self nextToken];
|
||||
state = 1;
|
||||
} else {
|
||||
state = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Should never get here
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)skipWhitespaces
|
||||
{
|
||||
while ([self currentToken] != nil && [self currentToken].tokenType == MPWhitespaceToken) {
|
||||
[self nextToken];
|
||||
}
|
||||
}
|
||||
|
||||
- (id<MPToken>)currentToken
|
||||
{
|
||||
if (self.currentTokenIndex >= self.tokens.count) {
|
||||
return nil;
|
||||
}
|
||||
return self.tokens[self.currentTokenIndex];
|
||||
}
|
||||
|
||||
- (void)nextToken
|
||||
{
|
||||
self.currentTokenIndex++;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)errors
|
||||
{
|
||||
if (!_errors) {
|
||||
_errors = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _errors;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)summands
|
||||
{
|
||||
if (!_summands) {
|
||||
_summands = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _summands;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)factors
|
||||
{
|
||||
if (!_factors) {
|
||||
_factors = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _factors;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)functionStack
|
||||
{
|
||||
if (!_functionStack) {
|
||||
_functionStack = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _functionStack;
|
||||
}
|
||||
|
||||
- (NSMutableArray *)valueGroup
|
||||
{
|
||||
if (!_valueGroup) {
|
||||
_valueGroup = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return _valueGroup;
|
||||
}
|
||||
|
||||
- (BOOL)parseOperatorList:(id<MPToken>)token // Returns YES if list is overall negative
|
||||
{
|
||||
NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""]
|
||||
stringByReplacingOccurrencesOfString:@"+" withString:@""];
|
||||
return (operatorString.length & 1) == 1;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)parseNumber:(id<MPToken>)token
|
||||
{
|
||||
return [NSDecimalNumber decimalNumberWithString:token.stringValue
|
||||
locale:[NSLocale currentLocale]];
|
||||
}
|
||||
|
||||
- (NSString *)parseVariable:(id<MPToken>)token
|
||||
{
|
||||
return token.stringValue;
|
||||
}
|
||||
|
||||
- (void)collapseSummand
|
||||
{
|
||||
if (self.factors.count > 0) {
|
||||
MPTerm *summand = [[MPProductTerm alloc] initWithFactors:self.factors];
|
||||
if (self.summandNegative) {
|
||||
summand = [[MPNegatedTerm alloc] initWithTerm:summand];
|
||||
}
|
||||
[self.summands addObject:summand];
|
||||
}
|
||||
self.factors = nil;
|
||||
self.summandNegative = NO;
|
||||
}
|
||||
|
||||
- (void)collapseFactor
|
||||
{
|
||||
if (self.valueGroup.count > 0) {
|
||||
MPTerm *factor = [[MPProductTerm alloc] initWithFactors:self.valueGroup];
|
||||
for (NSInteger index = self.functionStack.count - 1; index >= 0; index--) {
|
||||
factor = [[MPElementaryFunctionTerm alloc] initWithFunctionIdentifier:self.functionStack[index]
|
||||
term:factor];
|
||||
}
|
||||
if (self.factorNegative) {
|
||||
factor = [[MPNegatedTerm alloc] initWithTerm:factor];
|
||||
}
|
||||
[self.factors addObject:factor];
|
||||
}
|
||||
self.valueGroup = nil;
|
||||
self.functionStack = nil;
|
||||
self.factorNegative = NO;
|
||||
}
|
||||
|
||||
- (void)runSuffixMachine
|
||||
{
|
||||
BOOL checkMore = YES;
|
||||
while (checkMore) {
|
||||
if (self.currentToken.tokenType == MPFactorialToken) {
|
||||
[self nextToken];
|
||||
if (self.value) {
|
||||
MPFactorialTerm *term = [[MPFactorialTerm alloc] initWithTerm:self.value];
|
||||
self.value = term;
|
||||
}
|
||||
} else if (self.currentToken.tokenType == MPPowerToken) {
|
||||
if (self.value) {
|
||||
NSArray *errors;
|
||||
MPPowerTerm *term = [[MPPowerTerm alloc] initWithFunction:(MPFunction *)self.currentToken errors:&errors];
|
||||
if (!term) {
|
||||
[self.errors addObjectsFromArray:errors];
|
||||
} else {
|
||||
term.baseTerm = self.value;
|
||||
self.value = term;
|
||||
}
|
||||
}
|
||||
[self nextToken];
|
||||
} else {
|
||||
checkMore = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addErrorWithCode:(NSInteger)code
|
||||
localizedDescription:(NSString *)description
|
||||
useRange:(BOOL)flag
|
||||
{
|
||||
NSRange errorRange;
|
||||
if (flag) {
|
||||
errorRange = [self.tokens[self.errorTokenIndex] range];
|
||||
} else {
|
||||
NSInteger errorIndex = [self.expression convertIndex:self.errorTokenIndex
|
||||
fromReferenceFrame:MPTokenReferenceFrame
|
||||
toReferenceFrame:MPSymbolReferenceFrame];
|
||||
errorRange = NSMakeRange(errorIndex, 0);
|
||||
}
|
||||
[self.errors addObject:[NSError errorWithDomain:MPMathKitErrorDomain
|
||||
code:code
|
||||
userInfo:@{NSLocalizedDescriptionKey: description,
|
||||
MPPathToExpressionKey: self.expression.indexPath,
|
||||
MPErrorRangeKey: [NSValue valueWithRange:errorRange]}]];
|
||||
}
|
||||
|
||||
- (BOOL)errorOccured
|
||||
{
|
||||
[self skipWhitespaces];
|
||||
if (self.currentTokenIndex < self.tokens.count) {
|
||||
if (self.errorTokenIndex >= self.tokens.count) {
|
||||
[self addErrorWithCode:0 localizedDescription:NSLocalizedString(@"Unexpected end. Expected Value", nil) useRange:NO];
|
||||
} else {
|
||||
id<MPToken> unexpectedToken = self.tokens[self.errorTokenIndex];
|
||||
if (unexpectedToken.tokenType == MPPowerToken) {
|
||||
[self addErrorWithCode:34 localizedDescription:NSLocalizedString(@"No base for Power", nil) useRange:NO];
|
||||
} else if (unexpectedToken.tokenType == MPDeformedNumberToken) {
|
||||
[self addErrorWithCode:42 localizedDescription:NSLocalizedString(@"Not a Number", nil) useRange:YES];
|
||||
} else {
|
||||
[self addErrorWithCode:1 localizedDescription:NSLocalizedString(@"Unexpected Symbol. Expected Value", nil) useRange:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.errors.count > 0;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// MPExpressionStorage.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 22.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpression.h"
|
||||
|
||||
|
||||
|
||||
@class MPExpressionStorage, MPExpressionView, MPExpressionLayout;
|
||||
|
||||
|
||||
@interface MPExpressionStorage : MPExpression
|
||||
|
||||
@property (nonatomic, weak) MPExpressionView *expressionView; // Do not set
|
||||
@property (nonatomic, strong) MPExpressionLayout *rootLayout;
|
||||
|
||||
@end
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// MPExpressionStorage.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 22.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionStorage.h"
|
||||
|
||||
#import "MPExpressionView.h"
|
||||
#import "MPLayout.h"
|
||||
#import "MPExpressionLayout.h"
|
||||
|
||||
#import "MPRangePath.h"
|
||||
|
||||
@interface MPExpressionStorage ()
|
||||
@property (nonatomic, strong) NSLayoutManager *layoutManager;
|
||||
@property (nonatomic, strong) NSTextContainer *textContainer;
|
||||
@property (nonatomic, strong) NSTextStorage *textStorage;
|
||||
@end
|
||||
|
||||
@implementation MPExpressionStorage
|
||||
|
||||
- (instancetype)initWithElements:(NSArray *)elements
|
||||
{
|
||||
self = [super initWithElements:elements];
|
||||
if (self) {
|
||||
_rootLayout = [[MPExpressionLayout alloc] initWithExpression:self parent:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setExpressionView:(MPExpressionView *)expressionView
|
||||
{
|
||||
_expressionView = expressionView;
|
||||
self.rootLayout = [[MPExpressionLayout alloc] initWithExpression:self parent:nil];
|
||||
self.rootLayout.flipped = expressionView.flipped;
|
||||
}
|
||||
|
||||
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
|
||||
replacementLength:(NSUInteger)replacementLength
|
||||
{
|
||||
if (rangePath.location.length == 0) {
|
||||
return;
|
||||
}
|
||||
MPLayout *current = self.rootLayout;
|
||||
for (NSUInteger position = 0; position < rangePath.location.length-1; position++) {
|
||||
NSUInteger index = [rangePath.location indexAtPosition:position];
|
||||
current = [current childLayoutAtIndex:index];
|
||||
}
|
||||
[current clearCacheInRange:rangePath.rangeAtLastIndex
|
||||
replacementLength:replacementLength];
|
||||
[self.expressionView invalidateIntrinsicContentSize];
|
||||
self.expressionView.needsDisplay = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,37 +0,0 @@
|
||||
//
|
||||
// MPExpressionTokenizer.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 19.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@class MPExpressionTokenizer, MPExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPExpressionTokenizer
|
||||
@brief The expression tokenizer class convers an @c MPExpression
|
||||
instance into an array of tokens.
|
||||
*/
|
||||
@interface MPExpressionTokenizer : NSObject
|
||||
|
||||
|
||||
/*!
|
||||
@method tokenizeExpression:
|
||||
@brief Converts an @c MPExpression instance into an array of tokens.
|
||||
|
||||
@discussion The objects in the returned array all conform to the @c MPToken
|
||||
protocol. Function tokens are not copied from the @c expression
|
||||
so they can still be mutated.
|
||||
|
||||
@param expression
|
||||
The expression to be tokenized.
|
||||
|
||||
@return An array of objects that conform to the @c MPToken protocol.
|
||||
*/
|
||||
+ (NSArray *)tokenizeExpression:(MPExpression *)expression;
|
||||
|
||||
@end
|
||||
@@ -1,126 +0,0 @@
|
||||
//
|
||||
// MPExpressionTokenizer.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 19.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionTokenizer.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPExpressionElement.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
#import "NSRegularExpression+MPParsingAdditions.h"
|
||||
|
||||
|
||||
#define MPRangeExists(range) (range.location != NSNotFound)
|
||||
|
||||
|
||||
|
||||
@implementation MPExpressionTokenizer
|
||||
|
||||
+ (NSArray *)tokenizeExpression:(MPExpression *)expression
|
||||
{
|
||||
NSMutableArray *tokens = [[NSMutableArray alloc] init];
|
||||
NSUInteger symbolIndex = 0;
|
||||
for (NSUInteger index = 0; index < [expression countItemsInReferenceFrame:MPElementReferenceFrame]; index++) {
|
||||
id <MPExpressionElement> element = [expression itemAtIndex:index referenceFrame:MPElementReferenceFrame];
|
||||
if ([element isFunction]) {
|
||||
[tokens addObject:element];
|
||||
} else {
|
||||
[tokens addObjectsFromArray:[self tokenizeElement:(NSString *)element
|
||||
elementSymbolIndex:symbolIndex]];
|
||||
}
|
||||
symbolIndex += element.length;
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
|
||||
+ (NSArray *)tokenizeElement:(NSString *)element
|
||||
elementSymbolIndex:(NSUInteger)symbolIndex
|
||||
{
|
||||
NSUInteger lexLocation = 0;
|
||||
|
||||
NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
|
||||
NSString *regexStringFormat = @"\\A(?:"
|
||||
@"([\\*∙⋅])|"
|
||||
@"([+-](?:\\s*[+-])*)|"
|
||||
@"((?:\\d+%@(?!\\d+))|(?:(?:\\d*%@){2,}\\d*)|%@(?!\\d+))|" // Substitute with decimal separator 3 times
|
||||
@"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|" // Substitute with decimal separator 2 times
|
||||
@"(sin|cos|tan|asin|arcsin|acos|arccos|atan|arctan|lg|log|ln)|"
|
||||
@"([A-Za-z])|"
|
||||
@"(!)|"
|
||||
@"(=)|"
|
||||
@"(\\s+)"
|
||||
@")";
|
||||
NSString *regexString = [NSString stringWithFormat:regexStringFormat, decimalSeparator, decimalSeparator, decimalSeparator, decimalSeparator, decimalSeparator];
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString
|
||||
options:0
|
||||
error:NULL];
|
||||
NSMutableArray *tokens = [[NSMutableArray alloc] init];
|
||||
while (lexLocation < element.length) {
|
||||
NSTextCheckingResult *match = [regex firstMatchInString:element fromIndex:lexLocation];
|
||||
|
||||
NSRange range = NSMakeRange(lexLocation, 1);
|
||||
MPTokenType tokenType = MPUnidentifiedToken;
|
||||
if (match) {
|
||||
NSRange multiplicationSymbolRange = [match rangeAtIndex:1];
|
||||
NSRange operatorRange = [match rangeAtIndex:2];
|
||||
NSRange deformedNumberRange = [match rangeAtIndex:3];
|
||||
NSRange numberRange = [match rangeAtIndex:4];
|
||||
NSRange elementaryFunctionRange = [match rangeAtIndex:5];
|
||||
NSRange variableRange = [match rangeAtIndex:6];
|
||||
NSRange factorialRange = [match rangeAtIndex:7];
|
||||
NSRange equalsRange = [match rangeAtIndex:8];
|
||||
NSRange whitespaceRange = [match rangeAtIndex:9];
|
||||
|
||||
if (MPRangeExists(multiplicationSymbolRange)) {
|
||||
range = multiplicationSymbolRange;
|
||||
tokenType = MPMultiplicationSymbolToken;
|
||||
} else if (MPRangeExists(operatorRange)) {
|
||||
range = operatorRange;
|
||||
tokenType = MPOperatorListToken;
|
||||
} else if (MPRangeExists(deformedNumberRange)) {
|
||||
range = deformedNumberRange;
|
||||
tokenType = MPDeformedNumberToken;
|
||||
} else if (MPRangeExists(numberRange)) {
|
||||
range = numberRange;
|
||||
tokenType = MPNumberToken;
|
||||
} else if (MPRangeExists(elementaryFunctionRange)) {
|
||||
range = elementaryFunctionRange;
|
||||
tokenType = MPElementaryFunctionToken;
|
||||
} else if (MPRangeExists(variableRange)) {
|
||||
range = variableRange;
|
||||
tokenType = MPVariableToken;
|
||||
} else if (MPRangeExists(factorialRange)) {
|
||||
range = factorialRange;
|
||||
tokenType = MPFactorialToken;
|
||||
} else if (MPRangeExists(equalsRange)) {
|
||||
range = equalsRange;
|
||||
tokenType = MPEqualsToken;
|
||||
} else if (MPRangeExists(whitespaceRange)) {
|
||||
range = whitespaceRange;
|
||||
tokenType = MPWhitespaceToken;
|
||||
} else {
|
||||
// Should not get here
|
||||
range = NSMakeRange(lexLocation, 1);
|
||||
tokenType = MPUnidentifiedToken;
|
||||
}
|
||||
}
|
||||
|
||||
lexLocation = NSMaxRange(range);
|
||||
NSString *tokenStringValue = [element substringWithRange:range];
|
||||
range.location += symbolIndex;
|
||||
[tokens addObject:[[MPToken alloc] initWithTokenType:tokenType
|
||||
range:range
|
||||
stringValue:tokenStringValue]];
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// MPExpressionView.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 17.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
// TODO: Undo/Redo + Delegate (evaluateExpressionView:evaluate...)
|
||||
|
||||
|
||||
|
||||
@class MPExpressionView, MPExpressionStorage, MPRangePath;
|
||||
|
||||
|
||||
@interface MPExpressionView : NSView
|
||||
|
||||
#pragma mark Creation Methods
|
||||
|
||||
- (id)initWithFrame:(NSRect)frameRect;
|
||||
|
||||
#pragma mark Properties
|
||||
|
||||
@property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage;
|
||||
|
||||
@property (nonatomic, strong) MPRangePath *selection;
|
||||
|
||||
@property (nonatomic, strong) NSError *mathError;
|
||||
@property (nonatomic, strong) NSArray *syntaxErrors;
|
||||
|
||||
@property (nonatomic, weak) id target;
|
||||
@property (nonatomic) SEL action;
|
||||
|
||||
#pragma mark Actions
|
||||
- (IBAction)switchRadiansDegrees:(id)sender;
|
||||
- (IBAction)showFunctions:(id)sender;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPFactorialTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPFactorialTerm;
|
||||
|
||||
|
||||
@interface MPFactorialTerm : MPTerm
|
||||
|
||||
- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) MPTerm *term;
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// MPFactorialTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFactorialTerm.h"
|
||||
|
||||
@implementation MPFactorialTerm
|
||||
|
||||
- (instancetype)initWithTerm:(MPTerm *)term
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(term != nil, @"term must not be nil.");
|
||||
_term = term;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDecimalNumber *value = [self.term evaluate:error];
|
||||
if (!value) {
|
||||
return nil;
|
||||
}
|
||||
return [[NSDecimalNumber alloc] initWithDouble:tgamma(value.doubleValue + 1)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// MPFractionFunction.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunction.h"
|
||||
|
||||
|
||||
|
||||
@class MPFractionFunction, MPExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPFractionFunction
|
||||
@brief This class represents a fraction.
|
||||
|
||||
@discussion When a fraction is evaluated the nominator is divided by the
|
||||
denominator.
|
||||
*/
|
||||
@interface MPFractionFunction : MPFunction
|
||||
|
||||
/*!
|
||||
@property nominatorExpression
|
||||
@brief The receiver's nominator.
|
||||
|
||||
@discussion The nominator must not define a variable.
|
||||
*/
|
||||
@property (nonatomic, strong) MPExpression *nominatorExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@property denominatorExpression
|
||||
@brief The receiver's denominator.
|
||||
|
||||
@discussion The denominator must not define a variable.
|
||||
*/
|
||||
@property (nonatomic, strong) MPExpression *denominatorExpression;
|
||||
|
||||
@end
|
||||
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// MPFractionFunction.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
#import "MPFractionTerm.h"
|
||||
#import "MPExpression.h"
|
||||
|
||||
@implementation MPFractionFunction
|
||||
|
||||
MPFunctionAccessorImplementation(NominatorExpression, _nominatorExpression)
|
||||
MPFunctionAccessorImplementation(DenominatorExpression, _denominatorExpression)
|
||||
|
||||
- (NSArray *)childrenAccessors
|
||||
{
|
||||
return @[@"nominatorExpression", @"denominatorExpression"];
|
||||
}
|
||||
|
||||
- (Class)functionTermClass
|
||||
{
|
||||
return [MPFractionTerm class];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ / %@", self.nominatorExpression, self.denominatorExpression];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// MPFractionFunctionLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
|
||||
|
||||
@class MPFractionFunctionLayout, MPFractionFunction;
|
||||
|
||||
|
||||
@interface MPFractionFunctionLayout : MPFunctionLayout
|
||||
|
||||
- (MPFractionFunction *)fractionFunction;
|
||||
|
||||
@end
|
||||
@@ -1,89 +0,0 @@
|
||||
//
|
||||
// MPFractionFunctionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFractionFunctionLayout.h"
|
||||
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
#define kFractionFunctionLineWidth 1.0
|
||||
#define kFractionFunctionHorizontalInset 2.0
|
||||
#define kFractionFunctionNominatorOffset 0
|
||||
#define kFractionFunctionDenominatorOffset 0
|
||||
|
||||
#define MPFractionMiddle (CTFontGetCapHeight((CTFontRef)self.font) / 2)
|
||||
|
||||
@implementation MPFractionFunctionLayout
|
||||
|
||||
- (MPFractionFunction *)fractionFunction
|
||||
{
|
||||
return (MPFractionFunction *)self.function;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)indexesOfRemainingChildren
|
||||
{
|
||||
return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
|
||||
}
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
NSRect bounds = self.bounds;
|
||||
if (index == 0) {
|
||||
NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds;
|
||||
CGFloat y = MPFractionMiddle + kFractionFunctionLineWidth / 2 - nominatorBounds.origin.y;
|
||||
return NSMakePoint((bounds.size.width - nominatorBounds.size.width) / 2, y);
|
||||
} else {
|
||||
NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds;
|
||||
CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2;
|
||||
y -= denominatorBounds.size.height + denominatorBounds.origin.y;
|
||||
return NSMakePoint((bounds.size.width - denominatorBounds.size.width) / 2, y);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
|
||||
{
|
||||
if (point.x < self.bounds.size.width / 2) {
|
||||
return [NSIndexPath indexPathWithIndex:0];
|
||||
} else {
|
||||
return [NSIndexPath indexPathWithIndex:1];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
NSRect nominatorBounds = [self childLayoutAtIndex:0].bounds;
|
||||
NSRect denominatorBounds = [self childLayoutAtIndex:1].bounds;
|
||||
NSRect bounds;
|
||||
bounds.origin.x = 0;
|
||||
bounds.origin.y = MPFractionMiddle - kFractionFunctionLineWidth / 2 - denominatorBounds.size.height;
|
||||
bounds.size.width = MAX(nominatorBounds.size.width, denominatorBounds.size.width) + 2 * kFractionFunctionHorizontalInset;
|
||||
bounds.size.height = nominatorBounds.size.height + denominatorBounds.size.height + kFractionFunctionLineWidth;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
CGFloat y = MPFractionMiddle - kFractionFunctionLineWidth / 2;
|
||||
NSRectFill(NSMakeRect(0, y, self.bounds.size.width, kFractionFunctionLineWidth));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// MPFractionTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPFractionTerm;
|
||||
|
||||
|
||||
@interface MPFractionTerm : MPFunctionTerm
|
||||
|
||||
@end
|
||||
@@ -1,31 +0,0 @@
|
||||
//
|
||||
// MPFractionTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFractionTerm.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
@implementation MPFractionTerm
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
MPEvaluateExpression(nominator, 0);
|
||||
MPEvaluateExpression(denominator, 1);
|
||||
if ([denominator isEqualToNumber:@0]) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||
code:100
|
||||
userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Division by zero.", nil)}];
|
||||
}
|
||||
return nil;
|
||||
} else {
|
||||
return [nominator decimalNumberByDividingBy:denominator];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// MPFunction+MPToken.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 19.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunction.h"
|
||||
#import "MPToken.h"
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@category MPFunction (MPToken)
|
||||
@brief This category adds @c MPToken protocol conformance to the @c
|
||||
MPFunction class.
|
||||
*/
|
||||
@interface MPFunction (MPToken) <MPToken>
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// MPFunction+MPToken.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 19.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunction+MPToken.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
|
||||
|
||||
@implementation MPFunction (MPToken)
|
||||
|
||||
- (MPTokenType)tokenType
|
||||
{
|
||||
return [self isMemberOfClass:[MPPowerFunction class]] ? MPPowerToken : MPGenericFunctionToken;
|
||||
}
|
||||
|
||||
|
||||
- (NSRange)range
|
||||
{
|
||||
return NSMakeRange([self.parent convertIndex:[self.parent indexOfElement:self]
|
||||
fromReferenceFrame:MPElementReferenceFrame
|
||||
toReferenceFrame:MPSymbolReferenceFrame],
|
||||
1);
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)stringValue
|
||||
{
|
||||
return [self description];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,330 +0,0 @@
|
||||
//
|
||||
// MPFunction.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 18.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionElement.h"
|
||||
|
||||
|
||||
|
||||
/* Use this macro to implement a custom setter for children of custom MPFunction
|
||||
* subclasses. This is necessary to maintain the consistency of the expression
|
||||
* tree. Not implementing a custom setter using this macro or a similar custom
|
||||
* method will lead to unexpected behaviour.
|
||||
*/
|
||||
#define MPFunctionAccessorImplementation(Accessor, variableName) \
|
||||
- (void)set##Accessor:(MPExpression *)value \
|
||||
{ \
|
||||
variableName.parent = nil; \
|
||||
variableName = value; \
|
||||
variableName.parent = self; \
|
||||
[self didChangeChildAtIndex:[self indexOfChild:variableName]]; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
@class MPFunction, MPExpression, MPRangePath;
|
||||
@protocol MPExpressionElement;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPFunction
|
||||
@brief The @c MPFunction class represents a mathematical function.
|
||||
|
||||
@discussion A mathematical function can be anything that exceeds the
|
||||
graphical capability of text.
|
||||
*/
|
||||
@interface MPFunction : NSObject <NSCoding, NSCopying, MPExpressionElement>
|
||||
|
||||
#pragma mark Properties
|
||||
/* Subclasses should define readwrite properties for all child expressions they
|
||||
* can have. In the implementation file the property setter should be overridden
|
||||
* using the @c MPFunctionAccessorImplementation macro. The macro takes two
|
||||
* parameters: The capitalized name of the property and the name of the
|
||||
* property's backing variable (usually the property's name prefixed with an
|
||||
* underscore.
|
||||
*
|
||||
* Note that none of the children may be nil at any time.
|
||||
*/
|
||||
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
/*!
|
||||
@methodgroup Working With the Expression Tree
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@property parent
|
||||
@brief The receiver's parent.
|
||||
|
||||
@discussion Expressions and functions are organized in a tree-like structure.
|
||||
Through this property a function's containing expression can be
|
||||
accessed.
|
||||
@warning Do not set this property manually. It will automatically be set
|
||||
when the function is added to an expression.
|
||||
@note If you need to know when a function is added to or removed from
|
||||
an expression you can observe this property.
|
||||
|
||||
@return The parent of the receiver or @c nil if the receiver does not
|
||||
have a parent.
|
||||
*/
|
||||
@property (nonatomic, weak) MPExpression *parent;
|
||||
|
||||
|
||||
/*!
|
||||
@method rootExpression
|
||||
@brief Returns the root expression from the receiver's expression tree.
|
||||
|
||||
@discussion The root expression is the ultimate parent of all expressions and
|
||||
functions in the expression tree. A root expression does not have
|
||||
a parent.
|
||||
|
||||
@return The root expression from the receiver's expression tree or @c nil
|
||||
if this function does not have a parent.
|
||||
*/
|
||||
- (MPExpression *)rootExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPath
|
||||
@brief Returns the index path of the receiver in the expression tree.
|
||||
|
||||
@discussion The index path is calculated by going up the expression tree
|
||||
collecting the respective index of the receiver. The indexes are
|
||||
expressed in the element reference frame. If any of the indexes
|
||||
exceed the respective receiver's bounds a @c NSRangeException is
|
||||
raised.
|
||||
|
||||
@return The index path of the receiver in the expression tree or @c nil
|
||||
if this function does not have a parent.
|
||||
*/
|
||||
- (NSIndexPath *)indexPath;
|
||||
|
||||
|
||||
/*!
|
||||
@method numberOfChildren
|
||||
@brief Returns the number of children the receiver has.
|
||||
|
||||
@discussion The number of children must be equal to the number of child
|
||||
accessors as determined by the @c -childrenAccessors method.
|
||||
|
||||
@return The number of children of the receiving function.
|
||||
*/
|
||||
- (NSUInteger)numberOfChildren;
|
||||
|
||||
|
||||
/*!
|
||||
@method childAtIndex:
|
||||
@brief Returns the child at the specified index.
|
||||
|
||||
@discussion The ordering of children is determined by the order of the
|
||||
children returned from the @c -childrenAccessors method.
|
||||
|
||||
@param index
|
||||
The index of the requested child.
|
||||
|
||||
@return The expression that corresponds to the child at @c index.
|
||||
*/
|
||||
- (MPExpression *)childAtIndex:(NSUInteger)index;
|
||||
|
||||
|
||||
/*!
|
||||
@method setChild:atIndex:
|
||||
@brief Sets the child at the specified index.
|
||||
|
||||
@discussion Although this method does @b not call the respective accessor
|
||||
methods it behaves exactly as the setter method implemented using
|
||||
the @c MPFunctionAccessorImplementation macro.
|
||||
|
||||
@param child
|
||||
The child that should replace the previous one.
|
||||
|
||||
@param index
|
||||
The index of the child to be replaced.
|
||||
*/
|
||||
- (void)setChild:(MPExpression *)child
|
||||
atIndex:(NSUInteger)index;
|
||||
|
||||
|
||||
/*!
|
||||
@method children
|
||||
@brief Returns the receiver's children.
|
||||
|
||||
@discussion The ordering of the children is the same as in the array returned
|
||||
from @c -childrenAccessors.
|
||||
|
||||
@return An array containing all the function's children.
|
||||
*/
|
||||
- (NSArray *)children;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexOfChild:
|
||||
@brief Returns the index of @c child in the receiver.
|
||||
|
||||
@param child
|
||||
The child to be searched.
|
||||
|
||||
@return The index of @c child or @c NSNotFound if none of the receiver's
|
||||
children is equal to @c child.
|
||||
*/
|
||||
- (NSUInteger)indexOfChild:(MPExpression *)child;
|
||||
|
||||
|
||||
/*!
|
||||
@method elementAtIndexPath:
|
||||
@brief Returns the element at the specified index path.
|
||||
|
||||
@discussion This method @em walks down the expression tree (including other
|
||||
functions) using the specified index path and finds the
|
||||
corresponding element. The returned object can be an @c NSString,
|
||||
a @c MPFunction or an @c MPExpression depending on the element @c
|
||||
indexPath points to. If any of the indexes exceed the bounds of
|
||||
the respective receiver an @c NSRangeException is raised.
|
||||
|
||||
If the index path does not contain any indexes the receiver
|
||||
itself is returned.
|
||||
|
||||
@param indexPath
|
||||
The index path the required object is located at. The indexes are
|
||||
expressed in the element reference frame.
|
||||
|
||||
@return The element located at @c indexPath. The element is not copied
|
||||
before it is returned. Be aware of the fact that any mutations
|
||||
made to the returned object are reflected in the receiver.
|
||||
*/
|
||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
|
||||
#pragma mark Notifications
|
||||
/*!
|
||||
@methodgroup Notifications
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method didChangeElementsInRangePath:replacementLength:
|
||||
@brief Notification method that is called if one of the children was
|
||||
mutated.
|
||||
|
||||
@discussion This method is also called when one of the children is changed
|
||||
via one of the accessor methods.
|
||||
|
||||
@param rangePath
|
||||
The location of the change that happened. Because it may have
|
||||
happened somewhere deep down in the expression tree a range path
|
||||
is used.
|
||||
|
||||
@param replacementLength
|
||||
The number of elements that replaced the elements addressed by
|
||||
the range path.
|
||||
*/
|
||||
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
||||
replacementLength:(NSUInteger)replacementLength;
|
||||
|
||||
|
||||
/*!
|
||||
@method didChangeChildAtIndex:
|
||||
@brief Convenience method that calls @c
|
||||
-didChangeElementsInRangePath:replacementLength:
|
||||
|
||||
@discussion This method is automatically called when one of the children is
|
||||
changed using one of the accessor methods or @c
|
||||
-setChild:atIndex:.
|
||||
|
||||
@param index
|
||||
The index of the child that was changed.
|
||||
*/
|
||||
- (void)didChangeChildAtIndex:(NSUInteger)index;
|
||||
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
/*!
|
||||
@methodgroup Evaluating Functions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method expectsVariableDefinitionInChildAtIndex:
|
||||
@brief Returns whether the child at @c anIndex is supposed to contain a
|
||||
variable definition.
|
||||
|
||||
@discussion This method is automatically called during expression parsing.
|
||||
You can override this method to opt into the default behaviour.
|
||||
If you override this method you do not need to call super.
|
||||
|
||||
The default implementation just returns @c NO for every index.
|
||||
|
||||
@param anIndex
|
||||
The index of the child to check.
|
||||
|
||||
@return @c YES if the child at @c anIndex is supposed to contain a
|
||||
variable definition, @c NO otherwise.
|
||||
*/
|
||||
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)anIndex;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@category MPFunction (MPSubcalssOverride)
|
||||
@brief The methods in this category must be implemented by any concrete
|
||||
subclasses of @c MPFunction.
|
||||
|
||||
@discussion @c MPFunction does not implement the functions itself but calls
|
||||
them at various points. If a subclass does not provide
|
||||
implementations your app will most likely crash.
|
||||
*/
|
||||
@interface MPFunction (MPSubclassOverride)
|
||||
|
||||
/* In Addition to the methods listed below MPFunction subclasses should also
|
||||
* override the NSObject method -description.
|
||||
* Also -expectsVariabledefinitionInChildAtIndex: can be overridden if one of the
|
||||
* children should contain a variable definition.
|
||||
*/
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
/*!
|
||||
@methodgroup Working With the Expression Tree
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method childrenAccessors
|
||||
@brief Returns the names of the children properties.
|
||||
|
||||
@discussion A @c MPFunction subclass should declare a public propertiy for
|
||||
every child it can have. This method should return the names as
|
||||
they would be used for key-value-coding.
|
||||
|
||||
@return An array of strings describing the names of children a function
|
||||
has.
|
||||
*/
|
||||
- (NSArray *)childrenAccessors;
|
||||
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
/*!
|
||||
@methodgroup Evaluating Functions
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method functionTermClass
|
||||
@brief Returns the class that represents the receiver's evaluation
|
||||
behaviour.
|
||||
|
||||
@discussion The class returned must be a subclass of @c MPFunctionTerm. That
|
||||
class must not implement a custom initializer.
|
||||
|
||||
@return The @c MPFunctionTerm subclass that can evaluate instances of the
|
||||
receiver's class.
|
||||
*/
|
||||
- (Class)functionTermClass;
|
||||
|
||||
@end
|
||||
@@ -1,213 +0,0 @@
|
||||
//
|
||||
// MPFunction.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 18.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunction.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPRangePath.h"
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
|
||||
|
||||
|
||||
@implementation MPFunction
|
||||
|
||||
|
||||
#pragma mark Creation Methods
|
||||
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
for (NSString *key in self.childrenAccessors) {
|
||||
MPExpression *emptyExpression = [[MPExpression alloc] init];
|
||||
emptyExpression.parent = self;
|
||||
[self setValue:emptyExpression
|
||||
forKey:key];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
|
||||
|
||||
- (MPExpression *)rootExpression
|
||||
{
|
||||
return [self.parent rootExpression];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPath
|
||||
{
|
||||
if (!self.parent) {
|
||||
return nil;
|
||||
}
|
||||
NSUInteger selfIndex = [self.parent indexOfElement:self];
|
||||
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)numberOfChildren
|
||||
{
|
||||
return self.childrenAccessors.count;
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)childAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [self valueForKey:self.childrenAccessors[index]];
|
||||
}
|
||||
|
||||
|
||||
- (void)setChild:(MPExpression *)child
|
||||
atIndex:(NSUInteger)index
|
||||
{
|
||||
[[self valueForKey:self.childrenAccessors[index]] setParent:nil];
|
||||
[self setValue:child
|
||||
forKey:self.childrenAccessors[index]];
|
||||
child.parent = self;
|
||||
[self didChangeChildAtIndex:index];
|
||||
}
|
||||
|
||||
|
||||
- (NSArray *)children
|
||||
{
|
||||
NSUInteger childCount = [self numberOfChildren];
|
||||
NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:childCount];
|
||||
for (NSInteger i = 0; i < childCount; i++) {
|
||||
[children addObject:[self childAtIndex:i]];
|
||||
}
|
||||
return [children copy];
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)indexOfChild:(MPExpression *)child
|
||||
{
|
||||
|
||||
NSUInteger index = 0;
|
||||
for (; index < [self numberOfChildren]; index++) {
|
||||
if ([self childAtIndex:index] == child) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
|
||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.length == 0) {
|
||||
return self;
|
||||
}
|
||||
MPExpression *child = [self childAtIndex:[indexPath indexAtPosition:0]];
|
||||
return [child elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Notifications
|
||||
|
||||
|
||||
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
||||
replacementLength:(NSUInteger)replacementLength
|
||||
{
|
||||
NSUInteger selfIndex = [self.parent indexOfElement:self];
|
||||
MPRangePath *newPath = MPMakeRangePath([rangePath.location indexPathByPreceedingIndex:selfIndex], rangePath.length);
|
||||
[self.parent changedElementsInRangePath:newPath
|
||||
replacementLength:replacementLength];
|
||||
}
|
||||
|
||||
|
||||
- (void)didChangeChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
MPRangePath *path = [[MPRangePath alloc] initWithRange:NSMakeRange(index, 1)];
|
||||
[self didChangeElementsInRangePath:path
|
||||
replacementLength:1];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Evaluating Functions
|
||||
|
||||
|
||||
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Working With Functions
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return @"[]";
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
id copy = [[self.class allocWithZone:zone] init];
|
||||
for (NSString *key in self.childrenAccessors) {
|
||||
MPExpression *child = [[self valueForKey:key] copy];
|
||||
[copy setValue:child forKey:key];
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSArray *children = [aDecoder decodeObject];
|
||||
NSInteger index = 0;
|
||||
for (MPExpression *child in children) {
|
||||
[self setChild:child atIndex:index];
|
||||
++index;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:self.children];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - MPExpressionElement
|
||||
|
||||
|
||||
- (BOOL)isString
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isFunction
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,61 +0,0 @@
|
||||
//
|
||||
// MPFunctionLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPLayout.h"
|
||||
|
||||
|
||||
|
||||
@class MPFunctionLayout, MPFunction, MPExpressionLayout;
|
||||
|
||||
|
||||
@interface MPFunctionLayout : MPLayout
|
||||
|
||||
+ (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function
|
||||
parent:(MPExpressionLayout *)parent;
|
||||
|
||||
@property (readonly, nonatomic, weak) MPFunction *function;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPFunctionLayout (MPSubclassOverride)
|
||||
|
||||
- (instancetype)initWithFunction:(MPFunction *)function
|
||||
parent:(MPExpressionLayout *)parent;
|
||||
|
||||
#pragma mark Cache Methods
|
||||
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
|
||||
generator:(CTLineRef (^)())generator;
|
||||
- (id)objectForPrivateCacheIndex:(NSUInteger)index
|
||||
generator:(id (^)())generator;
|
||||
|
||||
// Should also implement accessor method for special function type:
|
||||
// - (MPCustomFunction *)customFunction
|
||||
// {
|
||||
// return (MPCustomFunction *)self.function;
|
||||
// }
|
||||
|
||||
#pragma mark Size and Drawing Methods
|
||||
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index; // May be implemented
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index; // To be implemented
|
||||
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;
|
||||
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point; // To be implemented
|
||||
- (NSRect)generateBounds; // To be implemented
|
||||
- (void)draw; // To be implemented
|
||||
|
||||
// Specify the child that is used when the cursor enters the function from the left or right respectively
|
||||
- (NSUInteger)indexOfLeadingChild; // To be implemented
|
||||
- (NSUInteger)indexOfTrailingChild; // To be implemented
|
||||
|
||||
// The index of the child before (left) or after (right) the child at @c index. return NSNotFound if there is no child before or after @c index.
|
||||
- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index; // May be implemented
|
||||
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index; // May be implemented
|
||||
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index; // May be implemented
|
||||
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index; // May be implemented
|
||||
- (NSIndexSet *)indexesOfRemainingChildren; // May be implemented
|
||||
|
||||
@end
|
||||
@@ -1,154 +0,0 @@
|
||||
//
|
||||
// MPFunctionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPFunction.h"
|
||||
#import "MPSumFunction.h"
|
||||
#import "MPParenthesisFunction.h"
|
||||
#import "MPPowerFunction.h"
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
#import "MPExpressionLayout.h"
|
||||
#import "MPSumFunctionLayout.h"
|
||||
#import "MPParenthesisFunctionLayout.h"
|
||||
#import "MPPowerFunctionLayout.h"
|
||||
#import "MPFractionFunctionLayout.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
@implementation MPFunctionLayout
|
||||
|
||||
#pragma mark Creation Methods
|
||||
+ (MPFunctionLayout *)functionLayoutForFunction:(MPFunction *)function
|
||||
parent:(MPExpressionLayout *)parent
|
||||
{
|
||||
Class class = [function class];
|
||||
if (class == [MPSumFunction class]) {
|
||||
return [[MPSumFunctionLayout alloc] initWithFunction:function parent:parent];
|
||||
} else if (class == [MPParenthesisFunction class]) {
|
||||
return [[MPParenthesisFunctionLayout alloc] initWithFunction:function parent:parent];
|
||||
} else if (class == [MPPowerFunction class]) {
|
||||
return [[MPPowerFunctionLayout alloc] initWithFunction:function parent:parent];
|
||||
} else if (class == [MPFractionFunction class]) {
|
||||
return [[MPFractionFunctionLayout alloc] initWithFunction:function parent:parent];
|
||||
}
|
||||
return [[self alloc] initWithFunction:function
|
||||
parent:parent];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFunction:(MPFunction *)function
|
||||
parent:(MPExpressionLayout *)parent
|
||||
{
|
||||
self = [super initWithParent:parent];
|
||||
if (self) {
|
||||
_function = function;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Cache Methods
|
||||
- (CTLineRef)lineForPrivateCacheIndex:(NSUInteger)index
|
||||
generator:(CTLineRef (^)())generator
|
||||
{
|
||||
NSUInteger actualIndex = self.function.numberOfChildren + index;
|
||||
id (^actualGenerator)() = ^{
|
||||
CTLineRef line = generator();
|
||||
return CFBridgingRelease(line);
|
||||
};
|
||||
id lineObject = [self cachableObjectForIndex:actualIndex
|
||||
generator:actualGenerator];
|
||||
return (__bridge CTLineRef)(lineObject);
|
||||
}
|
||||
|
||||
- (id)objectForPrivateCacheIndex:(NSUInteger)index
|
||||
generator:(id (^)())generator
|
||||
{
|
||||
NSUInteger actualIndex = self.function.numberOfChildren + index;
|
||||
return [self cachableObjectForIndex:actualIndex generator:generator];
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfChildren
|
||||
{
|
||||
return self.function.numberOfChildren;
|
||||
}
|
||||
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
return [self cachableObjectForIndex:index generator:^id{
|
||||
MPExpression *child = [self.function childAtIndex:index];
|
||||
MPExpressionLayout *layout = [[MPExpressionLayout alloc] initWithExpression:child
|
||||
parent:self];
|
||||
layout.flipped = self.flipped;
|
||||
layout.usesSmallSize = self.usesSmallSize ? YES : [self childAtIndexUsesSmallSize:index];
|
||||
return layout;
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point
|
||||
{
|
||||
// A single index is used to communicate back wether the
|
||||
// selection should be before or after the function.
|
||||
// A 0 means before, a 1 means after.
|
||||
for (NSUInteger index = 0; index < self.function.numberOfChildren; index++) {
|
||||
MPLayout *childLayout = [self childLayoutAtIndex:index];
|
||||
NSRect childBounds = childLayout.bounds;
|
||||
NSPoint childOffset = [self offsetOfChildLayoutAtIndex:index];
|
||||
childBounds.origin.x += childOffset.x;
|
||||
childBounds.origin.y += childOffset.y;
|
||||
if (NSMouseInRect(point, childBounds, self.flipped)) {
|
||||
NSPoint pointInChild = NSMakePoint(point.x - childOffset.x, point.y - childOffset.y);
|
||||
NSIndexPath *subPath = [childLayout indexPathForMousePoint:pointInChild];
|
||||
return [subPath indexPathByPreceedingIndex:index];
|
||||
}
|
||||
}
|
||||
return [self indexPathForLocalMousePoint:point];
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfLeadingChild
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfTrailingChild
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)indexesOfRemainingChildren
|
||||
{
|
||||
return [NSIndexSet indexSet];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// MPFunctionTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 12.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
#define MPEvaluateExpression(var, index) NSDecimalNumber *var = [[self expressionAtIndex:index] evaluate:error]; if (var == nil) return nil
|
||||
|
||||
|
||||
|
||||
@class MPFunctionTerm, MPFunction, MPParsedExpression;
|
||||
|
||||
|
||||
@interface MPFunctionTerm : MPTerm
|
||||
|
||||
- (instancetype)initWithFunction:(MPFunction *)function
|
||||
errors:(NSArray *__autoreleasing *)errors; /* designated initializer */
|
||||
|
||||
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex;
|
||||
|
||||
@end
|
||||
@@ -1,72 +0,0 @@
|
||||
//
|
||||
// MPFunctionTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 12.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
|
||||
#import "MPFunction.h"
|
||||
#import "MPExpression.h"
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
|
||||
|
||||
@interface MPFunctionTerm ()
|
||||
|
||||
@property (nonatomic, strong) NSArray *parsedExpressions;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPFunctionTerm
|
||||
|
||||
- (instancetype)initWithFunction:(MPFunction *)function
|
||||
errors:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
NSAssert(function != nil, @"function must not be nil.");
|
||||
if ([self isMemberOfClass:[MPFunctionTerm class]]) {
|
||||
return [[function.functionTermClass alloc] initWithFunction:function
|
||||
errors:errors];
|
||||
}
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSMutableArray *parsedExpressions = [[NSMutableArray alloc] initWithCapacity:function.childrenAccessors.count];
|
||||
NSUInteger childIndex = 0;
|
||||
BOOL errorOccured = NO;
|
||||
NSMutableArray *mutableErrors = [[NSMutableArray alloc] init];
|
||||
for (NSString *accessor in function.childrenAccessors) {
|
||||
MPExpression *expression = [function valueForKey:accessor];
|
||||
NSArray *localErrors;
|
||||
MPParsedExpression *parsedExpression = [expression parseExpectingVariable:[function expectsVariableDefinitionInChildAtIndex:childIndex]
|
||||
errors:&localErrors];
|
||||
if (parsedExpression) {
|
||||
[parsedExpressions addObject:parsedExpression];
|
||||
} else {
|
||||
[mutableErrors addObjectsFromArray:localErrors];
|
||||
errorOccured = YES;
|
||||
}
|
||||
childIndex++;
|
||||
}
|
||||
if (errorOccured) {
|
||||
if (errors) {
|
||||
*errors = mutableErrors;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
self.parsedExpressions = parsedExpressions;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex
|
||||
{
|
||||
return [self.parsedExpressions objectAtIndex:anIndex];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
//
|
||||
// MPFunctionsViewController.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 28.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@class MPFunctionsViewController, MPFunctionsCollectionView;
|
||||
|
||||
|
||||
@interface MPFunctionsViewController : NSViewController
|
||||
|
||||
- (id)init;
|
||||
|
||||
@property (weak) IBOutlet MPFunctionsCollectionView *collectionView;
|
||||
@property (nonatomic, strong) NSArray *functionPrototypes;
|
||||
@property (nonatomic, strong) NSString *currentDescription;
|
||||
|
||||
@property (nonatomic, weak) id target;
|
||||
@property (nonatomic) SEL action; // 1 argument: The function to insert
|
||||
|
||||
@end
|
||||
@@ -1,272 +0,0 @@
|
||||
//
|
||||
// MPFunctionsViewController.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 28.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionsViewController.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "NSString+MPExpressionElement.h"
|
||||
#import "MPFunction.h"
|
||||
#import "MPSumFunction.h"
|
||||
#import "MPParenthesisFunction.h"
|
||||
#import "MPPowerFunction.h"
|
||||
#import "MPFractionFunction.h"
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
@class MPFunctionsCollectionView, MPFunctionTemplateView, MPFunctionTemplateItem;
|
||||
|
||||
@interface MPFunctionsCollectionView : NSCollectionView
|
||||
|
||||
@property (nonatomic, weak) MPFunctionTemplateItem *hoverItem;
|
||||
|
||||
@property (nonatomic, weak) id target;
|
||||
@property (nonatomic) SEL action;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface MPFunctionTemplateView : NSView
|
||||
|
||||
@property (nonatomic, strong) MPFunction *functionTemplate;
|
||||
@property (readonly, nonatomic, strong) MPFunctionLayout *functionTemplateLayout;
|
||||
@property (nonatomic, strong) NSTrackingArea *trackingArea;
|
||||
@property (nonatomic) BOOL mouseOver;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface MPFunctionTemplateItem : NSCollectionViewItem
|
||||
@property (nonatomic, copy) NSString *templateName;
|
||||
@end
|
||||
|
||||
|
||||
@implementation MPFunctionsCollectionView
|
||||
|
||||
- (void)mouseDown:(NSEvent *)theEvent
|
||||
{
|
||||
}
|
||||
|
||||
- (void)mouseUp:(NSEvent *)theEvent
|
||||
{
|
||||
NSPoint pointInView = [self convertPoint:theEvent.locationInWindow
|
||||
fromView:nil];
|
||||
for (NSUInteger index = 0; index < self.content.count; index++) {
|
||||
NSCollectionViewItem *viewItem = [self itemAtIndex:index];
|
||||
if (NSMouseInRect(pointInView, viewItem.view.frame, self.isFlipped)) {
|
||||
if (self.target && self.action) {
|
||||
[self.target performSelector:self.action
|
||||
withObject:[[viewItem.representedObject objectForKey:@"function" ] copy]
|
||||
afterDelay:0.0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPFunctionTemplateView
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (!self.trackingArea) {
|
||||
self.trackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds
|
||||
options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInKeyWindow|NSTrackingInVisibleRect
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:self.trackingArea];
|
||||
}
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)theEvent
|
||||
{
|
||||
self.mouseOver = YES;
|
||||
self.needsDisplay = YES;
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)theEvent
|
||||
{
|
||||
self.mouseOver = NO;
|
||||
self.needsDisplay = YES;
|
||||
}
|
||||
|
||||
- (void)setFunctionTemplate:(MPFunction *)functionTemplate
|
||||
{
|
||||
_functionTemplate = functionTemplate;
|
||||
_functionTemplateLayout = nil;
|
||||
}
|
||||
|
||||
@synthesize functionTemplateLayout = _functionTemplateLayout;
|
||||
- (MPFunctionLayout *)functionTemplateLayout
|
||||
{
|
||||
|
||||
if (!_functionTemplateLayout) {
|
||||
_functionTemplateLayout = [MPFunctionLayout functionLayoutForFunction:self.functionTemplate
|
||||
parent:nil];
|
||||
_functionTemplateLayout.usesSmallSize = YES;
|
||||
}
|
||||
return _functionTemplateLayout;
|
||||
}
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
return [self.functionTemplateLayout bounds].size;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
if (self.mouseOver) {
|
||||
[[NSColor selectedTextBackgroundColor] set];
|
||||
NSBezierPath *background = [NSBezierPath bezierPathWithRoundedRect:self.bounds
|
||||
xRadius:10
|
||||
yRadius:10];
|
||||
[background fill];
|
||||
}
|
||||
[[NSColor textColor] set];
|
||||
NSPoint origin = NSMakePoint((self.bounds.size.width - self.functionTemplateLayout.bounds.size.width) / 2, (self.bounds.size.height - self.functionTemplateLayout.bounds.size.height) / 2);
|
||||
origin.y -= self.functionTemplateLayout.bounds.origin.y;
|
||||
[self.functionTemplateLayout drawAtPoint:origin];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPFunctionTemplateItem
|
||||
|
||||
static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMouseOverContext";
|
||||
|
||||
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
return [super awakeAfterUsingCoder:aDecoder];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
if (context == MPFunctionTemplateViewMouseOverContext) {
|
||||
MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view;
|
||||
((MPFunctionsCollectionView *)self.collectionView).hoverItem = view.mouseOver ? self : nil;
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath
|
||||
ofObject:object
|
||||
change:change
|
||||
context:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRepresentedObject:(id)representedObject
|
||||
{
|
||||
MPFunctionTemplateView *view = (MPFunctionTemplateView *)self.view;
|
||||
view.functionTemplate = [representedObject objectForKey:@"function"];
|
||||
self.templateName = [representedObject objectForKey:@"name"];
|
||||
[view addObserver:self
|
||||
forKeyPath:@"mouseOver"
|
||||
options:0
|
||||
context:MPFunctionTemplateViewMouseOverContext];
|
||||
[super setRepresentedObject:representedObject];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface MPFunctionsViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPFunctionsViewController
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithNibName:@"MPFunctionsViewController"
|
||||
bundle:[NSBundle bundleForClass:[self class]]];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
MPFunction *sumFunction = [[MPSumFunction alloc] init];
|
||||
MPFunction *parenthesisFunction = [[MPParenthesisFunction alloc] init];
|
||||
MPFunction *powerFunction = [[MPPowerFunction alloc] init];
|
||||
MPPowerFunction *squareFunction = [[MPPowerFunction alloc] init];
|
||||
squareFunction.exponentExpression = [[MPExpression alloc] initWithElement:@"2"];
|
||||
MPPowerFunction *cubicFunction = [[MPPowerFunction alloc] init];
|
||||
cubicFunction.exponentExpression = [[MPExpression alloc] initWithElement:@"3"];
|
||||
MPFractionFunction *fractionFunction = [[MPFractionFunction alloc] init];
|
||||
self.functionPrototypes = @[
|
||||
@{@"function": sumFunction,
|
||||
@"name": NSLocalizedString(@"Sum", @"Sum Function Name")},
|
||||
@{@"function": parenthesisFunction,
|
||||
@"name": NSLocalizedString(@"Parenthesis", @"Parenthesis Function Name")},
|
||||
@{@"function": squareFunction,
|
||||
@"name": NSLocalizedString(@"Square", @"Square Function Name")},
|
||||
@{@"function": cubicFunction,
|
||||
@"name": NSLocalizedString(@"Cubic", @"Cubic Function Name")},
|
||||
@{@"function": powerFunction,
|
||||
@"name": NSLocalizedString(@"Power", @"Power Function Name")},
|
||||
@{@"function": fractionFunction,
|
||||
@"name": NSLocalizedString(@"Fraction", @"Fraction Function Name")}
|
||||
];
|
||||
[self.collectionView addObserver:self
|
||||
forKeyPath:@"hoverItem"
|
||||
options:0
|
||||
context:MPCollectionViewHoverItemChangeContext];
|
||||
self.collectionView.target = self.target;
|
||||
self.collectionView.action = self.action;
|
||||
}
|
||||
|
||||
static void *MPCollectionViewHoverItemChangeContext = @"MPCollectionViewHoverItemChangeContext";
|
||||
|
||||
- (void)setView:(NSView *)view
|
||||
{
|
||||
[super setView:view];
|
||||
}
|
||||
|
||||
- (void)setTarget:(id)target
|
||||
{
|
||||
_target = target;
|
||||
if (self.collectionView) {
|
||||
self.collectionView.target = target;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAction:(SEL)action
|
||||
{
|
||||
_action = action;
|
||||
if (self.collectionView) {
|
||||
self.collectionView.action = action;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
{
|
||||
if (context == MPCollectionViewHoverItemChangeContext) {
|
||||
self.currentDescription = self.collectionView.hoverItem.templateName;
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath
|
||||
ofObject:object
|
||||
change:change
|
||||
context:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14B25" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MPFunctionsViewController">
|
||||
<connections>
|
||||
<outlet property="collectionView" destination="QmA-Ge-oq3" id="mYG-bs-5TG"/>
|
||||
<outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView id="Hz6-mo-xeY" customClass="MPWhiteView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="185"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="D89-Gi-YJm">
|
||||
<rect key="frame" x="10" y="10" width="300" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Description" drawsBackground="YES" id="xrL-Xy-aRm">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="currentDescription" id="0Vx-OA-1Zy">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Choose an Element</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9w0-UQ-Fqx">
|
||||
<rect key="frame" x="10" y="27" width="300" height="148"/>
|
||||
<clipView key="contentView" copiesOnScroll="NO" id="Z8C-m4-8Mb">
|
||||
<rect key="frame" x="1" y="1" width="248" height="158"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<collectionView selectable="YES" id="QmA-Ge-oq3" customClass="MPFunctionsCollectionView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="248" height="158"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<connections>
|
||||
<binding destination="-2" name="content" keyPath="functionPrototypes" id="sPD-4B-zL5"/>
|
||||
<outlet property="itemPrototype" destination="4Wf-e2-Nmy" id="v9O-tD-Y7U"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="umr-K7-88h">
|
||||
<rect key="frame" x="1" y="144" width="233" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="MEs-Qt-Wze">
|
||||
<rect key="frame" x="234" y="1" width="15" height="143"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="D89-Gi-YJm" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="9WK-zU-g2j"/>
|
||||
<constraint firstItem="D89-Gi-YJm" firstAttribute="leading" secondItem="9w0-UQ-Fqx" secondAttribute="leading" id="FSW-Jj-9RW"/>
|
||||
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" constant="10" id="OFp-Pr-zZT"/>
|
||||
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" constant="10" id="fPs-mT-ads"/>
|
||||
<constraint firstAttribute="bottom" secondItem="D89-Gi-YJm" secondAttribute="bottom" constant="10" id="mk2-Ph-Kh5"/>
|
||||
<constraint firstItem="D89-Gi-YJm" firstAttribute="top" secondItem="9w0-UQ-Fqx" secondAttribute="bottom" id="nYD-XL-3y1"/>
|
||||
<constraint firstItem="9w0-UQ-Fqx" firstAttribute="trailing" secondItem="D89-Gi-YJm" secondAttribute="trailing" id="w9y-K1-mce"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="-101" y="140.5"/>
|
||||
</customView>
|
||||
<collectionViewItem id="4Wf-e2-Nmy" customClass="MPFunctionTemplateItem">
|
||||
<connections>
|
||||
<outlet property="view" destination="dE9-Vu-psk" id="hX8-fz-7FR"/>
|
||||
</connections>
|
||||
</collectionViewItem>
|
||||
<view id="dE9-Vu-psk" customClass="MPFunctionTemplateView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<point key="canvasLocation" x="119" y="-114.5"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,76 +0,0 @@
|
||||
//
|
||||
// MPLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 07.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#define kMPEmptyBoxWidth (self.usesSmallSize ? 2.0 : 3.0)
|
||||
#define kMPEmptyBoxHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font))
|
||||
#define kMPEmptyBoxYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)))
|
||||
|
||||
#define kMPEmptyBoxDrawingWidth kMPEmptyBoxWidth
|
||||
#define kMPEmptyBoxDrawingHeight (CTFontGetDescent((CTFontRef)self.font) + CTFontGetAscent((CTFontRef)self.font))
|
||||
#define kMPEmptyBoxDrawingYOrigin (-(CTFontGetDescent((CTFontRef)self.font) + CTFontGetLeading((CTFontRef)self.font)/2))
|
||||
|
||||
|
||||
|
||||
@class MPLayout, MPRangePath;
|
||||
|
||||
|
||||
@interface MPLayout : NSObject
|
||||
|
||||
#pragma mark Creation Methods
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithParent:(MPLayout *)parent;
|
||||
|
||||
#pragma mark Properties
|
||||
- (NSFont *)normalFontWithSize:(CGFloat)size;
|
||||
- (NSFont *)specialFontWithSize:(CGFloat)size;
|
||||
- (CGFloat)contextInferredFontSize;
|
||||
- (CGFloat)normalFontSize;
|
||||
- (CGFloat)smallFontSize;
|
||||
|
||||
- (NSFont *)font;
|
||||
|
||||
#pragma mark Cache Tree
|
||||
@property (readonly, nonatomic, weak) MPLayout *parent;
|
||||
|
||||
#pragma mark Cache Methods
|
||||
// Querying Caches
|
||||
- (id)cachableObjectForIndex:(NSUInteger)index
|
||||
generator:(id(^)())generator;
|
||||
|
||||
// Clearing Caches
|
||||
- (void)clearCacheInRange:(NSRange)range
|
||||
replacementLength:(NSUInteger)replacementLength;
|
||||
- (void)invalidate;
|
||||
|
||||
- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
#pragma mark Calculation and Drawing Methods
|
||||
- (CTLineRef)createLineForString:(NSString *)aString;
|
||||
- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize;
|
||||
- (CTLineRef)createLineForString:(NSString *)aString usingFont:(NSFont *)font;
|
||||
|
||||
@property (nonatomic, getter = isFlipped) BOOL flipped;
|
||||
@property (nonatomic) BOOL usesSmallSize;
|
||||
- (NSRect)bounds;
|
||||
|
||||
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath; /* if rangePath.length is 0 the returned rect will have a width of 0 */
|
||||
|
||||
- (void)drawAtPoint:(NSPoint)point;
|
||||
|
||||
@end
|
||||
|
||||
@interface MPLayout (MPSubclassImplement)
|
||||
- (NSUInteger)numberOfChildren;
|
||||
- (BOOL)drawsChildrenManually;
|
||||
- (MPLayout *)childLayoutAtIndex:(NSUInteger)index; // To be implemented
|
||||
- (NSRect)generateBounds; // To be implemented
|
||||
- (NSRect)boundingRectForRange:(NSRange)range; // To be implemented, use rangePath instead, this one has wrong origin
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index;
|
||||
- (NSIndexPath *)indexPathForMousePoint:(NSPoint)point;
|
||||
- (void)draw; // To be implemented
|
||||
@end
|
||||
@@ -1,206 +0,0 @@
|
||||
//
|
||||
// MPLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPLayout.h"
|
||||
|
||||
#import "MPRangePath.h"
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
@interface MPLayout ()
|
||||
|
||||
// Querying and Storing Caches
|
||||
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index;
|
||||
- (void)ensureCacheSizeForIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPLayout {
|
||||
NSMutableArray *_cache;
|
||||
NSRect _cachedBounds;
|
||||
}
|
||||
|
||||
#pragma mark Creation Methods
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_cache = [[NSMutableArray alloc] init];
|
||||
_cachedBounds = NSZeroRect;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithParent:(MPLayout *)parent
|
||||
{
|
||||
self = [self init];
|
||||
if (self) {
|
||||
_parent = parent;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Properties
|
||||
- (NSFont *)normalFontWithSize:(CGFloat)size
|
||||
{
|
||||
return [NSFont fontWithName:@"CMU Serif"
|
||||
size:size];
|
||||
}
|
||||
|
||||
- (NSFont *)specialFontWithSize:(CGFloat)size
|
||||
{
|
||||
return [NSFont fontWithName:@"CMU Serif Italic"
|
||||
size:size];
|
||||
}
|
||||
|
||||
- (CGFloat)contextInferredFontSize
|
||||
{
|
||||
return self.usesSmallSize ? self.smallFontSize : self.normalFontSize;
|
||||
}
|
||||
|
||||
- (CGFloat)normalFontSize
|
||||
{
|
||||
return 18.0;
|
||||
}
|
||||
|
||||
- (CGFloat)smallFontSize
|
||||
{
|
||||
return 12.0;
|
||||
}
|
||||
|
||||
- (NSFont *)font
|
||||
{
|
||||
return [self normalFontWithSize:self.contextInferredFontSize];
|
||||
}
|
||||
|
||||
#pragma mark Cache Tree
|
||||
// Querying and Storing Caches
|
||||
- (BOOL)hasCacheForElementAtIndex:(NSUInteger)index
|
||||
{
|
||||
if (index >= _cache.count) {
|
||||
return NO;
|
||||
}
|
||||
return _cache[index] != [NSNull null];
|
||||
}
|
||||
|
||||
- (id)cachableObjectForIndex:(NSUInteger)index
|
||||
generator:(id (^)())generator
|
||||
{
|
||||
if ([self hasCacheForElementAtIndex:index]) {
|
||||
return _cache[index];
|
||||
}
|
||||
id object = generator();
|
||||
[self ensureCacheSizeForIndex:index];
|
||||
_cache[index] = object;
|
||||
return object;
|
||||
}
|
||||
|
||||
- (void)ensureCacheSizeForIndex:(NSUInteger)index
|
||||
{
|
||||
while (index >= _cache.count) {
|
||||
[_cache addObject:[NSNull null]];
|
||||
}
|
||||
}
|
||||
|
||||
// Clearing Caches
|
||||
- (void)clearCacheInRange:(NSRange)range
|
||||
replacementLength:(NSUInteger)replacementLength
|
||||
{
|
||||
NSMutableArray *placeholders = [[NSMutableArray alloc] initWithCapacity:replacementLength];
|
||||
while (placeholders.count < replacementLength) {
|
||||
[placeholders addObject:[NSNull null]];
|
||||
}
|
||||
[_cache replaceObjectsInRange:range
|
||||
withObjectsFromArray:placeholders];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_cachedBounds = NSZeroRect;
|
||||
[self.parent invalidate];
|
||||
}
|
||||
|
||||
- (MPLayout *)childLayoutAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.length == 0) {
|
||||
return self;
|
||||
}
|
||||
MPLayout *child = [self childLayoutAtIndex:indexPath.firstIndex];
|
||||
return [child childLayoutAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
|
||||
}
|
||||
|
||||
#pragma mark Calculation and Drawing Methods
|
||||
- (CTLineRef)createLineForString:(NSString *)aString
|
||||
{
|
||||
return [self createLineForString:aString
|
||||
usingFont:self.font];
|
||||
}
|
||||
|
||||
- (CTLineRef)createLineForString:(NSString *)aString emphasize:(BOOL)emphasize
|
||||
{
|
||||
return [self createLineForString:aString
|
||||
usingFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
|
||||
}
|
||||
|
||||
- (CTLineRef)createLineForString:(NSString *)aString
|
||||
usingFont:(NSFont *)font
|
||||
{
|
||||
NSAttributedString *text = [[NSAttributedString alloc] initWithString:aString
|
||||
attributes:@{NSFontAttributeName: font}];
|
||||
CFAttributedStringRef attributedString = CFBridgingRetain(text);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attributedString);
|
||||
CFRelease(attributedString);
|
||||
return line;
|
||||
}
|
||||
|
||||
- (NSRect)bounds
|
||||
{
|
||||
if (NSEqualRects(_cachedBounds, NSZeroRect)) {
|
||||
_cachedBounds = [self generateBounds];
|
||||
}
|
||||
return _cachedBounds;
|
||||
}
|
||||
|
||||
- (NSRect)boundingRectForRangePath:(MPRangePath *)rangePath
|
||||
{
|
||||
if (rangePath.location.length == 1) {
|
||||
return [self boundingRectForRange:rangePath.rangeAtLastIndex];
|
||||
}
|
||||
NSUInteger nextIndex = [rangePath.location indexAtPosition:0];
|
||||
NSIndexPath *newLocation = [rangePath.location indexPathByRemovingFirstIndex];
|
||||
MPRangePath *newRangePath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length];
|
||||
NSRect bounds = [[self childLayoutAtIndex:nextIndex] boundingRectForRangePath:newRangePath];
|
||||
NSPoint offset = [self offsetOfChildLayoutAtIndex:nextIndex];
|
||||
bounds.origin = NSMakePoint(bounds.origin.x + offset.x, bounds.origin.y + offset.y);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (BOOL)drawsChildrenManually
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)drawAtPoint:(NSPoint)point
|
||||
{
|
||||
NSAffineTransform *transform = [NSAffineTransform transform];
|
||||
[transform translateXBy:point.x
|
||||
yBy:point.y];
|
||||
[transform concat];
|
||||
[self draw];
|
||||
if (!self.drawsChildrenManually) {
|
||||
for (NSUInteger index = 0; index < [self numberOfChildren]; index++) {
|
||||
MPLayout *childLayout = [self childLayoutAtIndex:index];
|
||||
NSPoint offset = [self offsetOfChildLayoutAtIndex:index];
|
||||
[childLayout drawAtPoint:offset];
|
||||
}
|
||||
}
|
||||
[transform invert];
|
||||
[transform concat];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,31 +0,0 @@
|
||||
//
|
||||
// MPMathRules.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 14.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthKey;
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey;
|
||||
FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey;
|
||||
|
||||
|
||||
@class MPMathRules;
|
||||
|
||||
|
||||
@interface MPMathRules : NSObject
|
||||
|
||||
+ (MPMathRules *)sharedRules;
|
||||
|
||||
@property (nonatomic, getter = isUsingUserDefaultValues) BOOL usingUserDefaultValues;
|
||||
|
||||
// Default value uses the key "..." in NSUserDefaults or ... if the key does not exist.
|
||||
@property (nonatomic) NSUInteger maximumOperatorChainLength; // +--++-5 -> Chain length: 6, 2 default (sign of number and operator) (0 is invalid?)
|
||||
@property (nonatomic) NSUInteger maximumOperatorChainLengthInMultiplication; // Default: 1, 0 means actually 0
|
||||
|
||||
@property (nonatomic) BOOL isUsingDegrees;
|
||||
|
||||
@end
|
||||
@@ -1,83 +0,0 @@
|
||||
//
|
||||
// MPMathRules.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 14.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPMathRules.h"
|
||||
|
||||
NSString *MPMathRulesMaximumOperatorChainLengthKey = @"MPMathRulesMaximumOperatorChainLengthKey";
|
||||
NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey = @"MPMathRulesMaximumOperatorChainLengthInMultiplicationKey";
|
||||
NSString *MPMathRulesIsUsingDegreesKey = @"MPMathRulesIsUsingDegreesKey";
|
||||
|
||||
@implementation MPMathRules
|
||||
|
||||
static MPMathRules *sharedRules;
|
||||
|
||||
+ (MPMathRules *)sharedRules
|
||||
{
|
||||
if (!sharedRules) {
|
||||
sharedRules = [[MPMathRules alloc] init];
|
||||
}
|
||||
return sharedRules;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if (sharedRules) {
|
||||
return sharedRules;
|
||||
}
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_usingUserDefaultValues = YES;
|
||||
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
|
||||
NSNumber *userDefaultsMaximumOperatorChainLength = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthKey];
|
||||
NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
|
||||
NSNumber *userDefaultsIsUsingDegrees = [userDefaults objectForKey:MPMathRulesIsUsingDegreesKey];
|
||||
|
||||
_maximumOperatorChainLength = userDefaultsMaximumOperatorChainLength != nil ? userDefaultsMaximumOperatorChainLength.unsignedIntegerValue : 2;
|
||||
_maximumOperatorChainLengthInMultiplication = userDefaultsMaximumOperatorChainLengthInMultiplication != nil ? userDefaultsMaximumOperatorChainLengthInMultiplication.unsignedIntegerValue : 1;
|
||||
_isUsingDegrees = userDefaultsIsUsingDegrees.boolValue;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUsingUserDefaultValues:(BOOL)usingUserDefaultValues
|
||||
{
|
||||
_usingUserDefaultValues = usingUserDefaultValues;
|
||||
// Save the current values
|
||||
self.maximumOperatorChainLength = self.maximumOperatorChainLength;
|
||||
self.maximumOperatorChainLengthInMultiplication = self.maximumOperatorChainLengthInMultiplication;
|
||||
self.isUsingDegrees = self.isUsingDegrees;
|
||||
}
|
||||
|
||||
- (void)setMaximumOperatorChainLength:(NSUInteger)maximumOperatorChainLength
|
||||
{
|
||||
_maximumOperatorChainLength = maximumOperatorChainLength;
|
||||
if (self.isUsingUserDefaultValues) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLength)
|
||||
forKey:MPMathRulesMaximumOperatorChainLengthKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setMaximumOperatorChainLengthInMultiplication:(NSUInteger)maximumOperatorChainLengthInMultiplication
|
||||
{
|
||||
_maximumOperatorChainLengthInMultiplication = maximumOperatorChainLengthInMultiplication;
|
||||
if (self.isUsingUserDefaultValues) {
|
||||
[[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInMultiplication)
|
||||
forKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsUsingDegrees:(BOOL)isUsingDegrees
|
||||
{
|
||||
_isUsingDegrees = isUsingDegrees;
|
||||
if (self.isUsingUserDefaultValues) {
|
||||
[[NSUserDefaults standardUserDefaults] setBool:isUsingDegrees
|
||||
forKey:MPMathRulesIsUsingDegreesKey];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPNegatedTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 22.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPNegatedTerm;
|
||||
|
||||
|
||||
@interface MPNegatedTerm : MPTerm
|
||||
|
||||
- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) MPTerm *term;
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// MPNegatedTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 22.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPNegatedTerm.h"
|
||||
|
||||
@implementation MPNegatedTerm
|
||||
|
||||
- (instancetype)initWithTerm:(MPTerm *)term
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(term != nil, @"term must not be nil.");
|
||||
_term = term;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDecimalNumber *value = [self.term evaluate:error];
|
||||
if (value) {
|
||||
value = [value decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPNumber.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPNumber;
|
||||
|
||||
|
||||
@interface MPNumber : MPTerm
|
||||
|
||||
- (instancetype)initWithNumber:(NSDecimalNumber *)number; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) NSDecimalNumber *number;
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// MPNumber.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPNumber.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
@implementation MPNumber
|
||||
|
||||
- (instancetype)initWithNumber:(NSDecimalNumber *)number
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(number != nil, @"number must not be nil.");
|
||||
_number = number;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return self.number;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,35 +0,0 @@
|
||||
//
|
||||
// MPParenthesisFunction.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 17.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunction.h"
|
||||
|
||||
|
||||
|
||||
@class MPParenthesisFunction, MPExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPParenthesisFunction
|
||||
@brief A parenthesis function encapsulates a single expression and thus
|
||||
prioritizes it in evaluation.
|
||||
|
||||
@discussion Prioritizing the expression means that the complete expression
|
||||
must be evaluated 'as is'. No part of it can be 'used' elsewhere
|
||||
before the parenthesis function has been evaluated.
|
||||
*/
|
||||
@interface MPParenthesisFunction : MPFunction
|
||||
|
||||
/*!
|
||||
@property expression
|
||||
@brief The expression encapsulated by the parenthesis.
|
||||
|
||||
@discussion The expression must not define a variable.
|
||||
*/
|
||||
@property (nonatomic, strong) MPExpression *expression;
|
||||
|
||||
@end
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
//
|
||||
// MPParenthesisFunction.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 17.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPParenthesisFunction.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPParenthesisTerm.h"
|
||||
|
||||
|
||||
|
||||
@implementation MPParenthesisFunction
|
||||
|
||||
MPFunctionAccessorImplementation(Expression, _expression)
|
||||
|
||||
- (NSArray *)childrenAccessors
|
||||
{
|
||||
return @[@"expression"];
|
||||
}
|
||||
|
||||
- (Class)functionTermClass
|
||||
{
|
||||
return [MPParenthesisTerm class];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"(%@)", self.expression.description];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// MPParenthesisFunctionLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 17.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
|
||||
|
||||
@class MPParenthesisFunctionLayout, MPParenthesisFunction;
|
||||
|
||||
|
||||
@interface MPParenthesisFunctionLayout : MPFunctionLayout
|
||||
|
||||
- (MPParenthesisFunction *)parenthesisFunction;
|
||||
|
||||
@end
|
||||
@@ -1,187 +0,0 @@
|
||||
//
|
||||
// MPParenthesisFunctionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 17.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPParenthesisFunctionLayout.h"
|
||||
|
||||
#import "MPParenthesisFunction.h"
|
||||
|
||||
#define MPParenthesisFunctionOpeningParensOffset 2
|
||||
#define MPParenthesisFunctionClosingParensOffset 0
|
||||
|
||||
@interface MPParenthesisFunctionLayout ()
|
||||
|
||||
- (NSBezierPath *)openingParens;
|
||||
- (NSBezierPath *)closingParens;
|
||||
|
||||
- (NSBezierPath *)transformedOpeningParens;
|
||||
- (NSBezierPath *)transformedClosingParens;
|
||||
|
||||
- (NSAffineTransform *)parenthesisTransform;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPParenthesisFunctionLayout
|
||||
|
||||
- (MPParenthesisFunction *)parenthesisFunction
|
||||
{
|
||||
return (MPParenthesisFunction *)self.function;
|
||||
}
|
||||
|
||||
- (NSBezierPath *)openingParens
|
||||
{
|
||||
NSBezierPath *parens = [self objectForPrivateCacheIndex:0 generator:^id{
|
||||
CTLineRef line = [self createLineForString:@"("];
|
||||
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
|
||||
CFRetain(glyphRuns);
|
||||
CTRunRef glyphRun = CFArrayGetValueAtIndex(glyphRuns, 0);
|
||||
CFRetain(glyphRun);
|
||||
|
||||
CGGlyph *glyphs = malloc(1 * sizeof(CGGlyph));
|
||||
CTRunGetGlyphs(glyphRun, CFRangeMake(0, 1), glyphs);
|
||||
NSGlyph glyph = glyphs[0];
|
||||
|
||||
NSBezierPath *path = [[NSBezierPath alloc] init];
|
||||
[path moveToPoint:NSZeroPoint];
|
||||
[path appendBezierPathWithGlyph:glyph
|
||||
inFont:self.font];
|
||||
|
||||
CFRelease(line);
|
||||
CFRelease(glyphRuns);
|
||||
CFRelease(glyphRun);
|
||||
free(glyphs);
|
||||
|
||||
return path;
|
||||
}];
|
||||
return parens;
|
||||
}
|
||||
|
||||
- (NSBezierPath *)closingParens
|
||||
{
|
||||
NSBezierPath *parens = [self objectForPrivateCacheIndex:1 generator:^id{
|
||||
CTLineRef line = [self createLineForString:@")"];
|
||||
CFArrayRef glyphRuns = CTLineGetGlyphRuns(line);
|
||||
CFRetain(glyphRuns);
|
||||
CTRunRef glyphRun = CFArrayGetValueAtIndex(glyphRuns, 0);
|
||||
CFRetain(glyphRun);
|
||||
|
||||
CGGlyph *glyphs = malloc(1 * sizeof(CGGlyph));
|
||||
CTRunGetGlyphs(glyphRun, CFRangeMake(0, 1), glyphs);
|
||||
NSGlyph glyph = glyphs[0];
|
||||
|
||||
NSBezierPath *path = [[NSBezierPath alloc] init];
|
||||
[path moveToPoint:NSZeroPoint];
|
||||
[path appendBezierPathWithGlyph:glyph
|
||||
inFont:self.font];
|
||||
|
||||
CFRelease(line);
|
||||
CFRelease(glyphRuns);
|
||||
CFRelease(glyphRun);
|
||||
free(glyphs);
|
||||
|
||||
return path;
|
||||
}];
|
||||
return parens;
|
||||
}
|
||||
|
||||
- (NSBezierPath *)transformedOpeningParens
|
||||
{
|
||||
NSBezierPath *parens = self.openingParens.copy;
|
||||
[parens transformUsingAffineTransform:self.parenthesisTransform];
|
||||
return parens;
|
||||
}
|
||||
|
||||
- (NSBezierPath *)transformedClosingParens
|
||||
{
|
||||
NSBezierPath *parens = self.closingParens.copy;
|
||||
[parens transformUsingAffineTransform:self.parenthesisTransform];
|
||||
return parens;
|
||||
}
|
||||
|
||||
- (NSAffineTransform *)parenthesisTransform
|
||||
{
|
||||
NSAffineTransform *transform = [NSAffineTransform transform];
|
||||
[transform scaleXBy:1
|
||||
yBy:self.scaleFactor];
|
||||
return transform;
|
||||
}
|
||||
|
||||
- (CGFloat)scaleFactor
|
||||
{
|
||||
NSRect parensBounds = self.openingParens.bounds;
|
||||
NSRect expressionBounds = [self childLayoutAtIndex:0].bounds;
|
||||
CGFloat parensHeight = parensBounds.size.height;
|
||||
CGFloat expressionHeight = expressionBounds.size.height;
|
||||
if (expressionHeight <= parensHeight) {
|
||||
return 1.0;
|
||||
}
|
||||
return expressionHeight / parensHeight;
|
||||
}
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
return NSMakePoint(self.openingParens.bounds.size.width + MPParenthesisFunctionOpeningParensOffset, 0);
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
|
||||
{
|
||||
#warning Missing Implementation
|
||||
return [NSIndexPath indexPathWithIndex:0];
|
||||
}
|
||||
|
||||
- (NSIndexSet *)indexesOfRemainingChildren
|
||||
{
|
||||
return [NSIndexSet indexSetWithIndex:0];
|
||||
}
|
||||
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
NSRect openingParensBounds = self.transformedOpeningParens.bounds;
|
||||
NSRect closingParensBounds = self.transformedClosingParens.bounds;
|
||||
NSRect bounds = [self childLayoutAtIndex:0].bounds;
|
||||
|
||||
if (openingParensBounds.size.height > bounds.size.height) {
|
||||
bounds.size.height = openingParensBounds.size.height;
|
||||
}
|
||||
if (closingParensBounds.size.height > bounds.size.height) {
|
||||
bounds.size.height = closingParensBounds.size.height;
|
||||
}
|
||||
|
||||
bounds.size.width += MPParenthesisFunctionOpeningParensOffset;
|
||||
bounds.size.width += openingParensBounds.size.width;
|
||||
bounds.size.width += MPParenthesisFunctionClosingParensOffset;
|
||||
bounds.size.width += closingParensBounds.size.width;
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
NSBezierPath *openingParens = self.transformedOpeningParens;
|
||||
MPLayout *expressionLayout = [self childLayoutAtIndex:0];
|
||||
NSBezierPath *closingParens = self.transformedClosingParens;
|
||||
|
||||
CGFloat x = 0;
|
||||
CGFloat y = -openingParens.bounds.origin.y + expressionLayout.bounds.origin.y;
|
||||
|
||||
NSAffineTransform *transform = [NSAffineTransform transform];
|
||||
[transform translateXBy:0
|
||||
yBy:y];
|
||||
[openingParens transformUsingAffineTransform:transform];
|
||||
[openingParens fill];
|
||||
x += openingParens.bounds.size.width;
|
||||
x += MPParenthesisFunctionOpeningParensOffset;
|
||||
x += expressionLayout.bounds.size.width;
|
||||
x += MPParenthesisFunctionClosingParensOffset;
|
||||
|
||||
[transform translateXBy:x
|
||||
yBy:0];
|
||||
[closingParens transformUsingAffineTransform:transform];
|
||||
[closingParens fill];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// MPParenthesisTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPParenthesisTerm;
|
||||
|
||||
|
||||
@interface MPParenthesisTerm : MPFunctionTerm
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// MPParenthesisTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPParenthesisTerm.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
@implementation MPParenthesisTerm
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [[self expressionAtIndex:0] evaluate:error];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// MPParsedExpression.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 14.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@class MPParsedExpression, MPTerm;
|
||||
|
||||
|
||||
/*!
|
||||
@const MPMathKitErrorDomain
|
||||
@brief Predefined error domain for errors from the MathKit framework.
|
||||
|
||||
@discussion Errors in MathKit can occur during parsing of expressions or
|
||||
during evaluation of expressions. These two can be distinguished
|
||||
by the error code. Parsing errors have lower error codes.
|
||||
Evaluation errors (math errors) have error codes from @c 100 upwards.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain;
|
||||
|
||||
FOUNDATION_EXPORT NSString *const MPPathToExpressionKey;
|
||||
FOUNDATION_EXPORT NSString *const MPErrorRangeKey;
|
||||
|
||||
|
||||
@interface MPParsedExpression : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSString *definedVariable;
|
||||
@property (nonatomic, strong) MPTerm *term;
|
||||
|
||||
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;
|
||||
|
||||
@end
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// MPParsedExpression.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 14.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain";
|
||||
NSString *const MPPathToExpressionKey = @"MPPathToExpressionKey";
|
||||
NSString *const MPErrorRangeKey = @"MPErrorRangeKey";
|
||||
|
||||
@implementation MPParsedExpression
|
||||
|
||||
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self.term evaluate:error];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// MPPowerFunction.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 30.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunction.h"
|
||||
|
||||
|
||||
|
||||
@class MPPowerFunction, MPExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPPowerFunction
|
||||
@brief This class represents a power.
|
||||
*/
|
||||
@interface MPPowerFunction : MPFunction
|
||||
|
||||
/*!
|
||||
@property exponentExpression
|
||||
@brief The receiver's exponent.
|
||||
|
||||
@discussion The base of the power can only be set during evaluation because
|
||||
it is not actually part of the function element.
|
||||
The exponent must not define a variable.
|
||||
*/
|
||||
@property (nonatomic, strong) MPExpression *exponentExpression;
|
||||
|
||||
@end
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// MPPowerFunction.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 30.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
|
||||
@implementation MPPowerFunction
|
||||
|
||||
MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression)
|
||||
|
||||
- (NSArray *)childrenAccessors
|
||||
{
|
||||
return @[@"exponentExpression"];
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"^%@", self.exponentExpression.description];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPPowerFunctionLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 30.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
|
||||
|
||||
@class MPPowerFunctionLayout, MPPowerFunction;
|
||||
|
||||
|
||||
@interface MPPowerFunctionLayout : MPFunctionLayout
|
||||
|
||||
@property (nonatomic) NSRect baseBounds;
|
||||
|
||||
- (MPPowerFunction *)powerFunction;
|
||||
|
||||
@end
|
||||
@@ -1,64 +0,0 @@
|
||||
//
|
||||
// MPPowerFunctionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 30.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPowerFunctionLayout.h"
|
||||
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
#define kPowerFunctionExponentXOffset 1
|
||||
#define kPowerFunctionTrailingOffset 2
|
||||
|
||||
@implementation MPPowerFunctionLayout
|
||||
|
||||
- (NSRect)baseBounds
|
||||
{
|
||||
if (NSEqualRects(_baseBounds, NSZeroRect)) {
|
||||
return NSMakeRect(0, kMPEmptyBoxYOrigin, 0, kMPEmptyBoxHeight);
|
||||
}
|
||||
return _baseBounds;
|
||||
}
|
||||
|
||||
- (MPPowerFunction *)powerFunction
|
||||
{
|
||||
return (MPPowerFunction *)self.function;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)indexesOfRemainingChildren
|
||||
{
|
||||
return [NSIndexSet indexSetWithIndex:0];
|
||||
}
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
CGFloat y = (self.baseBounds.size.height + self.baseBounds.origin.y) / 2;
|
||||
return NSMakePoint(kPowerFunctionExponentXOffset, y);
|
||||
}
|
||||
|
||||
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
|
||||
{
|
||||
return [[NSIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0];
|
||||
}
|
||||
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
NSRect exponentBounds = [self childLayoutAtIndex:0].bounds;
|
||||
CGFloat y = self.baseBounds.origin.y;
|
||||
CGFloat height = -y + [self offsetOfChildLayoutAtIndex:0].y + exponentBounds.size.height + exponentBounds.origin.y;
|
||||
CGFloat width = kPowerFunctionExponentXOffset + exponentBounds.size.width + kPowerFunctionTrailingOffset;
|
||||
return NSMakeRect(0, y, width, height);
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{}
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// MPPowerTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPPowerTerm, MPTerm;
|
||||
|
||||
|
||||
@interface MPPowerTerm : MPFunctionTerm
|
||||
|
||||
@property (nonatomic, strong) MPTerm *baseTerm;
|
||||
|
||||
@end
|
||||
@@ -1,31 +0,0 @@
|
||||
//
|
||||
// MPPowerTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPPowerTerm.h"
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
@implementation MPPowerTerm
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
MPEvaluateExpression(exponent, 0);
|
||||
NSDecimalNumber *base = [self.baseTerm evaluate:error];
|
||||
ReturnNilIfNil(base);
|
||||
if ([base isEqualToNumber:@(0)] && [exponent isEqualToNumber:@(0)]) {
|
||||
// The C pow function returns 1 for pow(0, 0). Mathematically this should be undefined.
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||
code:101
|
||||
userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"0 to the power of 0 is undefined.", nil)}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPProductTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPProductTerm;
|
||||
|
||||
|
||||
@interface MPProductTerm : MPTerm
|
||||
|
||||
- (instancetype)initWithFactors:(NSArray *)factors; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) NSArray *factors;
|
||||
|
||||
@end
|
||||
@@ -1,49 +0,0 @@
|
||||
//
|
||||
// MPProductTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPProductTerm.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
#import "MPPowerFunction.h"
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
#import "MPElementaryFunctionTerm.h"
|
||||
#import "MPNumber.h"
|
||||
#import "MPFactorialTerm.h"
|
||||
#import "MPPowerTerm.h"
|
||||
#import "MPVariable.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
@implementation MPProductTerm
|
||||
|
||||
- (instancetype)initWithFactors:(NSArray *)factors
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(factors != nil, @"factors must not be nil.");
|
||||
NSAssert(factors.count > 0, @"factors must not be empty.");
|
||||
_factors = factors;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDecimalNumber *value = [NSDecimalNumber one];
|
||||
for (MPTerm *factor in self.factors) {
|
||||
NSDecimalNumber *currentValue = [factor evaluate:error];
|
||||
if (!currentValue) {
|
||||
return nil;
|
||||
}
|
||||
value = [value decimalNumberByMultiplyingBy:currentValue];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,295 +0,0 @@
|
||||
//
|
||||
// MPRange.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 18.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpression.h"
|
||||
|
||||
|
||||
|
||||
/// Convenience Constructor Macro
|
||||
#define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)]
|
||||
|
||||
|
||||
@class MPRangePath, MPExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPRangePath
|
||||
@brief @c MPRangePath is like @c NSRange but with an @c NSIndexPath as
|
||||
location.
|
||||
|
||||
@discussion Range paths are intended to address a range (as defined by @c
|
||||
NSRange) that resides in a tree structure. Thus the @c location
|
||||
property of a range path is a @c NSIndexPath.
|
||||
|
||||
|
||||
@c MPRangePath instances are immutable.
|
||||
*/
|
||||
@interface MPRangePath : NSObject <NSCopying, NSCoding>
|
||||
|
||||
|
||||
#pragma mark Creation Methods
|
||||
/*!
|
||||
@methodgroup Creation Methods
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method init
|
||||
@brief Initializes an empty range path.
|
||||
|
||||
@discussion This method is a convenience initializer to create a range path with
|
||||
an empty location and a length of @c 0.
|
||||
|
||||
@return A @c MPRangePath instance.
|
||||
*/
|
||||
- (instancetype)init;
|
||||
|
||||
|
||||
/*!
|
||||
@method initWithRange:
|
||||
@brief Initializes a newly created range path with the specified range.
|
||||
|
||||
@discussion This method is a convenience initializer to convert from a @c
|
||||
NSRange to a @c MPRangePath. The location of @c aRange is
|
||||
translated into a location for the range path.
|
||||
|
||||
@param aRange
|
||||
The range to be converted into a @c MPRangePath.
|
||||
|
||||
@return A @c MPRangePath instance.
|
||||
*/
|
||||
- (instancetype)initWithRange:(NSRange)aRange;
|
||||
|
||||
|
||||
/*!
|
||||
@method initWithLocation:lenght:
|
||||
@brief Initializes a newly created range path with the specified location
|
||||
and length.
|
||||
|
||||
@discussion The last index in the @c location should address the first item
|
||||
that is actually selected.
|
||||
|
||||
@param location
|
||||
The location of the first element that should be addressed by
|
||||
this range path.
|
||||
|
||||
@param length
|
||||
The number of elements to be addressed by this range path.
|
||||
|
||||
@return A @c MPRangePath instance.
|
||||
*/
|
||||
- (instancetype)initWithLocation:(NSIndexPath *)location
|
||||
length:(NSUInteger)length; /* designated initializer */
|
||||
|
||||
|
||||
/*!
|
||||
@method rangePath
|
||||
@brief Allocates and initializes an empty @c NSRangePath instance.
|
||||
|
||||
@discussion An empty range path's location has @c 0 indexes and a length of
|
||||
@c 0.
|
||||
|
||||
@return A newly created @c MPRangePath instance.
|
||||
*/
|
||||
+ (instancetype)rangePath;
|
||||
|
||||
|
||||
/*!
|
||||
@method rangePathWithRange:
|
||||
@brief Allocates a new @c MPRangePath instance and initializes it with
|
||||
the specified location and length.
|
||||
|
||||
@discussion The location of @c aRange is translated into an index path for
|
||||
the range path.
|
||||
|
||||
@param aRange
|
||||
The range to be converted into a range path.
|
||||
|
||||
@return A newly created @c MPRangePath instance.
|
||||
*/
|
||||
+ (instancetype)rangePathWithRange:(NSRange)aRange;
|
||||
|
||||
|
||||
/*!
|
||||
@method rangePathWithLocation:length:
|
||||
@brief Allocates a new @c MPRangePath instance and initializes it with
|
||||
the specified location and length.
|
||||
|
||||
@param location
|
||||
The location of the first element that should be addressed by the
|
||||
range path.
|
||||
|
||||
@param length
|
||||
The number of elements that should be addressed by the range
|
||||
path.
|
||||
|
||||
@return A newly created @c MPRangePath instance.
|
||||
*/
|
||||
+ (instancetype)rangePathWithLocation:(NSIndexPath *)location
|
||||
length:(NSUInteger)length;
|
||||
|
||||
|
||||
#pragma mark Properties
|
||||
/*!
|
||||
@methodgroup Properties
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@property location
|
||||
@brief The location of the first element addressed by the receiver.
|
||||
*/
|
||||
@property (readonly, nonatomic, strong) NSIndexPath *location;
|
||||
|
||||
|
||||
/*!
|
||||
@property length
|
||||
@brief The number of elements addressed by the receiver. The element
|
||||
addressed by the last index of the location property plus the
|
||||
length of the receiver is NOT considered inside of the range
|
||||
path.
|
||||
*/
|
||||
@property (readonly, nonatomic) NSUInteger length;
|
||||
|
||||
|
||||
/*!
|
||||
@method maxRangePath
|
||||
@brief Similar to the @c NSRange method @c NSMaxRange this method
|
||||
returns the first index after the receiving @c NSRangePath.
|
||||
|
||||
@return The first index after the receiving @c NSRangePath.
|
||||
*/
|
||||
- (NSIndexPath *)maxRangePath;
|
||||
|
||||
|
||||
/*!
|
||||
@method rangeAtLastIndex
|
||||
@brief Returns a @c NSRange constructed from the last index of the
|
||||
receiver's location and the receiver's length.
|
||||
|
||||
@return The range identifying the elements at the last index in the
|
||||
receiver's index path.
|
||||
*/
|
||||
- (NSRange)rangeAtLastIndex;
|
||||
|
||||
|
||||
#pragma mark Working With Ranges
|
||||
/*!
|
||||
@methodgroup Working With Ranges
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
@method containsLocation:
|
||||
@brief Checks wether the range path addressed by the receiver includes
|
||||
@c location.
|
||||
|
||||
@discussion The index path is considered inside the receiver if the indexes
|
||||
in the receiver's @c location property are equal to the
|
||||
respective indexes index in @c location. For the last position of
|
||||
the last index of the receiver's @c location it is also valid if
|
||||
the respective index is inside the range at the last index.
|
||||
|
||||
@param location
|
||||
The index path to check wether it is contained in the receiver.
|
||||
|
||||
@return @c YES if the range path addressed by the receiver also contains
|
||||
@c location, @c NO otherwise.
|
||||
*/
|
||||
- (BOOL)containsLocation:(NSIndexPath *)location;
|
||||
|
||||
|
||||
/*!
|
||||
@method containsRangePath:
|
||||
@brief Checks wether the receiver completely contains @c aRangePath.
|
||||
|
||||
@discussion A range path is contained by another range path if either the
|
||||
receiver's range at its last index contains the index at the
|
||||
respective location of the location path of @c aRangePath or (if
|
||||
the locations are of the same length) the receiver's range at its
|
||||
last index contains the last index's range of @c aRangePath.
|
||||
|
||||
@param aRangePath
|
||||
The range path to check wether it is contained in the receiver.
|
||||
|
||||
@return @c YES if the range path addressed by the receiver also includes
|
||||
@c aRangePath, @c NO otherwise.
|
||||
*/
|
||||
- (BOOL)containsRangePath:(MPRangePath *)aRangePath;
|
||||
|
||||
|
||||
/*!
|
||||
@method isEqualToRangePath:
|
||||
@brief Checks wether the receiver and @c aRangePath are equal.
|
||||
|
||||
@discussion If you know that you are comparing two range paths (that are not
|
||||
@c nil) you should use this method over @c -isEqual: because it
|
||||
is more performant.
|
||||
|
||||
@param aRangePath
|
||||
The range path to check for equality. If this parameter is @c nil
|
||||
this method may raise an exception.
|
||||
|
||||
@return @c YES if the receiver is equal to @c aRangePath, @c NO
|
||||
otherwise.
|
||||
*/
|
||||
- (BOOL)isEqualToRangePath:(MPRangePath *)aRangePath;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface MPExpression (MPRangeExtension)
|
||||
|
||||
|
||||
/*!
|
||||
@method subexpressionWithRangePath:
|
||||
@brief Creates a new expression with the symbols in the specified range
|
||||
path.
|
||||
|
||||
@discussion The elements in the newly created expression are copied to the
|
||||
new exoression. The range path is specified in the length
|
||||
reference frame.
|
||||
|
||||
If the specified range exceeds the receiver's bounds a @c
|
||||
NSRangeException is raised.
|
||||
|
||||
@param aRangePath
|
||||
The range path from which to create the new expression.
|
||||
|
||||
@return A new expression.
|
||||
*/
|
||||
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame;
|
||||
|
||||
|
||||
/*!
|
||||
@method replaceItemsInRangePath:referenceFrame:withElements:
|
||||
@brief Replaces the items in the specified range path with the contents
|
||||
of @c elements.
|
||||
|
||||
@discussion All objects in the @c elements array must conform to the @c
|
||||
MPExpressionElement protocol. If one or more objects do not
|
||||
conform to the protocol a @c MPIllegalElementException will be
|
||||
raised.
|
||||
|
||||
@param rangePath
|
||||
The range path to be replaced. The path of the range path is
|
||||
expressed in the element reference frame. The range of the range
|
||||
path is expressed in the specified @c referenceFrame.
|
||||
|
||||
@param referenceFrame
|
||||
The reference frame to use. This only applies to the range at the
|
||||
last index of the range path. The path of the range path is
|
||||
always expressed in the element reference frame.
|
||||
*/
|
||||
- (void)replaceItemsInRangePath:(MPRangePath *)rangePath
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
withElements:(NSArray *)elements;
|
||||
|
||||
@end
|
||||
@@ -1,215 +0,0 @@
|
||||
//
|
||||
// MPRange.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 18.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPRangePath.h"
|
||||
|
||||
|
||||
|
||||
@implementation MPRangePath
|
||||
|
||||
|
||||
#pragma mark Creation Methods
|
||||
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithLocation:[[NSIndexPath alloc] init]
|
||||
length:0];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithLocation:(NSIndexPath *)location
|
||||
length:(NSUInteger)length
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_location = location;
|
||||
_length = length;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithRange:(NSRange)aRange
|
||||
{
|
||||
return [self initWithLocation:[[NSIndexPath alloc] initWithIndex:aRange.location]
|
||||
length:aRange.length];
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype)rangePath
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype)rangePathWithRange:(NSRange)aRange
|
||||
{
|
||||
return [[self alloc] initWithRange:aRange];
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype)rangePathWithLocation:(NSIndexPath *)location
|
||||
length:(NSUInteger)length
|
||||
{
|
||||
return [[self alloc] initWithLocation:location
|
||||
length:length];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Properties
|
||||
|
||||
|
||||
- (NSIndexPath *)maxRangePath
|
||||
{
|
||||
NSUInteger lastIndex = [self.location indexAtPosition:self.location.length-1];
|
||||
NSUInteger newLastIndex = lastIndex + self.length;
|
||||
return [[self.location indexPathByRemovingLastIndex] indexPathByAddingIndex:newLastIndex];
|
||||
}
|
||||
|
||||
|
||||
- (NSRange)rangeAtLastIndex
|
||||
{
|
||||
return NSMakeRange([self.location indexAtPosition:self.location.length-1], self.length);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Working With Ranges
|
||||
|
||||
|
||||
- (BOOL)containsLocation:(NSIndexPath *)location
|
||||
{
|
||||
return [self containsRangePath:[[MPRangePath alloc] initWithLocation:location length:0]];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)containsRangePath:(MPRangePath *)aRangePath
|
||||
{
|
||||
if (aRangePath.location.length < self.location.length) {
|
||||
return NO;
|
||||
}
|
||||
// Compare indices (except the last one)
|
||||
for (NSUInteger pos = 0; pos < self.location.length-1; pos++) {
|
||||
NSUInteger selfIndex = [self.location indexAtPosition:pos];
|
||||
NSUInteger otherIndex = [aRangePath.location indexAtPosition:pos];
|
||||
if (selfIndex != otherIndex) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
// Compare range at last index
|
||||
NSUInteger selfIndex = [self.location indexAtPosition:self.location.length-1];
|
||||
NSUInteger otherIndex = [aRangePath.location indexAtPosition:self.location.length-1];
|
||||
if (aRangePath.location.length > self.location.length) {
|
||||
return NSLocationInRange(otherIndex, self.rangeAtLastIndex);
|
||||
} else {
|
||||
return otherIndex >= selfIndex && NSMaxRange(aRangePath.rangeAtLastIndex) <= NSMaxRange(self.rangeAtLastIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
if (self == object) {
|
||||
return YES;
|
||||
}
|
||||
if (object == nil) {
|
||||
return NO;
|
||||
}
|
||||
if (![object isKindOfClass:[MPRangePath class]]) {
|
||||
return NO;
|
||||
}
|
||||
return [self isEqualToRangePath:(MPRangePath *)object];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isEqualToRangePath:(MPRangePath *)aRangePath
|
||||
{
|
||||
return [self.location isEqual:aRangePath.location] && self.length == aRangePath.length;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSMutableString *description = [[NSMutableString alloc] initWithString:@"MPRangePath<location="];
|
||||
if (self.location.length > 0) {
|
||||
[description appendFormat:@"%ld", [self.location indexAtPosition:0]];
|
||||
}
|
||||
for (NSUInteger position = 1; position < self.location.length; position ++) {
|
||||
[description appendFormat:@",%ld", [self.location indexAtPosition:position]];
|
||||
}
|
||||
[description appendFormat:@" length=%ld", self.length];
|
||||
[description appendString:@">"];
|
||||
return description.copy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
MPRangePath *copy = [[MPRangePath allocWithZone:zone] initWithLocation:self.location.copy length:self.length];
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
return [self initWithLocation:[aDecoder decodeObjectForKey:@"location"]
|
||||
length:[aDecoder decodeIntegerForKey:@"length"]];
|
||||
}
|
||||
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:self.location forKey:@"location"];
|
||||
[aCoder encodeInteger:self.length forKey:@"length"];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPExpression (MPRangeExtension)
|
||||
|
||||
- (NSArray *)itemsInRangePath:(MPRangePath *)rangePath
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
|
||||
return [targetExpression itemsInRange:rangePath.rangeAtLastIndex referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
MPExpression *targetExpression = [self elementAtIndexPath:[aRangePath.location indexPathByRemovingLastIndex]];
|
||||
if (![targetExpression isKindOfClass:[MPExpression class]]) {
|
||||
// TODO: Raise appropriate exception
|
||||
return nil;
|
||||
}
|
||||
return [targetExpression subexpressionWithRange:aRangePath.rangeAtLastIndex
|
||||
referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (void)replaceItemsInRangePath:(MPRangePath *)rangePath
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
withElements:(NSArray *)elements
|
||||
{
|
||||
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
|
||||
[targetExpression replaceItemsInRange:rangePath.rangeAtLastIndex
|
||||
referenceFrame:referenceFrame
|
||||
withElements:elements];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,73 +0,0 @@
|
||||
//
|
||||
// MPSumFunction.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 22.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
/*!
|
||||
@header
|
||||
This is a description of the MPFunction class.
|
||||
|
||||
It has multiple lines.
|
||||
*/
|
||||
|
||||
#import "MPFunction.h"
|
||||
|
||||
|
||||
|
||||
@class MPSumFunction, MPExpression;
|
||||
|
||||
|
||||
/*!
|
||||
@class MPSumFunction
|
||||
@brief This class represents a sum function (generally noted using a
|
||||
capital sigma).
|
||||
|
||||
@discussion A sum function has a start and a target expression indicating how
|
||||
often the sum expression should be evaluated. Both the value of
|
||||
the start expression and the target expressions are included in
|
||||
the iterations.
|
||||
|
||||
Each iteration the sum value is incremented by @c 1. If the start
|
||||
and target expression evaluate to the same value the sum is
|
||||
evaluated once.
|
||||
*/
|
||||
@interface MPSumFunction : MPFunction
|
||||
|
||||
|
||||
/*!
|
||||
@property startExpression
|
||||
@brief The value of the first iteration.
|
||||
|
||||
@discussion The start expression must define a variable that may be used in
|
||||
the sum expression. If the start expression does not define a
|
||||
variable the sum function will fail on validation.
|
||||
@code Some samples
|
||||
*/
|
||||
@property (nonatomic, strong) MPExpression *startExpression; /* Index 0 */
|
||||
|
||||
|
||||
/*!
|
||||
@property targetExpression
|
||||
@brief The value if the last iteration.
|
||||
|
||||
@discussion The target expression must not define a variable.
|
||||
*/
|
||||
@property (nonatomic, strong) MPExpression *targetExpression; /* Index 1 */
|
||||
|
||||
|
||||
/*!
|
||||
@property sumExpression
|
||||
@brief The sum expression evaluated multiple times.
|
||||
|
||||
@discussion During evaluation of the sum expression the variable defined in
|
||||
the start expression is available. That variable always contains
|
||||
the value of the current iteration.
|
||||
|
||||
The sum expression itself must not define a variable.
|
||||
*/
|
||||
@property (nonatomic, strong) MPExpression *sumExpression; /* Index 2 */
|
||||
|
||||
@end
|
||||
@@ -1,45 +0,0 @@
|
||||
//
|
||||
// MPSumFunction.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 22.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPSumFunction.h"
|
||||
|
||||
#import "MPSumFunctionTerm.h"
|
||||
#import "MPExpression.h"
|
||||
|
||||
|
||||
|
||||
@implementation MPSumFunction
|
||||
|
||||
|
||||
MPFunctionAccessorImplementation(StartExpression, _startExpression)
|
||||
MPFunctionAccessorImplementation(TargetExpression, _targetExpression)
|
||||
MPFunctionAccessorImplementation(SumExpression, _sumExpression)
|
||||
|
||||
|
||||
- (NSArray *)childrenAccessors
|
||||
{
|
||||
return @[@"startExpression", @"targetExpression", @"sumExpression"];
|
||||
}
|
||||
|
||||
- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return index == 0;
|
||||
}
|
||||
|
||||
- (Class)functionTermClass
|
||||
{
|
||||
return [MPSumFunctionTerm class];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"Sum(From: %@; To: %@; Using: %@)", self.startExpression, self.targetExpression, self.sumExpression];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// MPSumFunctionLayout.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 23.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionLayout.h"
|
||||
|
||||
|
||||
|
||||
@class MPSumFunctionLayout, MPSumFunction;
|
||||
|
||||
|
||||
@interface MPSumFunctionLayout : MPFunctionLayout
|
||||
|
||||
- (MPSumFunction *)sumFunction;
|
||||
|
||||
@end
|
||||
@@ -1,160 +0,0 @@
|
||||
//
|
||||
// MPSumFunctionLayout.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 23.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPSumFunctionLayout.h"
|
||||
|
||||
#import "MPSumFunction.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
#define kSumFunctionStartExpressionOffset 0
|
||||
#define kSumFunctionTargetExpressionOffset 0
|
||||
#define kSumFunctionSumExpressionOffset 3
|
||||
#define kSumFunctionTrailingOffset 5
|
||||
|
||||
@implementation MPSumFunctionLayout
|
||||
|
||||
- (MPSumFunction *)sumFunction
|
||||
{
|
||||
return (MPSumFunction *)self.function;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfLeadingChild
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfTrailingChild
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildAfterChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
if (index != 2) {
|
||||
return 2;
|
||||
}
|
||||
return [super indexOfChildAfterChildAtIndex:index];
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildBelowChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfChildAboveChildAtIndex:(NSUInteger)index
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (NSIndexSet *)indexesOfRemainingChildren
|
||||
{
|
||||
return [NSIndexSet indexSetWithIndex:2];
|
||||
}
|
||||
|
||||
- (CTLineRef)line
|
||||
{
|
||||
CTLineRef line = [self lineForPrivateCacheIndex:0 generator:^CTLineRef{
|
||||
return [self createLineForString:@"∑"
|
||||
usingFont:[NSFont fontWithName:@"Times New Roman"
|
||||
size:self.contextInferredFontSize]];
|
||||
}];
|
||||
return line;
|
||||
}
|
||||
|
||||
- (NSRect)localLineBounds
|
||||
{
|
||||
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
|
||||
NSRect startExpressionBounds = [self childLayoutAtIndex:0].bounds;
|
||||
NSRect targetExpressionBounds = [self childLayoutAtIndex:1].bounds;
|
||||
CGFloat width = MAX(MAX(startExpressionBounds.size.width, targetExpressionBounds.size.width), lineBounds.size.width);
|
||||
CGFloat xPosition = (width - lineBounds.size.width) / 2;
|
||||
return NSMakeRect(xPosition, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height);
|
||||
}
|
||||
|
||||
- (NSPoint)offsetOfChildLayoutAtIndex:(NSUInteger)index
|
||||
{
|
||||
NSRect childBounds = [self childLayoutAtIndex:index].bounds;
|
||||
NSRect localLineBounds = [self localLineBounds];
|
||||
NSPoint offset;
|
||||
if (index == 0) {
|
||||
// Start Expression
|
||||
offset.x = localLineBounds.origin.x + localLineBounds.size.width / 2 - childBounds.size.width / 2;
|
||||
offset.y = localLineBounds.origin.y - kSumFunctionStartExpressionOffset - childBounds.size.height - childBounds.origin.y;
|
||||
} else if (index == 1) {
|
||||
// Target Expression
|
||||
offset.x = localLineBounds.origin.x + localLineBounds.size.width / 2 - childBounds.size.width / 2;
|
||||
offset.y = kSumFunctionTargetExpressionOffset + localLineBounds.size.height + localLineBounds.origin.y - childBounds.origin.y;
|
||||
} else {
|
||||
// Sum Expression
|
||||
MPLayout *startExpressionLayout = [self childLayoutAtIndex:0];
|
||||
MPLayout *targetExpressionLayout = [self childLayoutAtIndex:1];
|
||||
CGFloat sumWidth = MAX(MAX(localLineBounds.origin.x + localLineBounds.size.width, startExpressionLayout.bounds.size.width), targetExpressionLayout.bounds.size.width);
|
||||
offset.x = sumWidth + kSumFunctionSumExpressionOffset;
|
||||
offset.y = 0;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
- (BOOL)childAtIndexUsesSmallSize:(NSUInteger)index
|
||||
{
|
||||
return (index == 0 || index == 1) ? YES : self.usesSmallSize;
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathForLocalMousePoint:(NSPoint)point
|
||||
{
|
||||
if (point.x < CTLineGetBoundsWithOptions(self.line, 0).size.width / 2) {
|
||||
return [NSIndexPath indexPathWithIndex:0];
|
||||
} else {
|
||||
return [NSIndexPath indexPathWithIndex:1];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSRect)generateBounds
|
||||
{
|
||||
NSRect lineBounds = CTLineGetBoundsWithOptions(self.line, 0);
|
||||
NSRect startExpressionBounds = [self childLayoutAtIndex:0].bounds;
|
||||
NSRect targetExpressionBounds = [self childLayoutAtIndex:1].bounds;
|
||||
NSRect sumExpressionBounds = [self childLayoutAtIndex:2].bounds;
|
||||
NSRect bounds = lineBounds;
|
||||
|
||||
bounds.size.width = MAX(lineBounds.size.width, startExpressionBounds.size.width);
|
||||
bounds.size.height += startExpressionBounds.size.height + kSumFunctionStartExpressionOffset;
|
||||
bounds.origin.y -= startExpressionBounds.size.height + kSumFunctionStartExpressionOffset;
|
||||
|
||||
bounds.size.width = MAX(bounds.size.width, targetExpressionBounds.size.width);
|
||||
bounds.size.height += targetExpressionBounds.size.height + kSumFunctionTargetExpressionOffset;
|
||||
|
||||
bounds.size.width += kSumFunctionSumExpressionOffset + sumExpressionBounds.size.width + kSumFunctionTrailingOffset;
|
||||
CGFloat yOffsetBottom = bounds.origin.y - sumExpressionBounds.origin.y;
|
||||
CGFloat yOffsetTop = sumExpressionBounds.size.height - (yOffsetBottom + bounds.size.height);
|
||||
bounds.size.height = MAX(yOffsetBottom, 0) + bounds.size.height + MAX(yOffsetTop, 0);
|
||||
bounds.origin.y = MIN(sumExpressionBounds.origin.y, bounds.origin.y);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
- (void)draw
|
||||
{
|
||||
// Get the current context
|
||||
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
// Set the text matrix
|
||||
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
|
||||
|
||||
// Draw the sum symbol
|
||||
CTLineRef line = [self line];
|
||||
CFRetain(line);
|
||||
NSRect localLineBounds = [self localLineBounds];
|
||||
CGContextSetTextPosition(context, localLineBounds.origin.x, 0);
|
||||
CTLineDraw(line, context);
|
||||
|
||||
CFRelease(line);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// MPSumFunctionTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPFunctionTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPSumFunctionTerm;
|
||||
|
||||
|
||||
@interface MPSumFunctionTerm : MPFunctionTerm
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// MPSumFunctionTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 15.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPSumFunctionTerm.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
@implementation MPSumFunctionTerm
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
MPParsedExpression *startExpression = [self expressionAtIndex:0];
|
||||
MPParsedExpression *sumExpression = [self expressionAtIndex:2];
|
||||
|
||||
NSDecimalNumber *start = [startExpression evaluate:error];
|
||||
ReturnNilIfNil(start);
|
||||
MPEvaluateExpression(target, 1);
|
||||
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||
|
||||
for (NSDecimalNumber *current = start;
|
||||
[current compare:target] <= 0;
|
||||
current = [current decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) {
|
||||
if (![self defineVariable:startExpression.definedVariable value:current error:error]) {
|
||||
return nil;
|
||||
}
|
||||
NSDecimalNumber *currentValue = [sumExpression evaluate:error];
|
||||
ReturnNilIfNil(currentValue);
|
||||
value = [value decimalNumberByAdding:currentValue];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// MPSumTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 14.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPSumTerm;
|
||||
|
||||
|
||||
@interface MPSumTerm : MPTerm
|
||||
|
||||
- (instancetype)initWithSummands:(NSArray *)summands; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) NSArray *summands;
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
//
|
||||
// MPSumTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 14.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPSumTerm.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
#import "MPProductTerm.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
@implementation MPSumTerm
|
||||
|
||||
- (instancetype)initWithSummands:(NSArray *)summands
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(summands != nil, @"summands must not be nil.");
|
||||
NSAssert(summands.count > 0, @"summands must not be empty.");
|
||||
_summands = summands;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDecimalNumber *value = [NSDecimalNumber zero];
|
||||
for (MPTerm *summand in self.summands) {
|
||||
NSDecimalNumber *currentValue = [summand evaluate:error];
|
||||
if (!currentValue) {
|
||||
return nil;
|
||||
}
|
||||
value = [value decimalNumberByAdding:currentValue];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// MPTerm.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#define ReturnIfNil(test, value) if (test == nil) return value
|
||||
#define ReturnNilIfNil(test) if (test == nil) return nil
|
||||
|
||||
|
||||
@class MPTerm;
|
||||
|
||||
|
||||
@interface MPTerm : NSObject
|
||||
|
||||
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error;
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error;
|
||||
|
||||
- (BOOL)defineVariable:(NSString *)variableName
|
||||
value:(NSDecimalNumber *)value
|
||||
error:(NSError *__autoreleasing *)error;
|
||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
|
||||
error:(NSError *__autoreleasing *)error;
|
||||
- (void)undefineVariable:(NSString *)variableName; // Should rarely be needed, defined variables are automatically undefined at the end of evaluation.
|
||||
|
||||
@end
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// MPTerm.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 11.11.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
#import "MPSumTerm.h"
|
||||
#import "MPEvaluationContext.h"
|
||||
|
||||
@implementation MPTerm
|
||||
|
||||
- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error
|
||||
{
|
||||
[[MPEvaluationContext sharedContext] push];
|
||||
NSDecimalNumber *result = [self doEvaluation:error];
|
||||
[[MPEvaluationContext sharedContext] pop];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSLog(@"%@", self.class);
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)defineVariable:(NSString *)variableName
|
||||
value:(NSDecimalNumber *)value
|
||||
error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
BOOL couldDefineVariable = [[MPEvaluationContext sharedContext] defineVariable:variableName
|
||||
value:value];
|
||||
if (!couldDefineVariable) {
|
||||
if (error) {
|
||||
NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Redifinition of variable \"%@\".", nil), variableName];
|
||||
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||
code:100
|
||||
userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
|
||||
}
|
||||
}
|
||||
return couldDefineVariable;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)valueForVariable:(NSString *)variableName
|
||||
error:(NSError *__autoreleasing *)error
|
||||
{
|
||||
NSDecimalNumber *value = [[MPEvaluationContext sharedContext] valueForVariable:variableName];
|
||||
if (!value) {
|
||||
if (error) {
|
||||
NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Undefined variable \"%@\".", nil), variableName];
|
||||
*error = [NSError errorWithDomain:MPMathKitErrorDomain
|
||||
code:101
|
||||
userInfo:@{NSLocalizedDescriptionKey: localizedDescription}];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)undefineVariable:(NSString *)variableName
|
||||
{
|
||||
[[MPEvaluationContext sharedContext] undefineVariable:variableName];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,160 +0,0 @@
|
||||
//
|
||||
// MPToken.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 19.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@class MPToken;
|
||||
@protocol MPToken;
|
||||
|
||||
|
||||
/*!
|
||||
@typedef MPTokenType
|
||||
@brief The type of a <code>token</code> identifies its behaviour in a mathematical
|
||||
context.
|
||||
|
||||
@constant MPEOFToken
|
||||
A token that represents the end of the input. This token should
|
||||
not be used to create a @c MPToken instance. It exists so that
|
||||
you can safely send a @c tokenType message to @c nil without
|
||||
getting confusing results. A token with this token type should be
|
||||
interpreted as 'no token'.
|
||||
|
||||
@constant MPMultiplicationSymbolToken
|
||||
A multiplication symbol.
|
||||
|
||||
@constant MPOperatorListToken
|
||||
A list of operators (+ and - symbols). The token may be longer
|
||||
than the number of operators if there are spaces between them.
|
||||
|
||||
@constant MPElementaryFunction
|
||||
@em Most elementary functions are represented by this token type.
|
||||
Elementary functions not categorized as such are those that can
|
||||
not be represented as text (e.g. roots and powers).
|
||||
|
||||
@constant MPNumberToken
|
||||
A number. This may be an integer or a floating point number.
|
||||
Floating point numbers contain a @c NSLocaleDecimalSeparator.
|
||||
|
||||
@constant MPVariableToken
|
||||
A variable. A variable is exactly one character long.
|
||||
|
||||
@constant MPFactorialToken
|
||||
The factorial symbol (!).
|
||||
|
||||
@constant MPEqualsToken
|
||||
The equals sign.
|
||||
|
||||
@constant MPGenericFunctionToken
|
||||
A function represented by the @c MPFunction class. A token with
|
||||
this token type is guaranteed to be a @c MPFunction instance.
|
||||
|
||||
@constant MPWhitespaceToken
|
||||
A whitespace. This token can typically be ignored.
|
||||
|
||||
@constant MPUnidentifiedToken
|
||||
Any symbol that does not match any other token.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MPTokenType) {
|
||||
MPEOFToken = 0,
|
||||
MPMultiplicationSymbolToken,
|
||||
MPOperatorListToken,
|
||||
MPElementaryFunctionToken,
|
||||
MPNumberToken,
|
||||
MPDeformedNumberToken,
|
||||
MPVariableToken,
|
||||
MPEqualsToken,
|
||||
MPFactorialToken,
|
||||
MPPowerToken,
|
||||
MPGenericFunctionToken,
|
||||
|
||||
MPWhitespaceToken,
|
||||
MPUnidentifiedToken,
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@protocol MPToken
|
||||
@brief Tokens represent logical units in an expresion.
|
||||
*/
|
||||
@protocol MPToken <NSObject>
|
||||
|
||||
|
||||
/*!
|
||||
@method tokenType
|
||||
@brief Returns the receiver's token type.
|
||||
|
||||
@discussion The token type identifies what kind of token the receiver is. For
|
||||
more information see the documentation on the <code>MPTokenType
|
||||
</code> enum.
|
||||
|
||||
@return The receiver's token type.
|
||||
*/
|
||||
- (MPTokenType)tokenType;
|
||||
|
||||
|
||||
/*!
|
||||
@method range
|
||||
@brief Returns the receiver's range.
|
||||
|
||||
@discussion The range identifies where the token is in the expression. It is
|
||||
specified in the symbol reference frame.
|
||||
|
||||
@return The range the token occupies in the expression it was parsed
|
||||
from specified in the symbol reference frame.
|
||||
*/
|
||||
- (NSRange)range;
|
||||
|
||||
|
||||
/*!
|
||||
@method stringValue
|
||||
@brief The string that caused the token to be parsed.
|
||||
|
||||
@discussion Depending on the type of the token the string value can have a
|
||||
fixed or variable length. For example the equals token always has
|
||||
a length of @c 1 whereas a number or whitespace token can be much
|
||||
longer.
|
||||
|
||||
@return The receiver's string value.
|
||||
*/
|
||||
- (NSString *)stringValue;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@class MPToken
|
||||
@brief The @c MPToken class implements the functionality of the @c
|
||||
MPToken protocol. Most tokens are instances of the @c MPToken
|
||||
class.
|
||||
*/
|
||||
@interface MPToken : NSObject <MPToken>
|
||||
|
||||
|
||||
/*!
|
||||
@method initWithTokenType:range:stringValue:
|
||||
@brief Creates a new @c MPToken instance.
|
||||
|
||||
@param tokenType
|
||||
The type of the token.
|
||||
|
||||
@param range
|
||||
The range of the token in the expression. Specified in the symbol
|
||||
reference frame.
|
||||
|
||||
@param input
|
||||
The string value of the token.
|
||||
|
||||
@return A newly initialized token.
|
||||
*/
|
||||
- (instancetype)initWithTokenType:(MPTokenType)tokenType
|
||||
range:(NSRange)range
|
||||
stringValue:(NSString *)input; /* designated initializer */
|
||||
|
||||
@end
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// MPToken.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 19.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
|
||||
|
||||
@interface MPToken ()
|
||||
|
||||
@property (readonly, nonatomic) NSRange range;
|
||||
@property (readonly, nonatomic, strong) NSString *stringValue;
|
||||
@property (readonly, nonatomic) MPTokenType tokenType;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPToken
|
||||
|
||||
- (instancetype)initWithTokenType:(MPTokenType)tokenType
|
||||
range:(NSRange)range
|
||||
stringValue:(NSString *)input
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_range = range;
|
||||
_stringValue = input.copy;
|
||||
_tokenType = tokenType;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return self.stringValue;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPVariable.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPTerm.h"
|
||||
|
||||
|
||||
|
||||
@class MPVariable;
|
||||
|
||||
|
||||
@interface MPVariable : MPTerm
|
||||
|
||||
- (instancetype)initWithVariableName:(NSString *)variableName; /* designated initializer */
|
||||
|
||||
@property (readonly, nonatomic, strong) NSString *variableName;
|
||||
|
||||
@end
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// MPVariable.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.10.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPVariable.h"
|
||||
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "MPToken.h"
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPEvaluationContext.h"
|
||||
|
||||
@implementation MPVariable
|
||||
|
||||
- (instancetype)initWithVariableName:(NSString *)variableName
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSAssert(variableName != nil, @"variableName must not be nil.");
|
||||
_variableName = variableName;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error
|
||||
{
|
||||
return [self valueForVariable:self.variableName
|
||||
error:error];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,16 +0,0 @@
|
||||
//
|
||||
// MPWhiteView.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 28.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@class MPWhiteView;
|
||||
|
||||
|
||||
@interface MPWhiteView : NSView
|
||||
|
||||
@end
|
||||
@@ -1,22 +0,0 @@
|
||||
//
|
||||
// MPWhiteView.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 28.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPWhiteView.h"
|
||||
|
||||
|
||||
|
||||
@implementation MPWhiteView
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect {
|
||||
[super drawRect:dirtyRect];
|
||||
|
||||
[[NSColor whiteColor] set];
|
||||
NSRectFill(dirtyRect);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,163 +0,0 @@
|
||||
//
|
||||
// NSIndexPath+MPRemoveFirstIndex.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 23.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@interface NSIndexPath (MPAdditions)
|
||||
|
||||
|
||||
/*!
|
||||
@property firstIndex
|
||||
@brief The first index from the index path.
|
||||
|
||||
@discussion If the index path is empty @c NSNotFound is returned.
|
||||
*/
|
||||
@property (readonly, nonatomic) NSUInteger firstIndex;
|
||||
|
||||
|
||||
/*!
|
||||
@property lastIndex
|
||||
@brief The last index from the index path.
|
||||
|
||||
@discussion If the index path is empty @c NSNotFound is returned.
|
||||
*/
|
||||
@property (readonly, nonatomic) NSUInteger lastIndex;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPathByReplacingLastIndexWithIndex:
|
||||
@brief Provides an index path with the index in the receiving index path
|
||||
where the last one is replaced by @c index.
|
||||
|
||||
@discussion If the receiving index path is empty an index path of length @c 1
|
||||
is returned. The last index in the returned index path is @c
|
||||
index.
|
||||
|
||||
@param index
|
||||
The index with which to replace the last index in the receiving
|
||||
index path.
|
||||
|
||||
@return A new index path with @c index as its last index.
|
||||
*/
|
||||
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPathByRemovingFirstIndex
|
||||
@brief Provides an index path with the indexes in the receiving index
|
||||
path, excluding the first one.
|
||||
|
||||
@discussion Returns an empty NSIndexPath instance if the receiving index
|
||||
path’s length is 1 or less.
|
||||
|
||||
@return A new index path with the receiving index path’s indexes,
|
||||
excluding the first one.
|
||||
*/
|
||||
- (NSIndexPath *)indexPathByRemovingFirstIndex;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPathByPreceedingIndex:
|
||||
@brief Provides an index path with the specified index followed by the
|
||||
indexes of the receiver.
|
||||
|
||||
@discussion If the receiver does not contain any indexes the specified index
|
||||
is the only index contained in the returned index path.
|
||||
|
||||
@param index
|
||||
The index new index preceeding all others
|
||||
|
||||
@return A new index path with all the receiver's indexes preceeded by @c
|
||||
index.
|
||||
*/
|
||||
- (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPathByIncrementingLastIndex
|
||||
@brief Provides an index path with the indexes in the receiving index
|
||||
path where the last one is incremented by @c 1.
|
||||
|
||||
@discussion If the receiver does not contain any indexes an empty index path
|
||||
is returned.
|
||||
|
||||
@return A new index path with all the receiver's indexes and the last one
|
||||
incremented by @c 1.
|
||||
*/
|
||||
- (NSIndexPath *)indexPathByIncrementingLastIndex;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPathByDecrementingLastIndex
|
||||
@brief Provides an index path with the indexes in the receiving index
|
||||
path where the last one is decremented by @c 1.
|
||||
|
||||
@discussion If the receiver does not contain any indexes an empty index path
|
||||
is returned.
|
||||
|
||||
@return A new index path with all the receiver's indexes and the last one
|
||||
decremented by @c 1.
|
||||
*/
|
||||
- (NSIndexPath *)indexPathByDecrementingLastIndex;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPathByRemovingIndexesFrom:
|
||||
@brief Provides an index path with the indexes in the recieving index
|
||||
path up to the index at the specified position.
|
||||
|
||||
@discussion If @c from is greater or equal to the number of indexes in the
|
||||
receiving index path only the indexes to the end of the receiver
|
||||
are removed.
|
||||
|
||||
@param from
|
||||
The position of the first index to be excluded in the returned
|
||||
index path.
|
||||
|
||||
@return An index path with all indexes from the receiver up to position
|
||||
@c from.
|
||||
*/
|
||||
- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)from;
|
||||
|
||||
|
||||
/*!
|
||||
@method indexPathByRemovingIndexesTo:
|
||||
@brief Provides an index path with the indexes in the receiving index
|
||||
path where the first indexes are removed.
|
||||
|
||||
|
||||
@discussion @c to specifies the number of indexes to be removed from the
|
||||
front. Thus the index at position @c to will be included in the
|
||||
returned index path.
|
||||
|
||||
@param to
|
||||
The number of indexes to remove from the front.
|
||||
|
||||
@return A new index path with all the receiver's indexes exept the first
|
||||
@c to ones.
|
||||
*/
|
||||
- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)to;
|
||||
|
||||
|
||||
/*!
|
||||
@method commonIndexPathWith:
|
||||
@brief Provides an index path that contains the first indexes of the
|
||||
receiver that are equal to the specified index path.
|
||||
|
||||
@discussion If one index path is completely included in the other a new index
|
||||
path is returned that is equal to the contained index path.
|
||||
|
||||
@param indexPath
|
||||
The index path to compare the receiver against.
|
||||
|
||||
@return A new index path with the first indexes of the receiver that are
|
||||
also present in @c indexPath.
|
||||
*/
|
||||
- (NSIndexPath *)commonIndexPathWith:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
@@ -1,117 +0,0 @@
|
||||
//
|
||||
// NSIndexPath+MPRemoveFirstIndex.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 23.04.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
|
||||
|
||||
@implementation NSIndexPath (MPAdditions)
|
||||
|
||||
|
||||
- (NSUInteger)firstIndex
|
||||
{
|
||||
return [self indexAtPosition:0];
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)lastIndex
|
||||
{
|
||||
return [self indexAtPosition:self.length-1];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index
|
||||
{
|
||||
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:index];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPathByRemovingFirstIndex
|
||||
{
|
||||
if (self.length <= 1) {
|
||||
return [[NSIndexPath alloc] init];
|
||||
}
|
||||
NSUInteger indexes[self.length];
|
||||
[self getIndexes:indexes];
|
||||
NSUInteger newIndexes[self.length-1];
|
||||
for (NSUInteger i = 0; i < self.length-1; i++) {
|
||||
newIndexes[i] = indexes[i+1];
|
||||
}
|
||||
return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length-1];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index
|
||||
{
|
||||
NSUInteger newIndexes[self.length+1];
|
||||
newIndexes[0] = index;
|
||||
for (NSUInteger i = 0; i < self.length; i++) {
|
||||
newIndexes[i+1] = [self indexAtPosition:i];
|
||||
}
|
||||
return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length+1];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPathByIncrementingLastIndex
|
||||
{
|
||||
if (self.length < 1) {
|
||||
return [[NSIndexPath alloc] init];
|
||||
}
|
||||
NSUInteger lastIndex = [self lastIndex];
|
||||
lastIndex++;
|
||||
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex];
|
||||
}
|
||||
|
||||
- (NSIndexPath *)indexPathByDecrementingLastIndex
|
||||
{
|
||||
if (self.length < 1) {
|
||||
return [[NSIndexPath alloc] init];
|
||||
}
|
||||
NSUInteger lastIndex = [self lastIndex];
|
||||
lastIndex--;
|
||||
return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)from
|
||||
{
|
||||
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
|
||||
for (NSUInteger position = 0; position < MIN(from, self.length); position++) {
|
||||
indexPath = [indexPath indexPathByAddingIndex:[self indexAtPosition:position]];
|
||||
}
|
||||
return indexPath;
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)to
|
||||
{
|
||||
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
|
||||
for (NSUInteger position = to; position < self.length; position++) {
|
||||
indexPath = [indexPath indexPathByAddingIndex:[self indexAtPosition:position]];
|
||||
}
|
||||
return indexPath;
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)commonIndexPathWith:(NSIndexPath *)indexPath
|
||||
{
|
||||
NSIndexPath *commonPath = [[NSIndexPath alloc] init];
|
||||
NSUInteger length = MIN(self.length, indexPath.length);
|
||||
for (NSUInteger position = 0; position < length; position++) {
|
||||
NSUInteger selfIndex = [self indexAtPosition:position];
|
||||
NSUInteger otherIndex = [indexPath indexAtPosition:position];
|
||||
if (selfIndex == otherIndex) {
|
||||
commonPath = [commonPath indexPathByAddingIndex:selfIndex];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return commonPath;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,107 +0,0 @@
|
||||
//
|
||||
// NSRegularExpression+MPParsingAdditions.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
@interface NSRegularExpression (MPParsingAdditions)
|
||||
|
||||
|
||||
/*!
|
||||
@method firstMathInString:
|
||||
@brief Returns the first match of the regular expression within the
|
||||
specified string.
|
||||
|
||||
@discussion This is a convenience method that calls @c
|
||||
-firstMatchInString:options:range:
|
||||
|
||||
@param string
|
||||
The string to search.
|
||||
|
||||
@return An NSTextCheckingResult object. This result gives the overall
|
||||
matched range via its range property, and the range of each
|
||||
individual capture group via its rangeAtIndex: method. The range
|
||||
@c {NSNotFound, 0} is returned if one of the capture groups did
|
||||
not participate in this particular match.
|
||||
*/
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string;
|
||||
|
||||
|
||||
/*!
|
||||
@method firstMathInString:fromIndex
|
||||
@brief Returns the first match of the regular expression from the
|
||||
specified index in the string.
|
||||
|
||||
@discussion This is a convenience method that calls @c
|
||||
-firstMatchInString:options:range:
|
||||
|
||||
@param string
|
||||
The string to search.
|
||||
|
||||
@param start
|
||||
The index from which to start searching.
|
||||
|
||||
@return An NSTextCheckingResult object. This result gives the overall
|
||||
matched range via its range property, and the range of each
|
||||
individual capture group via its rangeAtIndex: method. The range
|
||||
@c {NSNotFound, 0} is returned if one of the capture groups did
|
||||
not participate in this particular match.
|
||||
*/
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string fromIndex:(NSUInteger)start;
|
||||
|
||||
|
||||
/*!
|
||||
@method firstMathInString:options:
|
||||
@brief Returns the first match of the regular expression within the
|
||||
specified string.
|
||||
|
||||
@discussion This is a convenience method that calls @c
|
||||
-firstMatchInString:options:range:
|
||||
|
||||
@param string
|
||||
The string to search.
|
||||
|
||||
@param options
|
||||
The matching options to use. See @c NSMatchingOptions for
|
||||
possible values.
|
||||
|
||||
@return An NSTextCheckingResult object. This result gives the overall
|
||||
matched range via its range property, and the range of each
|
||||
individual capture group via its rangeAtIndex: method. The range
|
||||
@c {NSNotFound, 0} is returned if one of the capture groups did
|
||||
not participate in this particular match.
|
||||
*/
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options;
|
||||
|
||||
|
||||
/*!
|
||||
@method firstMathInString:fromIndex
|
||||
@brief Returns the first match of the regular expression from the
|
||||
specified index in the string.
|
||||
|
||||
@discussion This is a convenience method that calls @c
|
||||
-firstMatchInString:options:range:
|
||||
|
||||
@param string
|
||||
The string to search.
|
||||
|
||||
@param options
|
||||
The matching options to use. See @c NSMatchingOptions for
|
||||
possible values.
|
||||
|
||||
@param start
|
||||
The index from which to start searching.
|
||||
|
||||
@return An NSTextCheckingResult object. This result gives the overall
|
||||
matched range via its range property, and the range of each
|
||||
individual capture group via its rangeAtIndex: method. The range
|
||||
@c {NSNotFound, 0} is returned if one of the capture groups did
|
||||
not participate in this particular match.
|
||||
*/
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options fromIndex:(NSUInteger)start;
|
||||
|
||||
@end
|
||||
@@ -1,38 +0,0 @@
|
||||
//
|
||||
// NSRegularExpression+MPParsingAdditions.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 09.09.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSRegularExpression+MPParsingAdditions.h"
|
||||
|
||||
|
||||
|
||||
@implementation NSRegularExpression (MPParsingAdditions)
|
||||
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string
|
||||
{
|
||||
return [self firstMatchInString:string options:0 range:NSMakeRange(0, string.length)];
|
||||
}
|
||||
|
||||
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string fromIndex:(NSUInteger)start
|
||||
{
|
||||
return [self firstMatchInString:string options:0 range:NSMakeRange(start, [string length]-start)];
|
||||
}
|
||||
|
||||
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options
|
||||
{
|
||||
return [self firstMatchInString:string options:options range:NSMakeRange(0, string.length)];
|
||||
}
|
||||
|
||||
|
||||
- (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options fromIndex:(NSUInteger)start
|
||||
{
|
||||
return [self firstMatchInString:string options:options range:NSMakeRange(start, [string length]-start)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// NSString+MPExpressionElement.h
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 10.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpressionElement.h"
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
@category NSString (MPExpressionElement)
|
||||
@brief This category adds @c MPExpressionElement protocol conformance to
|
||||
the @c NSString class.
|
||||
*/
|
||||
@interface NSString (MPExpressionElement) <MPExpressionElement>
|
||||
|
||||
@end
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// NSString+MPExpressionElement.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 10.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSString+MPExpressionElement.h"
|
||||
|
||||
|
||||
|
||||
@implementation NSString (MPExpressionElement)
|
||||
|
||||
- (BOOL)isString
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isFunction
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user