diff --git a/MathPad.xcodeproj/project.pbxproj b/MathPad.xcodeproj/project.pbxproj index 216020d..152396c 100644 --- a/MathPad.xcodeproj/project.pbxproj +++ b/MathPad.xcodeproj/project.pbxproj @@ -14,6 +14,10 @@ 3B52CEDD19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B52CEDB19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.m */; }; 3B591BBB19C58D000061D86B /* MPMathRules.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B591BB919C58D000061D86B /* MPMathRules.h */; }; 3B591BBC19C58D000061D86B /* MPMathRules.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B591BBA19C58D000061D86B /* MPMathRules.m */; }; + 3B5FF73B19DB2FF500C8348A /* MPPowerFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B5FF73919DB2FF500C8348A /* MPPowerFunction.h */; }; + 3B5FF73C19DB2FF500C8348A /* MPPowerFunction.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B5FF73A19DB2FF500C8348A /* MPPowerFunction.m */; }; + 3B69B66C19DB41B90028E608 /* MPPowerFunctionLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B69B66A19DB41B90028E608 /* MPPowerFunctionLayout.h */; }; + 3B69B66D19DB41B90028E608 /* MPPowerFunctionLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B69B66B19DB41B90028E608 /* MPPowerFunctionLayout.m */; }; 3B7172EA19C7147000FEAA5B /* FunctionsButtonDisclosure@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3B7172E819C7147000FEAA5B /* FunctionsButtonDisclosure@2x.png */; }; 3B7172EB19C7147000FEAA5B /* FunctionsButtonDisclosure.png in Resources */ = {isa = PBXBuildFile; fileRef = 3B7172E919C7147000FEAA5B /* FunctionsButtonDisclosure.png */; }; 3B7172EE19C9FA8E00FEAA5B /* MPParenthesisFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B7172EC19C9FA8E00FEAA5B /* MPParenthesisFunction.h */; }; @@ -143,7 +147,11 @@ 3B52CEDB19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSRegularExpression+MPParsingAdditions.m"; sourceTree = ""; }; 3B591BB919C58D000061D86B /* MPMathRules.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPMathRules.h; sourceTree = ""; }; 3B591BBA19C58D000061D86B /* MPMathRules.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPMathRules.m; sourceTree = ""; }; + 3B5FF73919DB2FF500C8348A /* MPPowerFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPowerFunction.h; sourceTree = ""; }; + 3B5FF73A19DB2FF500C8348A /* MPPowerFunction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPowerFunction.m; sourceTree = ""; }; 3B688D9819982DF50006B4AB /* MPLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLayout.m; sourceTree = ""; }; + 3B69B66A19DB41B90028E608 /* MPPowerFunctionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPowerFunctionLayout.h; sourceTree = ""; }; + 3B69B66B19DB41B90028E608 /* MPPowerFunctionLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPPowerFunctionLayout.m; sourceTree = ""; }; 3B7172E819C7147000FEAA5B /* FunctionsButtonDisclosure@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "FunctionsButtonDisclosure@2x.png"; sourceTree = ""; }; 3B7172E919C7147000FEAA5B /* FunctionsButtonDisclosure.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = FunctionsButtonDisclosure.png; sourceTree = ""; }; 3B7172EC19C9FA8E00FEAA5B /* MPParenthesisFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPParenthesisFunction.h; sourceTree = ""; }; @@ -370,6 +378,8 @@ 3BB09EC81906FD830080A5ED /* MPSumFunction.m */, 3B7172EC19C9FA8E00FEAA5B /* MPParenthesisFunction.h */, 3B7172ED19C9FA8E00FEAA5B /* MPParenthesisFunction.m */, + 3B5FF73919DB2FF500C8348A /* MPPowerFunction.h */, + 3B5FF73A19DB2FF500C8348A /* MPPowerFunction.m */, ); name = Functions; sourceTree = ""; @@ -401,6 +411,8 @@ 3BB09EE0190736160080A5ED /* MPSumFunctionLayout.m */, 3B7172F019C9FC6700FEAA5B /* MPParenthesisFunctionLayout.h */, 3B7172F119C9FC6700FEAA5B /* MPParenthesisFunctionLayout.m */, + 3B69B66A19DB41B90028E608 /* MPPowerFunctionLayout.h */, + 3B69B66B19DB41B90028E608 /* MPPowerFunctionLayout.m */, ); name = "Function Layouts"; sourceTree = ""; @@ -571,10 +583,12 @@ 3B7172F219C9FC6700FEAA5B /* MPParenthesisFunctionLayout.h in Headers */, 3B52CEDC19BEE63000CEDCFC /* NSRegularExpression+MPParsingAdditions.h in Headers */, 3B85833A19BB63D400D76A8D /* MPFunction.h in Headers */, + 3B69B66C19DB41B90028E608 /* MPPowerFunctionLayout.h in Headers */, 3B85834319BB653700D76A8D /* MPSumFunction.h in Headers */, 3B85834119BB651E00D76A8D /* MPRangePath.h in Headers */, 3B85834219BB652900D76A8D /* MPException.h in Headers */, 3B85834519BB655200D76A8D /* NSIndexPath+MPAdditions.h in Headers */, + 3B5FF73B19DB2FF500C8348A /* MPPowerFunction.h in Headers */, 3B52CED019BE509C00CEDCFC /* MPParseError.h in Headers */, 3B7172EE19C9FA8E00FEAA5B /* MPParenthesisFunction.h in Headers */, 3B85834619BB655C00D76A8D /* MPExpressionView.h in Headers */, @@ -758,6 +772,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3B5FF73C19DB2FF500C8348A /* MPPowerFunction.m in Sources */, 3BBEA94219BB79A700133766 /* MPExpressionLayout.m in Sources */, 3B7B3A1919CC44E4005849E5 /* MPExpressionTokenizer.m in Sources */, 3B591BBC19C58D000061D86B /* MPMathRules.m in Sources */, @@ -781,6 +796,7 @@ 3BF59AFF19D80ECC00E54292 /* MPFunctionsViewController.m in Sources */, 3B52CED119BE509C00CEDCFC /* MPParseError.m in Sources */, 3BB18AA619CDB3A900986DA0 /* MPTokenStream.m in Sources */, + 3B69B66D19DB41B90028E608 /* MPPowerFunctionLayout.m in Sources */, 3BBEA93619BB79A700133766 /* MPFunction.m in Sources */, 3BBEA93519BB79A700133766 /* MPExpression.m in Sources */, 3BBEA93B19BB79A700133766 /* MPRangePath.m in Sources */, diff --git a/MathPad/Base.lproj/MPDocument.xib b/MathPad/Base.lproj/MPDocument.xib index 1627610..7168019 100644 --- a/MathPad/Base.lproj/MPDocument.xib +++ b/MathPad/Base.lproj/MPDocument.xib @@ -6,9 +6,8 @@ - - + @@ -16,75 +15,41 @@ - + - + - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + diff --git a/MathPad/MPDocument.h b/MathPad/MPDocument.h index 44e2134..425a88a 100644 --- a/MathPad/MPDocument.h +++ b/MathPad/MPDocument.h @@ -12,7 +12,6 @@ @property (weak) IBOutlet MPExpressionView *expressionView; @property (weak) IBOutlet NSTextField *resultLabel; -@property (weak) IBOutlet NSTextField *errorLabel; - (IBAction)evaluateExpression:(id)sender; diff --git a/MathPad/MPDocument.m b/MathPad/MPDocument.m index 6b35a30..dea08b0 100644 --- a/MathPad/MPDocument.m +++ b/MathPad/MPDocument.m @@ -62,11 +62,8 @@ - (IBAction)evaluateExpression:(id)sender { MPParseError *error; NSDecimalNumber *result = [self.expressionView.expressionStorage evaluateWithError:&error]; - self.resultLabel.stringValue = result != nil ? result.description : @"Error!"; - self.errorLabel.stringValue = error != nil ? [NSString stringWithFormat:@"%@, %@", error, error.pathToExpression] : @"No Error"; - if (error) { - self.expressionView.error = error; - } + self.expressionView.error = error; + self.resultLabel.stringValue = result != nil ? [result descriptionWithLocale:[NSLocale currentLocale]] : @""; } @end diff --git a/MathPad/MPEvaluationContext.h b/MathPad/MPEvaluationContext.h index f5acfb2..2fb4e91 100644 --- a/MathPad/MPEvaluationContext.h +++ b/MathPad/MPEvaluationContext.h @@ -15,7 +15,7 @@ - (void)push; - (void)pop; -- (void)defineVariable:(NSString *)variable withValue:(id)value; +- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value; - (void)undefineVariable:(NSString *)variable; - (BOOL)isVariableDefined:(NSString *)variable; diff --git a/MathPad/MPEvaluationContext.m b/MathPad/MPEvaluationContext.m index 38b9a31..73ce3aa 100644 --- a/MathPad/MPEvaluationContext.m +++ b/MathPad/MPEvaluationContext.m @@ -31,6 +31,9 @@ static MPEvaluationContext *sharedContext; self = [super init]; if (self) { _stack = [[NSMutableArray alloc] init]; + [self push]; + [self defineVariable:@"e" withValue:[[NSDecimalNumber alloc] initWithDouble:M_E]]; + [self defineVariable:@"π" withValue:[[NSDecimalNumber alloc] initWithDouble:M_PI]]; } return self; } @@ -45,7 +48,7 @@ static MPEvaluationContext *sharedContext; [self.stack removeLastObject]; } -- (void)defineVariable:(NSString *)variable withValue:(id)value +- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value { NSMutableDictionary *currentBindings = self.stack.lastObject; currentBindings[variable] = value; @@ -59,8 +62,14 @@ static MPEvaluationContext *sharedContext; - (NSDecimalNumber *)valueForVariable:(NSString *)variable { - NSMutableDictionary *currentBindings = self.stack.lastObject; - return currentBindings[variable]; + NSUInteger currentIndex = self.stack.count; + NSDictionary *currentBindings; + NSDecimalNumber *value = nil; + while (!value && currentIndex > 0) { + currentBindings = self.stack[--currentIndex]; + value = currentBindings[variable]; + } + return value; } - (BOOL)isVariableDefined:(NSString *)variable diff --git a/MathPad/MPExpression.h b/MathPad/MPExpression.h index ad83b88..f89ad28 100644 --- a/MathPad/MPExpression.h +++ b/MathPad/MPExpression.h @@ -8,6 +8,13 @@ @import Foundation; #import "NSString+MPExpressionElement.h" +#import "MPToken.h" + +typedef NS_ENUM(NSUInteger, MPReferenceFrame) { + MPElementReferenceFrame, + MPSymbolReferenceFrame, + MPTokenReferenceFrame +}; @class MPExpression, MPFunction, MPRangePath, MPExpressionEvaluator, MPParseError; @protocol MPExpressionElement; @@ -127,6 +134,34 @@ @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 indexed 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 numberOfElements @brief Returns the number of elements in the receiver. @@ -139,7 +174,7 @@ @return The current number of elements in the receiver. */ -- (NSUInteger)numberOfElements; +- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame; /*! @@ -160,7 +195,23 @@ @return The element at @c anIndex. */ -- (id)elementAtIndex:(NSUInteger)anIndex; +- (id)itemAtIndex:(NSUInteger)anIndex + referenceFrame:(MPReferenceFrame)referenceFrame; + +- (id)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 indexed reference frame. + */ +- (NSUInteger)indexOfElement:(id)element; /*! @@ -185,7 +236,8 @@ The length of the returned array is equal to the length of the specified range. */ -- (NSArray *)elementsInIndexedRange:(NSRange)range; +- (NSArray *)itemsInRange:(NSRange)range + referenceFrame:(MPReferenceFrame)referenceFrame; /*! @@ -197,7 +249,51 @@ @return An array of all elements from the receiver. */ -- (NSArray *)elements; +- (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 indexed 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; + +- (NSUInteger)convertIndex:(NSUInteger)index + fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame + toReferenceFrame:(MPReferenceFrame)toReferenceFrame; + +- (NSUInteger)convertIndex:(NSUInteger)index + fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame + toReferenceFrame:(MPReferenceFrame)toReferenceFrame + offset:(NSUInteger *)offset; + +- (NSRange)convertRange:(NSRange)aRange + fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame + toReferenceFrame:(MPReferenceFrame)toReferenceFrame; + +- (NSRange)convertRange:(NSRange)aRange + fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame + toReferenceFrame:(MPReferenceFrame)toReferenceFrame + leadingOffset:(NSUInteger *)leadingOffset + trailingOffset:(NSUInteger *)trailingOffset; #pragma mark Mutating Expressions @@ -228,9 +324,16 @@ The elements that should replace the symbols specified by @c range. */ -- (void)replaceSymbolsInRange:(NSRange)range - withElements:(NSArray *)elements; +- (void)replaceItemsInRange:(NSRange)range + referenceFrame:(MPReferenceFrame)referenceFrame + withElements:(NSArray *)elements; +- (MPExpression *)subexpressionFromIndex:(NSUInteger)from + referenceFrame:(MPReferenceFrame)referenceFrame; +- (MPExpression *)subexpressionToIndex:(NSUInteger)to + referenceFrame:(MPReferenceFrame)referenceFrame; +- (MPExpression *)subexpressionWithRange:(NSRange)range + referenceFrame:(MPReferenceFrame)referenceFrame; #pragma mark Evaluating Expressions @@ -256,17 +359,6 @@ - (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error; -/*! - @property evaluator - @brief Returns an object that can evaluate the receiver. - - @discussion To just evaluate an expression it is recommended to send it an - @c evaluateWithError: message. You can however use this property - instead if you need more control over the evaluation process. - */ -@property (readonly, nonatomic, strong) MPExpressionEvaluator *evaluator; - - #pragma mark Notifications // All notification methods should create a new rangePath with the receiver's index added to the beginning of the path and then ascend the message to it's parent @@ -291,8 +383,8 @@ The number of elements replacing the elements specified by @c rangePath. */ -- (void)didChangeElementsInIndexedRangePath:(MPRangePath *)rangePath - replacementLength:(NSUInteger)replacementLength; +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath + replacementLength:(NSUInteger)replacementLength; #pragma mark Basic NSObject Methods @@ -301,412 +393,63 @@ @end - -/* --------------------------------------------------------------------------- */ -/* Extension Methods */ -/* --------------------------------------------------------------------------- */ - - -@interface MPExpression (MPExpressionExtension) +@interface MPExpression (MPExpressionConvenience) #pragma mark Querying Expressions +- (NSUInteger)countElements; +- (NSUInteger)countSymbols; +- (NSUInteger)countTokens; -/*! - @method length - @brief Returns the length of the receiver. - - @discussion The length of an expression is calculated by going over each - element in the receiver and sending it a @c -length message. This - method should be used to determine the number of digits or - symbols in an expression. - - The result of this method is expressed in the located reference - frame. The respective method for the indexed reference frame is - @c -numberOfSymbols. - - @return The length of the receiver. This is the number of symbols in all - elements in the receiver where a function element is counted as a - single symbol. - */ -- (NSUInteger)length; - - -/*! - @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 indexed 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; - - -// Subscripting is supported in the indexed reference frame -- (id)objectAtIndexedSubscript:(NSUInteger)idx; - - -#pragma mark Working With Expressions - - -/*! - @method elementAtLocation: - @brief Returns the element that is located at @c location. - - @discussion This method finds an element in the located reference frame. If - @c location is greater or equal to the @c length of the receiver - a @c NSRangeException is raised. - - @param location - The location of the element to find expressed in the located - reference frame. - - @return The element located at @c location. - */ -- (id)elementAtLocation:(NSUInteger)location; - - -/*! - @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 indexed 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 elementsInIndexedRangePath: - @brief Returns the elements in the specified range path. - - @discussion This method works similar to @c elementAtIndexPath: except that - it queries multiple elements at once. - - @param rangePath - The range path the requested objects are located at. The complete - range path is expressed in the indexed reference frame. - - @return An array of objects specified by the range path. The returned - elements are not copied before they are returned. Be aware that - any mutations made to the returned objects are reflected in the - receiver. - */ -- (NSArray *)elementsInIndexedRangePath:(MPRangePath *)rangePath; - - - -/*! - @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 indexed reference frame. - */ -- (NSUInteger)indexOfElement:(id)element; - - -#pragma mark Converting Between Indexes and Locations - - -/*! - @method indexOfElementAtLocation:offset: - @brief Calculates the index of the element the specified location points - to. - - @discussion The @c location is in the located reference frame whereas the - returned value is an index. This method converts from the former - to the latter. - - This method prefers higher indexes. This means that if the - returned @c offset would be equal to the length of the element at - the calculated index, insead index+1 is returned and the @c - offset is set to @c 0. - - If the @c location exceeds the receiver's bounds a @c - NSRangeException will be raised. - - @param location - The location of which you want the corresponding element index. - - @param offset - An output parameter that gets set to the offst into the symbol - whose index is returned. If location for example points to the - symbol @c '2' in the string element @c '123' the offset @c would - be set to @c 1. - - @return The index of the element the location points to. - */ -- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location offset:(out NSUInteger *)offset; - - -/*! - @method locationOfElementAtIndex: - @brief Calculates the location of the element at @c index. - - @discussion @c index is expressed in the indexed reference frame. Use this - method to convert an index into the located reference frame. - - If the index exceeds the receiver's number of elements a @c - NSRangeException will be raised. - - @param index - The index of the element that is to be converted into the length - reference frame. - - @return The number of symbols (in the length reference frame) before the - element at @c index. - */ -- (NSUInteger)locationOfElementAtIndex:(NSUInteger)index; - - -/*! - @method indexedRangeForRange: - @brief Converts @c aRange from the located reference frame into the - indexed reference frame. - - @discussion If the range exceeds the receiver's bounds a @c NSRangeException - is raised. - - @param aRange - The range to be converted. Expressed in the located reference - frame. - - @return @c aRange converted into the indexed reference frame. - */ -- (NSRange)indexedRangeForRange:(NSRange)aRange; - - -/*! - @method rangeForIndexedRange: - @brief Converts @c aRange from the indexed reference frame into the - located reference frame. - - @discussion In the range exceeds the receiver's bounds a @c NSRangeException - is raised. - - @param aRange - The range to be converted. Expressed in the indexed reference - frame. - - @return @c aRange converted into the located reference frame. - */ -- (NSRange)rangeForIndexedRange:(NSRange)aRange; +- (id)elementAtIndex:(NSUInteger)index; +- (id)symbolAtIndex:(NSUInteger)index; +- (id)tokenAtIndex:(NSUInteger)index; #pragma mark Mutating Expressions - -- (MPExpression *)subexpressionFromIndex:(NSUInteger)from; -- (MPExpression *)subexpressionToIndex:(NSUInteger)to; -- (MPExpression *)subexpressionWithIndexedRange:(NSRange)range; - - /*! - @method subexpressionFromLocation: - @brief Creates a new expression from the specified location (inclusive) - to the end of the receiver. + @method appendElement: + @brief Appends @c anElement to the receiver. - @discussion The elements in the newly created expression are copied to the - new expression. The location is specified in the located - reference frame. - - If the given location exceeds the receiver's bounds a @c - NSRangeException is raised. - - @param from - The first location to be included in the new expression. - - @return A new expression from the given location to the end of the - receiver. - */ -- (MPExpression *)subexpressionFromLocation:(NSUInteger)from; - - -/*! - @method subexpressionToLocation: - @brief Creates a new expression from the beginning to the specified - location (exclusive). - - @discussion The elements in the newly created expression are copied to the - new expression. The location is specified in the located - reference frame. - - If the given location exceeds the receiver's bounds a @c - NSRangeException is raised. - - @param to - The first location not to be included in the new expression (or - the length of the new expression). - - @return A new expression with the first @c to symbols of the receiver. - */ -- (MPExpression *)subexpressionToLocation:(NSUInteger)to; - - -/*! - @method subexpressionWithRange: - @brief Creates a new expression with the symbols in the specified range. - - @discussion The elements in the newly created expression are copied to the - new exoression. The range is specified in the located reference - frame. - - If the given range exceeds the receiver's bounds a @c - NSRangeException is raised. - - @param range - The range from which to create the new expression. - - @return A new expression with the symbols in the specified range. - */ -- (MPExpression *)subexpressionWithRange:(NSRange)range; - - -- (void)replaceElementsInIndexedRange:(NSRange)range - withElements:(NSArray *)elements; - -- (void)replaceSymbolsInRangePath:(MPRangePath *)rangePath - withElements:(NSArray *)elements; - - -/*! - @method appendElement: - @brief Appends @c anElement to the receiver. - - @param anElement - The element to append to the receiver. + @param anElement + The element to append to the receiver. */ - (void)appendElement:(id)anElement; /*! - @method appendElements: - @brief Appends the objects from @c elements to the receiver. + @method appendElements: + @brief Appends the objects from @c elements to the receiver. - @param elements - The elements to append to the receiver. + @param elements + The elements to append to the receiver. */ - (void)appendElements:(NSArray *)elements; - - (void)insertElement:(id)anElement - atIndex:(NSUInteger)index; + atIndex:(NSUInteger)index + referenceFrame:(MPReferenceFrame)referenceFrame; - (void)insertElements:(NSArray *)elements - atIndex:(NSUInteger)index; - - - -/*! - @method insertElement:atLocation: - @brief Inserts @c anElement at @c location. - - @discussion The location is specified in the length reference frame. - - If the given location exceeds the receiver's bounds a @c - NSRangeException is raised. - - @param anElement - The element to be inserted into the receiver. - - @param location - The location @c anElement should be inserted at. - */ -- (void)insertElement:(id)anElement - atLocation:(NSUInteger)location; + atIndex:(NSUInteger)index + referenceFrame:(MPReferenceFrame)referenceFrame; /*! - @method insertElements:atLocation: - @brief Inserts the elements from @c elements at @c location. + @method deleteElementsInRange: + @brief Removes the elements specified by @c range from the receiver. - @discussion The location is specified in the length reference frame. + @discussion The range is specified in the length reference frame. - If the given location exceeds the receiver's bounds a @c - NSRangeException is raised. + If @c range exceeds the receiver's bounds a @c NSRangeException + is raised. - @param elements - The elements to be inserted into the receiver. - - @param location - The location the elements in @c elements should be inserted into - the receiver. + @param range + The range to remove from the receiver. */ -- (void)insertElements:(NSArray *)elements - atLocation:(NSUInteger)location; - - -- (void)deleteElementsInIndexedRange:(NSRange)range; - -/*! - @method deleteElementsInRange: - @brief Removes the elements specified by @c range from the receiver. - - @discussion The range is specified in the length reference frame. - - If @c range exceeds the receiver's bounds a @c NSRangeException - is raised. - - @param range - The range to remove from the receiver. - */ -- (void)deleteElementsInRange:(NSRange)range; - - -/*! - @method mutableElements - @brief Returns a proxy mutable array object that responds to all methods - defined by @c NSMutableArray. - - @discussion Mutations on the proxy object also change the receiver. The proxy - object does not respond to coding methods. Copying the proxy - object will not duplicate it. - - @return A proxy object that responds to all methods defined by @c - NSMutableArray. - */ -// - (NSMutableArray *)mutableElements; - -/* Subscripting is supported for elements in the indexed reference frame */ -- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx; +- (void)deleteElementsInRange:(NSRange)range + referenceFrame:(MPReferenceFrame)referenceFrame; @end - diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m index 9a8ff35..6c57f58 100644 --- a/MathPad/MPExpression.m +++ b/MathPad/MPExpression.m @@ -13,84 +13,32 @@ #import "NSIndexPath+MPAdditions.h" #import "MPException.h" +#import "MPExpressionTokenizer.h" #import "MPExpressionEvaluator.h" +#import "MPToken.h" @interface MPExpression () { - NSMutableArray *__strong _elements; + NSMutableArray * _elements; } -- (void)fixElements; - -@end - - - -@interface MPExpression (MPExpressionPrivate) +- (NSArray *)tokens; - (void)validateElements:(NSArray *)elements; +- (void)fixElements; -- (NSUInteger)lengthOfElements:(NSArray *)elements; - -- (BOOL)splitElementsAtLocation:(NSUInteger)location - insertionIndex:(out NSUInteger *)insertionIndex; - -@end - - - -@implementation MPExpression (MPExpressionPrivate) - -- (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}]; - } - } -} - -- (NSUInteger)lengthOfElements:(NSArray *)elements -{ - NSUInteger length = 0; - for (id element in elements) { - length += element.length; - } - return length; -} - -- (BOOL)splitElementsAtLocation:(NSUInteger)location - insertionIndex:(out NSUInteger *)insertionIndex -{ - if (location == 0) { - *insertionIndex = 0; - return NO; - } - - NSUInteger splitOffset; - NSUInteger splitElementIndex = [self indexOfElementAtLocation:location - offset:&splitOffset]; - if (splitOffset != 0) { - NSString *splitElement = (NSString *)self.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)_replaceSymbolsInRange:(NSRange)range + withElements:(NSArray *)elements; +- (BOOL)_splitElementsAtLocation:(NSUInteger)location + insertionIndex:(out NSUInteger *)insertionIndex; @end @implementation MPExpression { - NSUInteger _cachedLength; + NSArray *_tokenCache; NSRange _editedRange; BOOL _didSplitStartOnEditing; @@ -117,7 +65,6 @@ self = [super init]; if (self) { [self validateElements:elements]; - _cachedLength = 0; _elements = [[NSMutableArray alloc] initWithArray:elements copyItems:YES]; [self fixElements]; @@ -129,11 +76,32 @@ #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 < self.elements.count; index++) { - id next = index+1 < self.elements.count ? self.elements[index+1] : nil; - id current = self.elements[index]; + for (NSUInteger index = 0; index < _elements.count; index++) { + id next = index+1 < _elements.count ? _elements[index+1] : nil; + id current = _elements[index]; if ([current isString]) { if (current.length == 0) { [_elements removeObjectAtIndex:index]; @@ -166,38 +134,283 @@ } } - #pragma mark Querying Expressions -- (NSUInteger)numberOfElements +- (MPExpression *)rootExpression { - return self.elements.count; + 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 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 location = 0; + NSUInteger elementIndex = 0; + id element = nil; + while (location < anIndex) { + element = _elements[elementIndex++]; + location += element.length; + } + if (location == anIndex && element.isFunction) { + return element; + } + NSUInteger indexInString = location - element.length + anIndex; + return [((NSString *)element) substringWithRange:NSMakeRange(indexInString, 1)]; + } + + case MPTokenReferenceFrame: + return self.tokens[anIndex]; + } } - (id)elementAtIndex:(NSUInteger)anIndex + referenceFrame:(MPReferenceFrame)referenceFrame { - return self.elements[anIndex]; + NSUInteger elementIndex = [self convertIndex:anIndex + fromReferenceFrame:referenceFrame + toReferenceFrame:MPElementReferenceFrame]; + return _elements[elementIndex]; } -- (NSArray *)elementsInIndexedRange:(NSRange)range +#warning If multiple equal expressions exist errors may occur... +- (NSUInteger)indexOfElement:(id)element { - return [self.elements subarrayWithRange:range]; + return [_elements indexOfObject:element]; } -- (NSArray *)elements +- (NSArray *)itemsInRange:(NSRange)range + referenceFrame:(MPReferenceFrame)referenceFrame { - return _elements; + MPExpression *subexpression = [self subexpressionWithRange:range + 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 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 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)index + fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame + toReferenceFrame:(MPReferenceFrame)toReferenceFrame +{ + return [self convertIndex:index + fromReferenceFrame:fromReferenceFrame + toReferenceFrame:toReferenceFrame + offset:NULL]; +} + +- (NSUInteger)convertIndex:(NSUInteger)index + fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame + toReferenceFrame:(MPReferenceFrame)toReferenceFrame + offset:(NSUInteger *)offset +{ + if (fromReferenceFrame == toReferenceFrame || index == 0) { + if (offset) { + *offset = 0; + } + return index; + } + + NSUInteger symbolIndex __block = 0; + switch (fromReferenceFrame) { + case MPElementReferenceFrame: + [_elements enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + symbolIndex += obj.length; + *stop = idx >= index - 1; + }]; + break; + + case MPSymbolReferenceFrame: + symbolIndex = index; + break; + + case MPTokenReferenceFrame: + [_elements enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + symbolIndex = NSMaxRange(obj.range); + *stop = idx >= index - 1; + }]; + break; + } + + switch (toReferenceFrame) { + case MPElementReferenceFrame: + { + NSUInteger totalLength = 0; + NSUInteger elementIndex = 0; + id 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 token; + while (totalLength < symbolIndex) { + token = self.tokens[tokenIndex++]; + totalLength = NSMaxRange(token.range); + } + 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)replaceSymbolsInRange:(NSRange)range +- (void)replaceItemsInRange:(NSRange)range + referenceFrame:(MPReferenceFrame)referenceFrame + withElements:(NSArray *)elements +{ + NSUInteger start = [self convertIndex:range.location + fromReferenceFrame:referenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; + NSUInteger end = [self convertIndex:NSMaxRange(range) + fromReferenceFrame:referenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; + [self _replaceSymbolsInRange:NSMakeRange(start, end - start) + withElements:elements]; +} + +- (void)_replaceSymbolsInRange:(NSRange)range withElements:(NSArray *)elements { - if (NSMaxRange(range) > self.length) { + if (NSMaxRange(range) > [self countItemsInReferenceFrame:MPSymbolReferenceFrame]) { @throw [NSException exceptionWithName:NSRangeException reason:@"Range out of bounds of expression" userInfo:nil]; @@ -207,15 +420,15 @@ // Locate the position, split the elements NSUInteger startIndex; // startIndex is inclusive BOOL didSplitStart = NO; - if ([self numberOfElements] == 0) { + if (_elements.count == 0) { startIndex = 0; } else { - didSplitStart = [self splitElementsAtLocation:range.location - insertionIndex:&startIndex]; + didSplitStart = [self _splitElementsAtLocation:range.location + insertionIndex:&startIndex]; } NSUInteger endIndex; // endIndex is exclusive - BOOL didSplitEnd = [self splitElementsAtLocation:NSMaxRange(range) - insertionIndex:&endIndex]; + BOOL didSplitEnd = [self _splitElementsAtLocation:NSMaxRange(range) + insertionIndex:&endIndex]; // Perform the replacement NSMutableArray *newElements = [[NSMutableArray alloc] initWithArray:elements @@ -223,7 +436,7 @@ [_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex) withObjectsFromArray:newElements]; - _cachedLength = 0; + _tokenCache = nil; NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0); NSUInteger editLength = endIndex - startIndex; @@ -246,37 +459,78 @@ } [self fixElements]; - [self.evaluator expressionDidChangeInRange:_editedRange - replacementLength:_replacementLength]; - [self didChangeElementsInIndexedRangePath:[[MPRangePath alloc] initWithRange:_editedRange] + [self didChangeElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange] replacementLength:_replacementLength]; } +- (BOOL)_splitElementsAtLocation:(NSUInteger)location + insertionIndex:(out 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; +} + +- (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)range + referenceFrame:(MPReferenceFrame)referenceFrame +{ + MPExpression *subexpression = [self copy]; + NSRange preceedingRange = NSMakeRange(0, range.location); + NSUInteger firstOut = NSMaxRange(range); + 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 *)evaluateWithError:(MPParseError *__autoreleasing *)error { - MPTerm *term = [self.evaluator parseExpectingVariable:NO - error:error]; + MPExpressionEvaluator *evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self]; + MPTerm *term = [evaluator parseExpectingVariable:NO + error:error]; return [term evaluate]; } -@synthesize evaluator = _evaluator; -- (MPExpressionEvaluator *)evaluator -{ - if (!_evaluator) { - _evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self]; - } - return _evaluator; -} - - #pragma mark Notifications -- (void)didChangeElementsInIndexedRangePath:(MPRangePath *)rangePath +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)replacementLength { NSUInteger selfIndex = [self.parent indexOfChild:self]; @@ -315,11 +569,11 @@ #warning Bad Implementation NSMutableString *description = [[NSMutableString alloc] init]; NSUInteger index = 0; - for (id element in self.elements) { + for (id element in _elements) { if ([element isString]) { NSMutableString *correctedSymbol = [[element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy]; // Prefix operator - if (element != self.elements[0]) { + if (element != _elements[0]) { unichar prefix = [correctedSymbol characterAtIndex:0]; if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) { [correctedSymbol insertString:@"*" @@ -327,14 +581,14 @@ } } // Suffix operator - if (element != [self.elements lastObject]) { + 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 && [self.elements[index-1] isKindOfClass:[MPFunction class]]) { + } else if (index > 0 && [_elements[index-1] isKindOfClass:[MPFunction class]]) { [description appendFormat:@"*%@", [element description]]; } else { [description appendString:[element description]]; @@ -346,7 +600,7 @@ - (NSUInteger)hash { - return [self.elements hash]; + return [_elements hash]; } @@ -355,7 +609,7 @@ - (id)copyWithZone:(NSZone *)zone { - MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:self.elements]; + MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:_elements]; return copy; } @@ -371,211 +625,52 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.elements]; + [aCoder encodeObject:_elements]; } @end - -@implementation MPExpression (MPExpressionExtension) - +@implementation MPExpression (MPExpressionConvenience) #pragma mark Querying Expressions - -- (NSUInteger)length +- (NSUInteger)countElements { - if (_cachedLength == 0) { - _cachedLength = [self lengthOfElements:self.elements]; - } - return _cachedLength; + return [self countItemsInReferenceFrame:MPElementReferenceFrame]; } -- (MPExpression *)rootExpression +- (NSUInteger)countSymbols { - if (self.parent == nil) { - return self; - } - return [self.parent rootExpression]; + return [self countItemsInReferenceFrame:MPSymbolReferenceFrame]; } -- (NSIndexPath *)indexPath +- (NSUInteger)countTokens { - if (self.parent) { - NSUInteger selfIndex = [self.parent indexOfChild:self]; - return [[self.parent indexPath] indexPathByAddingIndex:selfIndex]; - } else { - return [[NSIndexPath alloc] init]; - } + return [self countItemsInReferenceFrame:MPTokenReferenceFrame]; } -- (id)objectAtIndexedSubscript:(NSUInteger)idx +- (id)elementAtIndex:(NSUInteger)index { - return [self elementAtIndex:idx]; + return [self itemAtIndex:index + referenceFrame:MPElementReferenceFrame]; } - -#pragma mark Working With Expressions - - -- (id)elementAtLocation:(NSUInteger)location +- (id)symbolAtIndex:(NSUInteger)index { - NSUInteger index = [self indexOfElementAtLocation:location offset:NULL]; - return [self elementAtIndex:index]; + return [self itemAtIndex:index + referenceFrame:MPSymbolReferenceFrame]; } -- (id)elementAtIndexPath:(NSIndexPath *)indexPath +- (id)tokenAtIndex:(NSUInteger)index { - if (indexPath.length == 0) { - return self; - } - id element = [self elementAtIndex:[indexPath indexAtPosition:0]]; - if (indexPath.length == 1) { - return element; - } - if ([element isFunction]) { - return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; - } - return nil; -} - -- (NSArray *)elementsInIndexedRangePath:(MPRangePath *)rangePath -{ - MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]]; - return [targetExpression elementsInIndexedRange:rangePath.rangeAtLastIndex]; -} - -#warning If multiple equal expressions exist errors may occur... -- (NSUInteger)indexOfElement:(id)element -{ - return [self.elements indexOfObject:element]; -} - - -#pragma mark Converting Between Indexes and Locations - - -- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location - offset:(out NSUInteger *)offset -{ - if (location == 0) { - if (offset != NULL) { - *offset = 0; - } - return 0; - } - - // Calculating elementIndex and splitOffset - NSUInteger totalLength = 0; - NSUInteger elementIndex = 0; - NSUInteger elementLength = 0; - for (id element in self.elements) { - elementLength = element.length; - totalLength += elementLength; - if (totalLength >= location) { - break; - } - ++elementIndex; - } - NSUInteger elementOffset = elementLength - (totalLength - location); - - id element = self.elements[elementIndex]; - if (elementOffset == element.length) { - elementOffset = 0; - elementIndex++; - } - if (offset != NULL) { - *offset = elementOffset; - } - return elementIndex; -} - -- (NSUInteger)locationOfElementAtIndex:(NSUInteger)index -{ - NSUInteger location = 0; - for (NSUInteger i = 0; i < index; i++) { - location += [self elementAtIndex:i].length; - } - return location; -} - -- (NSRange)indexedRangeForRange:(NSRange)aRange -{ - NSUInteger startLocation = aRange.location; - NSUInteger endLocation = NSMaxRange(aRange); - NSUInteger startIndex = [self indexOfElementAtLocation:startLocation offset:NULL]; - NSUInteger endIndex = [self indexOfElementAtLocation:endLocation offset:NULL]; - return NSMakeRange(startIndex, endIndex-startIndex); -} - -- (NSRange)rangeForIndexedRange:(NSRange)aRange -{ - NSUInteger startIndex = aRange.location; - NSUInteger endIndex = NSMaxRange(aRange); - NSUInteger startLocation = [self locationOfElementAtIndex:startIndex]; - NSUInteger endLocation = [self locationOfElementAtIndex:endIndex]; - return NSMakeRange(startLocation, endLocation-startLocation); + return [self itemAtIndex:index + referenceFrame:MPTokenReferenceFrame]; } #pragma mark Mutating Expressions - -- (MPExpression *)subexpressionFromIndex:(NSUInteger)from -{ - NSUInteger fromLocation = [self locationOfElementAtIndex:from]; - return [self subexpressionFromLocation:fromLocation]; -} - -- (MPExpression *)subexpressionToIndex:(NSUInteger)to -{ - NSUInteger toLocation = [self locationOfElementAtIndex:to]; - return [self subexpressionToLocation:toLocation]; -} - -- (MPExpression *)subexpressionWithIndexedRange:(NSRange)range -{ - NSRange locationRange = [self rangeForIndexedRange:range]; - return [self subexpressionWithRange:locationRange]; -} - -- (MPExpression *)subexpressionFromLocation:(NSUInteger)from -{ - return [self subexpressionWithRange:NSMakeRange(from, self.length - from)]; -} - -- (MPExpression *)subexpressionToLocation:(NSUInteger)to -{ - return [self subexpressionWithRange:NSMakeRange(0, to)]; -} - -- (MPExpression *)subexpressionWithRange:(NSRange)range -{ - MPExpression *subexpression = [self copy]; - NSRange preceedingRange = NSMakeRange(0, range.location); - NSUInteger firstOut = NSMaxRange(range); - NSRange exceedingRange = NSMakeRange(firstOut, self.length-firstOut); - [subexpression deleteElementsInRange:exceedingRange]; - [subexpression deleteElementsInRange:preceedingRange]; - return subexpression; -} - -- (void)replaceElementsInIndexedRange:(NSRange)range - withElements:(NSArray *)elements -{ - [self replaceSymbolsInRange:[self rangeForIndexedRange:range] - withElements:elements]; -} - -- (void)replaceSymbolsInRangePath:(MPRangePath *)rangePath - withElements:(NSArray *)elements -{ - MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]]; - [targetExpression replaceSymbolsInRange:rangePath.rangeAtLastIndex - withElements:elements]; -} - - (void)appendElement:(id)anElement { [self appendElements:@[anElement]]; @@ -583,49 +678,35 @@ - (void)appendElements:(NSArray *)elements { - [self replaceSymbolsInRange:NSMakeRange(self.length, 0) withElements:elements]; + [self replaceItemsInRange:NSMakeRange([self countItemsInReferenceFrame:MPSymbolReferenceFrame], 0) + referenceFrame:MPSymbolReferenceFrame + withElements:elements]; } - (void)insertElement:(id)anElement atIndex:(NSUInteger)index + referenceFrame:(MPReferenceFrame)referenceFrame { - [self insertElement:anElement - atLocation:[self locationOfElementAtIndex:index]]; + [self insertElements:@[anElement] + atIndex:index + referenceFrame:referenceFrame]; } - (void)insertElements:(NSArray *)elements atIndex:(NSUInteger)index + referenceFrame:(MPReferenceFrame)referenceFrame { - [self insertElements:elements - atLocation:[self locationOfElementAtIndex:index]]; -} - -- (void)insertElement:(id)anElement - atLocation:(NSUInteger)location -{ - [self insertElements:@[anElement] atLocation:location]; -} - -- (void)insertElements:(NSArray *)elements - atLocation:(NSUInteger)location -{ - [self replaceSymbolsInRange:NSMakeRange(location, 0) withElements:elements]; -} - -- (void)deleteElementsInIndexedRange:(NSRange)range -{ - [self deleteElementsInIndexedRange:[self rangeForIndexedRange:range]]; + [self replaceItemsInRange:NSMakeRange(index, 0) + referenceFrame:referenceFrame + withElements:elements]; } - (void)deleteElementsInRange:(NSRange)range + referenceFrame:(MPReferenceFrame)referenceFrame { - [self replaceSymbolsInRange:range withElements:@[]]; -} - -- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx -{ - [self replaceSymbolsInRange:NSMakeRange(idx, 1) - withElements:@[obj]]; + [self replaceItemsInRange:range + referenceFrame:referenceFrame + withElements:@[]]; } @end diff --git a/MathPad/MPExpressionEvaluator.h b/MathPad/MPExpressionEvaluator.h index 185999b..1ed91cf 100644 --- a/MathPad/MPExpressionEvaluator.h +++ b/MathPad/MPExpressionEvaluator.h @@ -15,15 +15,9 @@ @interface MPExpressionEvaluator : NSObject -// Do not instanciate yourself, use evaluator property of MPExpression instead - (instancetype)initWithExpression:(MPExpression *)expression; @property (readonly, nonatomic, weak) MPExpression *expression; -@property (nonatomic, strong) MPExpressionTokenizer *lexer; - -- (void)expressionDidChangeInRange:(NSRange)range - replacementLength:(NSUInteger)replacementLength; - #pragma mark Evaluating Expressions @property (readonly, nonatomic, copy) NSString *definedVariable; diff --git a/MathPad/MPExpressionEvaluator.m b/MathPad/MPExpressionEvaluator.m index e07ffb3..fb49bf2 100644 --- a/MathPad/MPExpressionEvaluator.m +++ b/MathPad/MPExpressionEvaluator.m @@ -21,7 +21,7 @@ @interface MPExpressionEvaluator () @property (readwrite, nonatomic, copy) NSString *definedVariable; -@property (nonatomic, strong) NSArray *tokens; + - (void)setError:(MPParseError *)error; @@ -41,36 +41,6 @@ return self; } -@synthesize lexer = _lexer; - -- (void)setLexer:(MPExpressionTokenizer *)lexer -{ - _lexer = lexer; - self.tokens = nil; -} - -- (MPExpressionTokenizer *)lexer -{ - if (!_lexer) { - _lexer = [[MPExpressionTokenizer alloc] init]; - } - return _lexer; -} - -- (NSArray *)tokens -{ - if (!_tokens) { - _tokens = [self.lexer tokenizeExpression:self.expression]; - } - return _tokens; -} - -- (void)expressionDidChangeInRange:(NSRange)range - replacementLength:(NSUInteger)replacementLength -{ - self.tokens = nil; -} - #pragma mark Evaluating Expressions - (void)setError:(MPParseError *)error { @@ -84,7 +54,7 @@ error:(MPParseError *__autoreleasing *)error { _error = error; - tokenStream = [[MPTokenStream alloc] initWithTokens:self.tokens]; + tokenStream = [[MPTokenStream alloc] initWithTokens:[self.expression allItemsInReferenceFrame:MPTokenReferenceFrame]]; if (!tokenStream.hasMoreTokens) { self.error = MPParseError(NSMakeRange(0, 0), @"Empty Expression"); return nil; @@ -186,11 +156,16 @@ case MPGenericFunctionToken: { + if ([token isKindOfClass:[MPPowerFunction class]]) { + self.error = MPParseError(NSMakeRange(token.range.location, 0), @"No Base for Power"); + return nil; + } return [self decoratedTerm:[((MPFunction *)token) parseWithError:_error]]; } case MPSinToken: { + BOOL inverse = [self inverseFunction]; NSRange sinTermRange; MPTerm *sinTerm = [self nextValue:&sinTermRange]; if (!sinTerm) { @@ -199,11 +174,12 @@ } return nil; } - return [[MPTerm alloc] initWithSinOfTerm:sinTerm]; + return inverse ? [[MPTerm alloc] initWithInverseSinOfTerm:sinTerm] : [[MPTerm alloc] initWithSinOfTerm:sinTerm]; } case MPCosToken: { + BOOL inverse = [self inverseFunction]; NSRange cosTermRange; MPTerm *cosTerm = [self nextValue:&cosTermRange]; if (!cosTerm) { @@ -212,11 +188,12 @@ } return nil; } - return [[MPTerm alloc] initWithSinOfTerm:cosTerm]; + return inverse ? [[MPTerm alloc] initWithInverseCosOfTerm:cosTerm] : [[MPTerm alloc] initWithCosOfTerm:cosTerm]; } case MPTanToken: { + BOOL inverse = [self inverseFunction]; NSRange tanTermRange; MPTerm *tanTerm = [self nextValue:&tanTermRange]; if (!tanTerm) { @@ -225,7 +202,7 @@ } return nil; } - return [[MPTerm alloc] initWithTanOfTerm:tanTerm]; + return inverse ? [[MPTerm alloc] initWithInverseTanOfTerm:tanTerm] : [[MPTerm alloc] initWithTanOfTerm:tanTerm]; } case MPEOFToken: @@ -254,10 +231,31 @@ MPPowerFunction *powerFunction = (MPPowerFunction *)powerToken; powerFunction.baseTerm = decoratedTerm; return [powerFunction parseWithError:_error]; - } else { - tokenStream.currentLocation--; + } else if (powerToken) { + tokenStream.currentTokenIndex--; } return decoratedTerm; } +- (BOOL)inverseFunction +{ + MPToken *powerToken = [tokenStream nextTokenOfType:MPGenericFunctionToken]; + if ([powerToken isKindOfClass:[MPPowerFunction class]]) { + MPPowerFunction *powerFunction = (MPPowerFunction *)powerToken; + if (powerFunction.exponentExpression.countElements == 1) { + id element = [powerFunction.exponentExpression elementAtIndex:0]; + if ([element isString]) { + NSString *exponent = [[((NSString *)element) componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""]; + if ([exponent isEqualToString:@"-1"]) { + return YES; + } + } + } + } + if(powerToken) { + tokenStream.currentTokenIndex--; + } + return NO; +} + @end diff --git a/MathPad/MPExpressionLayout.m b/MathPad/MPExpressionLayout.m index 98da14c..ffa8566 100644 --- a/MathPad/MPExpressionLayout.m +++ b/MathPad/MPExpressionLayout.m @@ -8,6 +8,8 @@ #import "MPExpressionLayout.h" #import "MPFunctionLayout.h" +#import "MPPowerFunction.h" +#import "MPPowerFunctionLayout.h" #import "NSString+MPExpressionElement.h" #import "NSIndexPath+MPAdditions.h" @@ -91,11 +93,11 @@ #pragma mark Drawing Methods - (NSRect)generateBounds { - if (self.expression.numberOfElements == 0) { + 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.numberOfElements; index++) { + for (NSUInteger index = 0; index < self.expression.countElements; index++) { NSRect elementBounds = [self boundsOfElementAtIndex:index]; width += elementBounds.size.width; height = MAX(height, elementBounds.size.height); @@ -107,8 +109,10 @@ - (NSRect)boundingRectForRange:(NSRange)range { NSUInteger startOffset; - NSUInteger startElementIndex = [self.expression indexOfElementAtLocation:range.location - offset:&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++) { @@ -122,7 +126,7 @@ x += xOffset; width += CTLineGetBoundsWithOptions(line, 0).size.width - xOffset; CFRelease(line); - } else if (startElementIndex < self.expression.numberOfElements) { // Otherwise the selection is after the last symbol + } else if (startElementIndex < self.expression.countElements) { // Otherwise the selection is after the last symbol width += [self boundsOfElementAtIndex:startElementIndex].size.width; } @@ -132,8 +136,10 @@ } NSUInteger endOffset; - NSUInteger endElementIndex = [self.expression indexOfElementAtLocation:NSMaxRange(range) - offset:&endOffset]; + NSUInteger endElementIndex = [self.expression convertIndex:NSMaxRange(range) + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame + offset:&endOffset]; // Selection is inside of one string element if (startElementIndex == endElementIndex) { @@ -171,7 +177,7 @@ - (NSIndexPath *)indexPathForMousePoint:(NSPoint)point { NSUInteger currentPosition = 0; - for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { + for (NSUInteger index = 0; index < self.expression.countElements; index++) { NSRect elementBounds = [self boundsOfElementAtIndex:index]; NSPoint elementOffset = [self offsetOfChildLayoutAtIndex:index]; elementBounds.origin.x += elementOffset.x; @@ -206,7 +212,7 @@ if (point.x < self.bounds.size.width / 2) { return [NSIndexPath indexPathWithIndex:0]; } else { - return [NSIndexPath indexPathWithIndex:self.expression.length]; + return [NSIndexPath indexPathWithIndex:self.expression.countSymbols]; } } @@ -217,21 +223,8 @@ (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; CGContextSaveGState(context); -#ifdef MPDEBUG_DRAW_ORIGIN - [[NSColor blueColor] set]; - [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(-2, -2, 4, 4)] fill]; -#endif - [[NSColor textColor] set]; - -#ifdef MPDEBUG_DRAW_BOUNDS - [[NSColor greenColor] set]; - [[NSBezierPath bezierPathWithRect:self.bounds] stroke]; - [[NSColor textColor] set]; -#endif - - if (self.expression.numberOfElements == 0) { - + if (self.expression.countElements == 0) { CGContextRestoreGState(context); NSBezierPath *path = [NSBezierPath bezierPathWithRect:NSMakeRect(0, kMPEmptyBoxDrawingYOrigin, kMPEmptyBoxDrawingWidth, kMPEmptyBoxDrawingHeight)]; path.lineWidth = 0.5; @@ -245,7 +238,7 @@ // Track the x position CGFloat x = 0; - for (NSUInteger index = 0; index < self.expression.numberOfElements; index++) { + for (NSUInteger index = 0; index < self.expression.countElements; index++) { // The current element id element = [self.expression elementAtIndex:index]; NSRect elementBounds = [self boundsOfElementAtIndex:index]; @@ -258,16 +251,15 @@ // Move to the appropriate position CGContextSetTextPosition(context, x, 0); -#ifdef MPDEBUG_DRAW_BASELINE - [[NSColor redColor] set]; - NSRectFill(NSMakeRect(x, -1, elementBounds.size.width, 1)); - [[NSColor textColor] set]; -#endif - // Perform the drawing CTLineDraw(line, context); CFRelease(line); + + if (index < self.expression.countElements-1 && [[self.expression elementAtIndex:index+1] isKindOfClass:[MPPowerFunction class]]) { + MPPowerFunctionLayout *layout = (MPPowerFunctionLayout *)[self childLayoutAtIndex:index+1]; + layout.baseBounds = elementBounds; + } } else { // Let the child layout draw itself MPLayout *layout = [self childLayoutAtIndex:index]; diff --git a/MathPad/MPExpressionStorage.m b/MathPad/MPExpressionStorage.m index b1f3f05..3eed689 100644 --- a/MathPad/MPExpressionStorage.m +++ b/MathPad/MPExpressionStorage.m @@ -36,7 +36,7 @@ self.rootLayout.flipped = expressionView.isFlipped; } -- (void)didChangeElementsInIndexedRangePath:(MPRangePath *)rangePath +- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)replacementLength { if (rangePath.location.length == 0) { diff --git a/MathPad/MPExpressionTokenizer.h b/MathPad/MPExpressionTokenizer.h index 8b8b012..e0e3333 100644 --- a/MathPad/MPExpressionTokenizer.h +++ b/MathPad/MPExpressionTokenizer.h @@ -11,6 +11,6 @@ @interface MPExpressionTokenizer : NSObject -- (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's ++ (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's @end diff --git a/MathPad/MPExpressionTokenizer.m b/MathPad/MPExpressionTokenizer.m index 77d5577..7cd7ec6 100644 --- a/MathPad/MPExpressionTokenizer.m +++ b/MathPad/MPExpressionTokenizer.m @@ -18,21 +18,25 @@ @implementation MPExpressionTokenizer -- (NSArray *)tokenizeExpression:(MPExpression *)expression ++ (NSArray *)tokenizeExpression:(MPExpression *)expression { NSMutableArray *tokens = [[NSMutableArray alloc] init]; - for (NSUInteger index = 0; index < expression.numberOfElements; index++) { - id element = [expression elementAtIndex:index]; + NSUInteger symbolIndex = 0; + for (NSUInteger index = 0; index < [expression countItemsInReferenceFrame:MPElementReferenceFrame]; index++) { + id element = [expression itemAtIndex:index referenceFrame:MPElementReferenceFrame]; if ([element isFunction]) { [tokens addObject:element]; } else { - [tokens addObjectsFromArray:[self tokenizeElement:(NSString *)element]]; + [tokens addObjectsFromArray:[self tokenizeElement:(NSString *)element + elementSymbolIndex:symbolIndex]]; } + symbolIndex += element.length; } return tokens; } -- (NSArray *)tokenizeElement:(NSString *)element ++ (NSArray *)tokenizeElement:(NSString *)element + elementSymbolIndex:(NSUInteger)symbolIndex { NSUInteger lexLocation = 0; @@ -40,7 +44,7 @@ NSString *regexStringFormat = @"\\A(?:" @"(\\*)|" @"([+-](?:\\s*[+-])*)|" - @"((?:\\d+(?:%@\\d+)?)|(?:\\s%@\\d+))|" + @"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|" @"(sin)|" @"(cos)|" @"(tan)|" @@ -109,9 +113,11 @@ } lexLocation = NSMaxRange(range); + NSString *tokenStringValue = [element substringWithRange:range]; + range.location += symbolIndex; [tokens addObject:[[MPToken alloc] initWithTokenType:tokenType range:range - inString:element]]; + stringValue:tokenStringValue]]; } return tokens; diff --git a/MathPad/MPExpressionView.h b/MathPad/MPExpressionView.h index 99ccf13..73fec97 100644 --- a/MathPad/MPExpressionView.h +++ b/MathPad/MPExpressionView.h @@ -24,9 +24,6 @@ @property (readonly, nonatomic, strong) MPExpressionStorage *expressionStorage; -// @property (nonatomic, getter = isEditable) BOOL editable; -@property (nonatomic) BOOL allowsIntelligentReplacements; - @property (nonatomic, strong) MPRangePath *selection; @property (nonatomic, strong) MPParseError *error; @@ -34,6 +31,7 @@ @property (nonatomic) SEL action; #pragma mark Actions +- (IBAction)switchRadiansDegrees:(id)sender; - (IBAction)showFunctions:(id)sender; @end diff --git a/MathPad/MPExpressionView.m b/MathPad/MPExpressionView.m index 4b89886..989875b 100644 --- a/MathPad/MPExpressionView.m +++ b/MathPad/MPExpressionView.m @@ -11,8 +11,11 @@ #import "MPExpressionLayout.h" #import "MPFunctionLayout.h" +#import "MPPowerFunction.h" + #import "MPRangePath.h" +#import "MPMathRules.h" #import "NSIndexPath+MPAdditions.h" #import "MPSumFunction.h" @@ -24,10 +27,14 @@ @interface MPExpressionView () +@property (nonatomic, strong) NSButton *radiansDegreesButton; + @property (nonatomic, strong) NSButton *functionsButton; @property (nonatomic, strong) NSPopover *functionsPopover; @property (nonatomic, strong) MPFunctionsViewController *functionsViewController; +@property (nonatomic, strong) NSTextField *errorLabel; + @property (nonatomic, strong) NSTimer *caretTimer; @property (nonatomic) NSTimeInterval caretBlinkRate; @property (nonatomic) BOOL caretVisible; @@ -117,20 +124,22 @@ MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath]; NSUInteger locationInTarget = selectionPath.lastIndex; NSUInteger locationInElement; - NSUInteger targetElementIndex = [targetExpression indexOfElementAtLocation:locationInTarget - offset:&locationInElement]; + NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame + offset:&locationInElement]; id targetElement; // There is only a target element if the selection is not the last location in an expression - if (targetElementIndex < targetExpression.numberOfElements) { + if (targetElementIndex < targetExpression.countElements) { targetElement = [targetExpression elementAtIndex:targetElementIndex]; } - if (!selectWords && !extendingSelection && (locationInElement == 0 || locationInTarget == targetExpression.length)) { + if (!selectWords && !extendingSelection && (locationInElement == 0 || locationInTarget == targetExpression.countSymbols)) { // First or last index in an element or expression // Last element in the expression - if (locationInTarget == targetExpression.length) { + if (locationInTarget == targetExpression.countSymbols) { // The selection is inside a function and should proceed if (selectionPath.length > 1) { NSIndexPath *functionPath = [[selectionPath indexPathByRemovingLastIndex] indexPathByRemovingLastIndex]; @@ -141,7 +150,9 @@ // The function is to be exited if (newChildIndex == NSNotFound) { targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]]; - NSUInteger functionLocationInExpression = [targetExpression locationOfElementAtIndex:functionPath.lastIndex]; + NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression+1]; } else { return [[targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex] indexPathByAddingIndex:0]; @@ -159,9 +170,11 @@ return [[targetFunctionPath indexPathByAddingIndex:leadingChildIndex] indexPathByAddingIndex:0]; } } - } else if (locationInTarget < targetExpression.length) { + } else if (locationInTarget < targetExpression.countSymbols) { if (selectWords) { - locationInTarget = [targetExpression locationOfElementAtIndex:targetElementIndex+1]; + locationInTarget = [targetExpression convertIndex:targetElementIndex+1 + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; } else { locationInTarget++; } @@ -177,8 +190,10 @@ MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath]; NSUInteger locationInTarget = selectionPath.lastIndex; NSUInteger locationInElement; - NSUInteger targetElementIndex = [targetExpression indexOfElementAtLocation:locationInTarget - offset:&locationInElement]; + NSUInteger targetElementIndex = [targetExpression convertIndex:locationInTarget + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame + offset:&locationInElement]; NSUInteger previousElementIndex = targetElementIndex - (locationInElement == 0 ? 1 : 0); id previousElement; @@ -198,12 +213,14 @@ // The function is to be exited if (newChildIndex == NSNotFound) { targetExpression = [self.expressionStorage elementAtIndexPath:[functionPath indexPathByRemovingLastIndex]]; - NSUInteger functionLocationInExpression = [targetExpression locationOfElementAtIndex:functionPath.lastIndex]; + NSUInteger functionLocationInExpression = [targetExpression convertIndex:functionPath.lastIndex + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; return [functionPath indexPathByReplacingLastIndexWithIndex:functionLocationInExpression]; } else { targetExpressionPath = [targetExpressionPath indexPathByReplacingLastIndexWithIndex:newChildIndex]; targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath]; - return [targetExpressionPath indexPathByAddingIndex:targetExpression.length]; + return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols]; } } // else the selection does not change @@ -218,7 +235,7 @@ targetExpressionPath = [targetFunctionPath indexPathByAddingIndex:trailingChildIndex]; targetExpression = [self.expressionStorage elementAtIndexPath:targetExpressionPath]; - return [targetExpressionPath indexPathByAddingIndex:targetExpression.length]; + return [targetExpressionPath indexPathByAddingIndex:targetExpression.countSymbols]; } } } else if (locationInTarget > 0) { @@ -226,7 +243,9 @@ if (locationInElement == 0) { targetElementIndex--; } - locationInTarget = [targetExpression locationOfElementAtIndex:targetElementIndex]; + locationInTarget = [targetExpression convertIndex:targetElementIndex + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; } else { locationInTarget--; } @@ -258,10 +277,14 @@ NSUInteger newIndex = [newSelectionPath indexAtPosition:commonPath.length]; if (commonPath.length < anchorPath.length-1) { - anchorIndex = [closestCommonAncestor locationOfElementAtIndex:anchorIndex]; + anchorIndex = [closestCommonAncestor convertIndex:anchorIndex + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; } if (commonPath.length < newSelectionPath.length-1) { - newIndex = [closestCommonAncestor locationOfElementAtIndex:newIndex]; + newIndex = [closestCommonAncestor convertIndex:newIndex + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; } NSUInteger minIndex = MIN(anchorIndex, newIndex); @@ -313,6 +336,7 @@ _expressionStorage = expressionStorage; [self initializeButtons]; + [self initializeErrorLabel]; self.selection = [[MPRangePath alloc] initWithRange:NSMakeRange(0, 0)]; self.caretBlinkRate = 1.0; @@ -321,10 +345,23 @@ - (void)initializeButtons { + NSButton *radiansDegreesButton = self.radiansDegreesButton; + [self addSubview:radiansDegreesButton]; NSButton *functionsButton = self.functionsButton; [self addSubview:functionsButton]; - NSDictionary *variableBindings = NSDictionaryOfVariableBindings(functionsButton); - [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"[functionsButton]-10-|" + + NSDictionary *variableBindings = NSDictionaryOfVariableBindings(radiansDegreesButton, functionsButton); + + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[radiansDegreesButton]" + options:0 + metrics:nil + views:variableBindings]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-10-[radiansDegreesButton]" + options:0 + metrics:nil + views:variableBindings]]; + + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[functionsButton]-10-|" options:0 metrics:nil views:variableBindings]]; @@ -337,6 +374,21 @@ constant:0]]; } +- (void)initializeErrorLabel +{ + NSTextField *errorLabel = self.errorLabel; + [self addSubview:errorLabel]; + NSDictionary *variableBindings = NSDictionaryOfVariableBindings(errorLabel); + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[errorLabel]" + options:0 + metrics:nil + views:variableBindings]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[errorLabel]-10-|" + options:0 + metrics:nil + views:variableBindings]]; +} + #pragma mark Properties - (void)setExpressionStorage:(MPExpressionStorage *)expressionStorage { @@ -356,16 +408,36 @@ - (void)setError:(MPParseError *)error { _error = error; + self.errorLabel.stringValue = error.localizedErrorMessage != nil ? error.localizedErrorMessage : @""; self.needsDisplay = YES; } +- (NSButton *)radiansDegreesButton +{ + if (!_radiansDegreesButton) { + NSButton *radiansDegreesButton = [[NSButton alloc] initWithFrame:NSZeroRect]; + radiansDegreesButton.translatesAutoresizingMaskIntoConstraints = NO; + radiansDegreesButton.buttonType = NSMomentaryPushInButton; + radiansDegreesButton.bordered = YES; + radiansDegreesButton.bezelStyle = NSRoundedBezelStyle; + radiansDegreesButton.imagePosition = NSNoImage; + radiansDegreesButton.alignment = NSCenterTextAlignment; + radiansDegreesButton.title = NSLocalizedString([MPMathRules sharedRules].isUsingDegrees ? @"Deg" : @"Rad", nil); + radiansDegreesButton.font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + radiansDegreesButton.target = self; + radiansDegreesButton.action = @selector(switchRadiansDegrees:); + _radiansDegreesButton = radiansDegreesButton; + } + return _radiansDegreesButton; +} + - (NSButton *)functionsButton { if (!_functionsButton) { NSBundle *frameworkBundle = [NSBundle bundleForClass:[self class]]; NSImage *image = [frameworkBundle imageForResource:@"FunctionsButtonDisclosure"]; [image setName:@"FunctionsButtonDisclosure"]; - NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect]; button.translatesAutoresizingMaskIntoConstraints = NO; button.target = self; button.action = @selector(showFunctions:); @@ -382,7 +454,28 @@ return _functionsButton; } +- (NSTextField *)errorLabel +{ + if (!_errorLabel) { + NSTextField *label = [[NSTextField alloc] initWithFrame:NSZeroRect]; + label.textColor = [NSColor redColor]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.bezeled = NO; + label.drawsBackground = NO; + label.editable = NO; + label.selectable = NO; + _errorLabel = label; + } + return _errorLabel; +} + #pragma mark Actions +- (void)switchRadiansDegrees:(id)sender +{ + [MPMathRules sharedRules].isUsingDegrees = ![MPMathRules sharedRules].isUsingDegrees; + self.radiansDegreesButton.title = NSLocalizedString([MPMathRules sharedRules].isUsingDegrees ? @"Deg" : @"Rad", nil); +} + - (void)showFunctions:(id)sender { if (self.functionsPopover == nil || self.functionsViewController == nil) { @@ -404,10 +497,14 @@ - (void)insertFunction:(MPFunction *)function { [self.functionsPopover close]; - [self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[function]]; + [self.expressionStorage replaceItemsInRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame + withElements:@[function]]; MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; - NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex - offset:NULL]]; + NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame]; + NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex]; MPFunctionLayout *functionLayout = (MPFunctionLayout *)[self.expressionStorage.rootLayout childLayoutAtIndexPath:functionPath]; self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:functionLayout.indexOfLeadingChild] indexPathByAddingIndex:0], 0); } @@ -475,15 +572,19 @@ [self insertParenthesisFunction:nil]; return; } - - + if (theEvent.keyCode == 10) { + [self insertPowerFunction]; + return; + } NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]]; NSMutableCharacterSet *allowedCharacters = [NSMutableCharacterSet alphanumericCharacterSet]; [allowedCharacters addCharactersInString:[NSString stringWithFormat:@"+-*= !%@", decimalSeparator]]; if (characters.length == 1 && [characters stringByTrimmingCharactersInSet:allowedCharacters].length == 0) { - [self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[characters]]; + [self.expressionStorage replaceItemsInRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame + withElements:@[characters]]; self.selection = MPMakeRangePath([self.selection.location indexPathByIncrementingLastIndex], 0); } else { [self interpretKeyEvents:@[theEvent]]; @@ -492,15 +593,37 @@ - (void)insertParenthesisFunction:(id)sender { - MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection]; + MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame]; MPParenthesisFunction *function = [[MPParenthesisFunction alloc] init]; function.expression = selectedElementsExpression; - [self.expressionStorage replaceSymbolsInRangePath:self.selection - withElements:@[function]]; + [self.expressionStorage replaceItemsInRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame + withElements:@[function]]; MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; - NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex - offset:NULL]]; + NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame]; + NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex]; + self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length); +} + +- (void)insertPowerFunction +{ + MPExpression *selectedElementsExpression = [self.expressionStorage subexpressionWithRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame]; + MPPowerFunction *function = [[MPPowerFunction alloc] init]; + function.exponentExpression = selectedElementsExpression; + [self.expressionStorage replaceItemsInRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame + withElements:@[function]]; + + MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; + NSUInteger functionElementIndex = [targetExpression convertIndex:self.selection.location.lastIndex + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame]; + NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex]; self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], self.selection.length); } @@ -563,7 +686,7 @@ - (void)moveToEndOfLine:(id)sender { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; - self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.length], 0); + self.selection = MPMakeRangePath([self.selection.location indexPathByReplacingLastIndexWithIndex:targetExpression.countSymbols], 0); } - (void)moveLeftAndModifySelection:(id)sender @@ -676,22 +799,27 @@ - (void)selectAll:(id)sender { NSIndexPath *location = [NSIndexPath indexPathWithIndex:0]; - self.selection = MPMakeRangePath(location, self.expressionStorage.length); + self.selection = MPMakeRangePath(location, self.expressionStorage.countSymbols); } - (void)deleteBackward:(id)sender { if (self.selection.length > 0) { - [self.expressionStorage replaceSymbolsInRangePath:self.selection withElements:@[]]; + [self.expressionStorage replaceItemsInRangePath:self.selection + referenceFrame:MPSymbolReferenceFrame + withElements:@[]]; self.selection = MPMakeRangePath(self.selection.location, 0); } else if (self.selection.location.lastIndex > 0) { MPExpression *targetExpression = [self.expressionStorage elementAtIndexPath:[self.selection.location indexPathByRemovingLastIndex]]; - id elementToDelete = [targetExpression elementAtIndex:[targetExpression indexOfElementAtLocation:self.selection.location.lastIndex-1 - offset:NULL]]; + id elementToDelete = [targetExpression elementAtIndex:[targetExpression convertIndex:self.selection.location.lastIndex-1 + fromReferenceFrame:MPSymbolReferenceFrame + toReferenceFrame:MPElementReferenceFrame]]; if ([elementToDelete isFunction]) { self.selection = MPMakeRangePath(self.selection.location.indexPathByDecrementingLastIndex, 1); } else { - [targetExpression replaceSymbolsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1) withElements:@[]]; + [targetExpression replaceItemsInRange:NSMakeRange(self.selection.location.lastIndex-1, 1) + referenceFrame:MPSymbolReferenceFrame + withElements:@[]]; self.selection = MPMakeRangePath([self.selection.location indexPathByDecrementingLastIndex], 0); } } else { @@ -707,14 +835,20 @@ for (NSUInteger index = remainingIndexes.firstIndex; index <= remainingIndexes.lastIndex; index = [remainingIndexes indexGreaterThanIndex:index]) { - [remainder addObjectsFromArray:[function childAtIndex:index].elements]; + MPExpression *expression = [function childAtIndex:index]; + [remainder addObjectsFromArray:[expression allItemsInReferenceFrame:MPElementReferenceFrame]]; } NSIndexPath *newTargetExpressionPath = [functionPath indexPathByRemovingLastIndex]; MPExpression *newTargetExpression = [self.expressionStorage elementAtIndexPath:newTargetExpressionPath]; - NSIndexPath *newSelectionLocation = [functionPath indexPathByReplacingLastIndexWithIndex:[newTargetExpression locationOfElementAtIndex:functionPath.lastIndex]]; + NSUInteger newSelectionElementIndex = [newTargetExpression convertIndex:functionPath.lastIndex + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; + NSIndexPath *newSelectionLocation = [functionPath indexPathByReplacingLastIndexWithIndex:newSelectionElementIndex]; - [self.expressionStorage replaceSymbolsInRangePath:MPMakeRangePath(newSelectionLocation, 1) withElements:remainder]; + [self.expressionStorage replaceItemsInRangePath:MPMakeRangePath(newSelectionLocation, 1) + referenceFrame:MPSymbolReferenceFrame + withElements:remainder]; self.selection = MPMakeRangePath(newSelectionLocation, 0); } } @@ -762,6 +896,24 @@ yBy:expressionOrigin.y]; [transform concat]; + // Draw the error + if (self.error) { + [[NSColor redColor] set]; + NSRect rect = [self.expressionStorage.rootLayout boundingRectForRangePath:self.error.rangePath]; + if (self.error.rangePath.length == 0) { + NSBezierPath *bezierPath = [NSBezierPath bezierPath]; + [bezierPath moveToPoint:rect.origin]; + [bezierPath lineToPoint:NSMakePoint(rect.origin.x - 5, rect.origin.y - 5)]; + [bezierPath moveToPoint:rect.origin]; + [bezierPath lineToPoint:NSMakePoint(rect.origin.x + 5, rect.origin.y - 5)]; + bezierPath.lineWidth = 2.0; + [bezierPath stroke]; + } else { + NSRect underlineRect = NSMakeRect(rect.origin.x, rect.origin.y + 2, rect.size.width, 2); + NSRectFill(underlineRect); + } + } + // Draw the selection if (self.caretVisible || self.selection.length > 0) { if (self.selection.length == 0) { diff --git a/MathPad/MPFunction+MPToken.m b/MathPad/MPFunction+MPToken.m index 94a939a..eb708f0 100644 --- a/MathPad/MPFunction+MPToken.m +++ b/MathPad/MPFunction+MPToken.m @@ -18,7 +18,10 @@ - (NSRange)range { NSUInteger selfIndex = [self.parent indexOfElement:self]; - return NSMakeRange([self.parent locationOfElementAtIndex:selfIndex], 1); + return NSMakeRange([self.parent convertIndex:selfIndex + fromReferenceFrame:MPElementReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame], + 1); } - (BOOL)exists diff --git a/MathPad/MPFunction.m b/MathPad/MPFunction.m index a36f335..61f4176 100644 --- a/MathPad/MPFunction.m +++ b/MathPad/MPFunction.m @@ -99,7 +99,7 @@ { NSUInteger selfIndex = [self.parent indexOfElement:self]; MPRangePath *newPath = MPMakeRangePath([rangePath.location indexPathByPreceedingIndex:selfIndex], rangePath.length); - [self.parent didChangeElementsInIndexedRangePath:newPath + [self.parent didChangeElementsInRangePath:newPath replacementLength:replacementLength]; } diff --git a/MathPad/MPFunctionLayout.m b/MathPad/MPFunctionLayout.m index 08ef102..1b48727 100644 --- a/MathPad/MPFunctionLayout.m +++ b/MathPad/MPFunctionLayout.m @@ -14,6 +14,7 @@ #import "MPSumFunctionLayout.h" #import "MPParenthesisFunction.h" #import "MPParenthesisFunctionLayout.h" +#import "MPPowerFunctionLayout.h" #import "NSIndexPath+MPAdditions.h" @@ -28,6 +29,8 @@ 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]; } return [[self alloc] initWithFunction:function parent:parent]; @@ -101,6 +104,16 @@ return [self indexPathForLocalMousePoint:point]; } +- (NSUInteger)indexOfLeadingChild +{ + return 0; +} + +- (NSUInteger)indexOfTrailingChild +{ + return 0; +} + - (NSUInteger)indexOfChildBeforeChildAtIndex:(NSUInteger)index { return NSNotFound; diff --git a/MathPad/MPFunctionsViewController.m b/MathPad/MPFunctionsViewController.m index 098506e..14d9b69 100644 --- a/MathPad/MPFunctionsViewController.m +++ b/MathPad/MPFunctionsViewController.m @@ -12,6 +12,7 @@ #import "MPSumFunction.h" #import "MPParenthesisFunction.h" +#import "MPPowerFunction.h" @class MPFunctionTemplateItem; @@ -197,11 +198,24 @@ static void *MPFunctionTemplateViewMouseOverContext = @"MPFunctionTemplateViewMo - (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"]; self.functionPrototypes = @[ - @{@"function": [[MPSumFunction alloc] init], + @{@"function": sumFunction, @"name": NSLocalizedString(@"Sum", @"Sum Function Name")}, - @{@"function": [[MPParenthesisFunction alloc] init], - @"name": NSLocalizedString(@"Parenthesis", @"Parenthesis 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")} ]; [self.collectionView addObserver:self forKeyPath:@"hoverItem" diff --git a/MathPad/MPMathRules.h b/MathPad/MPMathRules.h index 14e9302..989cf91 100644 --- a/MathPad/MPMathRules.h +++ b/MathPad/MPMathRules.h @@ -12,6 +12,7 @@ FOUNDATION_EXPORT NSString *MPMathRulesAllowsImplicitMultiplicationKey; FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthKey; FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey; FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey; +FOUNDATION_EXPORT NSString *MPMathRulesIsUsingDegreesKey; @interface MPMathRules : NSObject @@ -25,4 +26,6 @@ FOUNDATION_EXPORT NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey; @property (nonatomic) NSUInteger maximumOperatorChainLengthInMultiplication; // Default: 1, 0 means actually 0 @property (nonatomic) NSUInteger maximumOperatorChainLengthInFunction; // For sin, cos, tan. Default: 1 +@property (nonatomic) BOOL isUsingDegrees; + @end diff --git a/MathPad/MPMathRules.m b/MathPad/MPMathRules.m index 324506b..90936f8 100644 --- a/MathPad/MPMathRules.m +++ b/MathPad/MPMathRules.m @@ -12,6 +12,7 @@ NSString *MPMathRulesAllowsImplicitMultiplicationKey = @"MPMathRulesAllowsImplic NSString *MPMathRulesMaximumOperatorChainLengthKey = @"MPMathRulesMaximumOperatorChainLengthKey"; NSString *MPMathRulesMaximumOperatorChainLengthInMultiplicationKey = @"MPMathRulesMaximumOperatorChainLengthInMultiplicationKey"; NSString *MPMathRulesMaximumOperatorChainLengthInFunctionKey = @"MPMathRulesMaximumOperatorChainLengthInFunctionKey"; +NSString *MPMathRulesIsUsingDegreesKey = @"MPMathRulesIsUsingDegreesKey"; @implementation MPMathRules @@ -33,15 +34,18 @@ static MPMathRules *sharedRules; self = [super init]; if (self) { _usingUserDefaultValues = YES; - NSNumber *userDefaultsAllowImplicitMultiplication = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesAllowsImplicitMultiplicationKey]; - NSNumber *userDefaultsMaximumOperatorChainLength = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesMaximumOperatorChainLengthKey]; - NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey]; - NSNumber *userDefaultsMaximumOperatorChainLengthInFunction = [[NSUserDefaults standardUserDefaults] objectForKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey]; + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSNumber *userDefaultsAllowImplicitMultiplication = [userDefaults objectForKey:MPMathRulesAllowsImplicitMultiplicationKey]; + NSNumber *userDefaultsMaximumOperatorChainLength = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthKey]; + NSNumber *userDefaultsMaximumOperatorChainLengthInMultiplication = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey]; + NSNumber *userDefaultsMaximumOperatorChainLengthInFunction = [userDefaults objectForKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey]; + NSNumber *userDefaultsIsUsingDegrees = [userDefaults objectForKey:MPMathRulesIsUsingDegreesKey]; - _allowsImplicitMultiplication = userDefaultsAllowImplicitMultiplication != nil ? userDefaultsAllowImplicitMultiplication.boolValue : NO; + _allowsImplicitMultiplication = userDefaultsAllowImplicitMultiplication.boolValue; _maximumOperatorChainLength = userDefaultsMaximumOperatorChainLength != nil ? userDefaultsMaximumOperatorChainLength.unsignedIntegerValue : 2; _maximumOperatorChainLengthInMultiplication = userDefaultsMaximumOperatorChainLengthInMultiplication != nil ? userDefaultsMaximumOperatorChainLengthInMultiplication.unsignedIntegerValue : 1; _maximumOperatorChainLengthInFunction = userDefaultsMaximumOperatorChainLengthInFunction != nil ? userDefaultsMaximumOperatorChainLengthInFunction.unsignedIntegerValue : 1; + _isUsingDegrees = userDefaultsIsUsingDegrees.boolValue; } return self; } @@ -54,6 +58,7 @@ static MPMathRules *sharedRules; self.maximumOperatorChainLength = self.maximumOperatorChainLength; self.maximumOperatorChainLengthInMultiplication = self.maximumOperatorChainLengthInMultiplication; self.maximumOperatorChainLengthInFunction = self.maximumOperatorChainLengthInFunction; + self.isUsingDegrees = self.isUsingDegrees; } - (void)setAllowsImplicitMultiplication:(BOOL)allowsImplicitMultiplication @@ -69,7 +74,7 @@ static MPMathRules *sharedRules; { _maximumOperatorChainLength = maximumOperatorChainLength; if (self.isUsingUserDefaultValues) { - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInteger:maximumOperatorChainLength] + [[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLength) forKey:MPMathRulesMaximumOperatorChainLengthKey]; } } @@ -78,7 +83,7 @@ static MPMathRules *sharedRules; { _maximumOperatorChainLengthInMultiplication = maximumOperatorChainLengthInMultiplication; if (self.isUsingUserDefaultValues) { - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInteger:maximumOperatorChainLengthInMultiplication] + [[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInMultiplication) forKey:MPMathRulesMaximumOperatorChainLengthInMultiplicationKey]; } } @@ -87,9 +92,18 @@ static MPMathRules *sharedRules; { _maximumOperatorChainLengthInFunction = maximumOperatorChainLengthInFunction; if (self.isUsingUserDefaultValues) { - [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithUnsignedInteger:maximumOperatorChainLengthInFunction] + [[NSUserDefaults standardUserDefaults] setObject:@(maximumOperatorChainLengthInFunction) forKey:MPMathRulesMaximumOperatorChainLengthInFunctionKey]; } } +- (void)setIsUsingDegrees:(BOOL)isUsingDegrees +{ + _isUsingDegrees = isUsingDegrees; + if (self.isUsingUserDefaultValues) { + [[NSUserDefaults standardUserDefaults] setBool:isUsingDegrees + forKey:MPMathRulesIsUsingDegreesKey]; + } +} + @end diff --git a/MathPad/MPParenthesisFunction.m b/MathPad/MPParenthesisFunction.m index 742c7cc..6093e7b 100644 --- a/MathPad/MPParenthesisFunction.m +++ b/MathPad/MPParenthesisFunction.m @@ -14,13 +14,6 @@ MPFunctionAccessorImplementation(Expression, _expression) -//- (void)setExpression:(MPExpression *)expression -//{ -// _expression.parent = nil; -// _expression = expression; -// _expression.parent = self; -//} - - (NSArray *)childrenAccessors { return @[@"expression"]; @@ -28,7 +21,7 @@ MPFunctionAccessorImplementation(Expression, _expression) - (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error { - MPExpressionEvaluator *evaluator = self.expression.evaluator; + MPExpressionEvaluator *evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.expression]; return [evaluator parseExpectingVariable:NO error:error]; } diff --git a/MathPad/MPParenthesisFunctionLayout.m b/MathPad/MPParenthesisFunctionLayout.m index 947cb20..7894a31 100644 --- a/MathPad/MPParenthesisFunctionLayout.m +++ b/MathPad/MPParenthesisFunctionLayout.m @@ -128,16 +128,6 @@ return [NSIndexPath indexPathWithIndex:0]; } -- (NSUInteger)indexOfLeadingChild -{ - return 0; -} - -- (NSUInteger)indexOfTrailingChild -{ - return 0; -} - - (NSIndexSet *)indexesOfRemainingChildren { return [NSIndexSet indexSetWithIndex:0]; diff --git a/MathPad/MPParseError.m b/MathPad/MPParseError.m index c6a9db3..5923309 100644 --- a/MathPad/MPParseError.m +++ b/MathPad/MPParseError.m @@ -36,7 +36,7 @@ - (MPRangePath *)rangePath { NSIndexPath *location = self.pathToExpression; - location = [location indexPathByPreceedingIndex:self.errorRange.location]; + location = [location indexPathByAddingIndex:self.errorRange.location]; MPRangePath *rangePath = [MPRangePath rangePathWithLocation:location length:self.errorRange.length]; return rangePath; } diff --git a/MathPad/MPPowerFunction.h b/MathPad/MPPowerFunction.h index 1cc123b..cfb8131 100644 --- a/MathPad/MPPowerFunction.h +++ b/MathPad/MPPowerFunction.h @@ -10,8 +10,7 @@ @interface MPPowerFunction : MPFunction +@property (nonatomic, strong) MPTerm *baseTerm; @property (nonatomic, strong) MPExpression *exponentExpression; -@property (nonatomic, strong) MPTerm *baseTerm; - @end diff --git a/MathPad/MPPowerFunction.m b/MathPad/MPPowerFunction.m index d6782a0..55807d7 100644 --- a/MathPad/MPPowerFunction.m +++ b/MathPad/MPPowerFunction.m @@ -20,8 +20,9 @@ MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression) - (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error { - MPTerm *exponentTerm = [self.exponentExpression.evaluator parseExpectingVariable:NO - error:error]; + MPExpressionEvaluator *exponent = [[MPExpressionEvaluator alloc] initWithExpression:self.exponentExpression]; + MPTerm *exponentTerm = [exponent parseExpectingVariable:NO + error:error]; if (exponentTerm == nil) { return nil; } diff --git a/MathPad/MPPowerFunctionLayout.h b/MathPad/MPPowerFunctionLayout.h new file mode 100644 index 0000000..9aeced3 --- /dev/null +++ b/MathPad/MPPowerFunctionLayout.h @@ -0,0 +1,19 @@ +// +// MPPowerFunctionLayout.h +// MathPad +// +// Created by Kim Wittenburg on 30.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunctionLayout.h" + +#import "MPPowerFunction.h" + +@interface MPPowerFunctionLayout : MPFunctionLayout + +@property (nonatomic) NSRect baseBounds; + +- (MPPowerFunction *)powerFunction; + +@end diff --git a/MathPad/MPPowerFunctionLayout.m b/MathPad/MPPowerFunctionLayout.m new file mode 100644 index 0000000..b89b5ae --- /dev/null +++ b/MathPad/MPPowerFunctionLayout.m @@ -0,0 +1,68 @@ +// +// MPPowerFunctionLayout.m +// MathPad +// +// Created by Kim Wittenburg on 30.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPPowerFunctionLayout.h" + +#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 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 / 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 +{ + MPLayout *exponentLayout = [self childLayoutAtIndex:0]; + [exponentLayout drawAtPoint:[self offsetOfChildLayoutAtIndex:0]]; +} + +@end diff --git a/MathPad/MPRangePath.h b/MathPad/MPRangePath.h index 92edb20..0674a9c 100644 --- a/MathPad/MPRangePath.h +++ b/MathPad/MPRangePath.h @@ -251,6 +251,11 @@ @return A new expression. */ -- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath; // If location points to expression (in function) nil is returned +- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath + referenceFrame:(MPReferenceFrame)referenceFrame; + +- (void)replaceItemsInRangePath:(MPRangePath *)rangePath + referenceFrame:(MPReferenceFrame)referenceFrame + withElements:(NSArray *)elements; @end diff --git a/MathPad/MPRangePath.m b/MathPad/MPRangePath.m index 9e4dcd0..c2c46a2 100644 --- a/MathPad/MPRangePath.m +++ b/MathPad/MPRangePath.m @@ -156,13 +156,33 @@ @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]; + 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 diff --git a/MathPad/MPSumFunction.m b/MathPad/MPSumFunction.m index 5c94637..3698e87 100644 --- a/MathPad/MPSumFunction.m +++ b/MathPad/MPSumFunction.m @@ -29,12 +29,12 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression) #pragma mark Evaluating Functions - (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error { - MPExpressionEvaluator *startEvaluator = self.startExpression.evaluator; + MPExpressionEvaluator *startEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.startExpression]; MPTerm *start = [startEvaluator parseExpectingVariable:YES error:error]; ReturnIfNil(start); - MPExpressionEvaluator *targetEvaluator = self.targetExpression.evaluator; + MPExpressionEvaluator *targetEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.targetExpression]; MPTerm *target = [targetEvaluator parseExpectingVariable:NO error:error]; ReturnIfNil(target); @@ -43,7 +43,7 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression) [[MPEvaluationContext sharedContext] push]; [[MPEvaluationContext sharedContext] defineVariable:variable withValue:[NSNull null]]; - MPExpressionEvaluator *sumEvaluator = self.sumExpression.evaluator; + MPExpressionEvaluator *sumEvaluator = [[MPExpressionEvaluator alloc] initWithExpression:self.sumExpression]; MPTerm *sum = [sumEvaluator parseExpectingVariable:NO error:error]; ReturnIfNil(sum); diff --git a/MathPad/MPSumFunctionLayout.m b/MathPad/MPSumFunctionLayout.m index 7eb9abb..3d6e40e 100644 --- a/MathPad/MPSumFunctionLayout.m +++ b/MathPad/MPSumFunctionLayout.m @@ -142,12 +142,6 @@ - (void)draw { -#ifdef MPDEBUG_DRAW_BASELINE - [[NSColor redColor] set]; - NSRectFill(NSMakeRect(0, -1, self.bounds.size.width, 1)); - [[NSColor textColor] set]; -#endif - // Get the current context CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; @@ -161,12 +155,6 @@ CGContextSetTextPosition(context, localLineBounds.origin.x, 0); CTLineDraw(line, context); -#ifdef MPDEBUG_DRAW_FUNCTION_BOUNDS - [[NSColor blueColor] set]; - [[NSBezierPath bezierPathWithRect:localLineBounds] stroke]; - [[NSColor textColor] set]; -#endif - // Draw the start function MPLayout *startExpressionLayout = [self childLayoutAtIndex:0]; NSPoint startExpressionLocation = [self offsetOfChildLayoutAtIndex:0]; diff --git a/MathPad/MPTerm.h b/MathPad/MPTerm.h index 09dfe4c..eb5f16c 100644 --- a/MathPad/MPTerm.h +++ b/MathPad/MPTerm.h @@ -23,6 +23,10 @@ - (instancetype)initWithCosOfTerm:(MPTerm *)term; - (instancetype)initWithTanOfTerm:(MPTerm *)term; +- (instancetype)initWithInverseSinOfTerm:(MPTerm *)term; +- (instancetype)initWithInverseCosOfTerm:(MPTerm *)term; +- (instancetype)initWithInverseTanOfTerm:(MPTerm *)term; + - (NSDecimalNumber *)evaluate; @end diff --git a/MathPad/MPTerm.m b/MathPad/MPTerm.m index 6549787..8b1d46b 100644 --- a/MathPad/MPTerm.m +++ b/MathPad/MPTerm.m @@ -8,6 +8,7 @@ #import "MPTerm.h" #import "MPEvaluationContext.h" +#import "MPMathRules.h" @interface MPTerm () @@ -66,7 +67,6 @@ { return [self initWithBlock:^NSDecimalNumber *{ NSDecimalNumber *termValue = [term evaluate]; - NSLog(@"Factorial of %@ = %f", termValue, tgamma(termValue.doubleValue + 1)); return [[NSDecimalNumber alloc] initWithDouble:tgamma(termValue.doubleValue + 1)]; }]; } @@ -75,6 +75,9 @@ { return [self initWithBlock:^NSDecimalNumber *{ NSDecimalNumber *termValue = [term evaluate]; + if ([MPMathRules sharedRules].isUsingDegrees) { + termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; + } return [[NSDecimalNumber alloc] initWithDouble:sin(termValue.doubleValue)]; }]; } @@ -83,6 +86,8 @@ { return [self initWithBlock:^NSDecimalNumber *{ NSDecimalNumber *termValue = [term evaluate]; + if ([MPMathRules sharedRules].isUsingDegrees) { + termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; } return [[NSDecimalNumber alloc] initWithDouble:cos(termValue.doubleValue)]; }]; } @@ -91,13 +96,54 @@ { return [self initWithBlock:^NSDecimalNumber *{ NSDecimalNumber *termValue = [term evaluate]; + if ([MPMathRules sharedRules].isUsingDegrees) { + termValue = [[termValue decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; } return [[NSDecimalNumber alloc] initWithDouble:tan(termValue.doubleValue)]; }]; } +- (instancetype)initWithInverseSinOfTerm:(MPTerm *)term +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *termValue = [term evaluate]; + NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:asin(termValue.doubleValue)]; + if ([MPMathRules sharedRules].isUsingDegrees) { + result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]]; + } + return result; + }]; +} + +- (instancetype)initWithInverseCosOfTerm:(MPTerm *)term +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *termValue = [term evaluate]; + NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:acos(termValue.doubleValue)]; + if ([MPMathRules sharedRules].isUsingDegrees) { + result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]]; + } + return result; }]; +} + +- (instancetype)initWithInverseTanOfTerm:(MPTerm *)term +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *termValue = [term evaluate]; + NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithDouble:atan(termValue.doubleValue)]; + if ([MPMathRules sharedRules].isUsingDegrees) { + result = [[result decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]]; + } + return result; }]; +} + - (NSDecimalNumber *)evaluate { - return self.block(); + @try { + return self.block(); + } + @catch (NSException *exception) { + return [NSDecimalNumber notANumber]; + } } @end diff --git a/MathPad/MPToken.h b/MathPad/MPToken.h index 0ab11d2..48ee6a8 100644 --- a/MathPad/MPToken.h +++ b/MathPad/MPToken.h @@ -41,10 +41,11 @@ typedef NS_ENUM(NSUInteger, MPTokenType) { @interface MPToken : NSObject - (instancetype)initEOFTokenAtLocation:(NSUInteger)eofLocation; -- (instancetype)initWithRange:(NSRange)range inString:(NSString *)input; +- (instancetype)initWithRange:(NSRange)range + stringValue:(NSString *)input; - (instancetype)initWithTokenType:(MPTokenType)tokenType range:(NSRange)range - inString:(NSString *)input; + stringValue:(NSString *)input; @end diff --git a/MathPad/MPToken.m b/MathPad/MPToken.m index 512684f..4b919e6 100644 --- a/MathPad/MPToken.m +++ b/MathPad/MPToken.m @@ -23,22 +23,22 @@ return self; } -- (instancetype)initWithRange:(NSRange)range inString:(NSString *)input +- (instancetype)initWithRange:(NSRange)range stringValue:(NSString *)input { self = [super init]; if (self) { _range = range; - _stringValue = [input substringWithRange:range].copy; + _stringValue = input.copy; } return self; } - (instancetype)initWithTokenType:(MPTokenType)tokenType range:(NSRange)range - inString:(NSString *)input + stringValue:(NSString *)input { self = [self initWithRange:range - inString:input]; + stringValue:input]; if (self) { _tokenType = tokenType; } diff --git a/MathPad/MPTokenStream.h b/MathPad/MPTokenStream.h index 3e0e54f..0dd4768 100644 --- a/MathPad/MPTokenStream.h +++ b/MathPad/MPTokenStream.h @@ -16,7 +16,7 @@ @property (nonatomic, copy) NSArray *tokens; @property (nonatomic, getter=isIgnoringWhitespaceTokens) BOOL ignoringWhitespaceTokens; // Default: YES -@property (nonatomic) NSUInteger currentLocation; +@property (nonatomic) NSUInteger currentTokenIndex; - (void)reset; - (BOOL)hasMoreTokens; diff --git a/MathPad/MPTokenStream.m b/MathPad/MPTokenStream.m index 99f3f9f..c258629 100644 --- a/MathPad/MPTokenStream.m +++ b/MathPad/MPTokenStream.m @@ -9,7 +9,6 @@ #import "MPTokenStream.h" @implementation MPTokenStream { - NSUInteger currentTokenIndex; NSUInteger eofLocation; } @@ -32,37 +31,34 @@ if (!self.isIgnoringWhitespaceTokens) { return; } - while (currentTokenIndex < self.tokens.count) { - MPToken *token = self.tokens[currentTokenIndex]; + while (self.currentTokenIndex < self.tokens.count) { + MPToken *token = self.tokens[self.currentTokenIndex]; if (token.tokenType != MPWhitespaceToken) { return; } - self.currentLocation = NSMaxRange(token.range); - ++currentTokenIndex; + ++self.currentTokenIndex; } } - (void)reset { - currentTokenIndex = 0; - self.currentLocation = 0; + self.currentTokenIndex = 0; [self skipWhitespaces]; } - (BOOL)hasMoreTokens { [self skipWhitespaces]; - return currentTokenIndex < self.tokens.count; + return self.currentTokenIndex < self.tokens.count; } - (MPToken *)nextToken { [self skipWhitespaces]; - if (currentTokenIndex >= self.tokens.count) { + if (self.currentTokenIndex >= self.tokens.count) { return [[MPToken alloc] initEOFTokenAtLocation:eofLocation]; } else { - MPToken *token = self.tokens[currentTokenIndex++]; - self.currentLocation = NSMaxRange(token.range); + MPToken *token = self.tokens[self.currentTokenIndex++]; return token; } } @@ -70,15 +66,14 @@ - (MPToken *)nextTokenOfType:(MPTokenType)type { [self skipWhitespaces]; - if (currentTokenIndex >= self.tokens.count) { + if (self.currentTokenIndex >= self.tokens.count) { return nil; } else { - MPToken *token = self.tokens[currentTokenIndex]; + MPToken *token = self.tokens[self.currentTokenIndex]; if (token.tokenType != type) { return nil; } - ++currentTokenIndex; - self.currentLocation = NSMaxRange(token.range); + ++self.currentTokenIndex; return token; } } diff --git a/MathPadTests/MPExpressionTests.m b/MathPadTests/MPExpressionTests.m index d5c8eb6..e29eb81 100644 --- a/MathPadTests/MPExpressionTests.m +++ b/MathPadTests/MPExpressionTests.m @@ -29,21 +29,21 @@ // Test expression with function testExpression = [[MPExpression alloc] initWithElement:[[MPFunction alloc] init]]; - XCTAssertEqual([testExpression numberOfElements], 1); + XCTAssertEqual([testExpression countElements], 1); XCTAssertEqualObjects([testExpression elementAtIndex:0], [[MPFunction alloc] init]); testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"17", [[MPFunction alloc] init]]]; - XCTAssertEqual([testExpression numberOfElements], 4); + XCTAssertEqual([testExpression countElements], 4); XCTAssertEqualObjects([testExpression elementAtIndex:2], @"17"); // Test expression with subsequent strings testExpression = [[MPExpression alloc] initWithElements:@[@"1234", @"5678"]]; - XCTAssertEqual([testExpression numberOfElements], 1); + XCTAssertEqual([testExpression countElements], 1); XCTAssertEqualObjects([testExpression elementAtIndex:0], @"12345678"); // Test expression with only empty string testExpression = [[MPExpression alloc] initWithElement:@""]; - XCTAssertEqual([testExpression numberOfElements], 0); + XCTAssertEqual([testExpression countElements], 0); } - (void)testSubexpressions { @@ -52,77 +52,77 @@ /********** subexpressionFromIndex: **********/ // Test with start index at front MPExpression *subexpression = [testExpression subexpressionFromLocation:0]; - XCTAssertEqual([subexpression numberOfElements], 4); + XCTAssertEqual([subexpression countElements], 4); XCTAssertEqualObjects([subexpression elementAtIndex:0], @"1234"); // Test with start index in first element subexpression = [testExpression subexpressionFromLocation:2]; - XCTAssertEqual([subexpression numberOfElements], 4); + XCTAssertEqual([subexpression countElements], 4); XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34"); // Test with start index in middle element starting with a literal subexpression = [testExpression subexpressionFromLocation:6]; - XCTAssertEqual([subexpression numberOfElements], 2); + XCTAssertEqual([subexpression countElements], 2); XCTAssertEqualObjects([subexpression elementAtIndex:0], @"7"); // Test with start index in middle element starting with a function subexpression = [testExpression subexpressionFromLocation:4]; - XCTAssertEqual([subexpression numberOfElements], 3); + XCTAssertEqual([subexpression countElements], 3); XCTAssertEqualObjects([subexpression elementAtIndex:0], [[MPFunction alloc] init]); // Test with start index in last element subexpression = [testExpression subexpressionFromLocation:7]; - XCTAssertEqual([subexpression numberOfElements], 1); + XCTAssertEqual([subexpression countElements], 1); XCTAssertEqualObjects([subexpression elementAtIndex:0], [[MPFunction alloc] init]); // Test with start index at end subexpression = [testExpression subexpressionFromLocation:8]; - XCTAssertEqual([subexpression numberOfElements], 0); + XCTAssertEqual([subexpression countElements], 0); /********** subexpressionToIndex: **********/ // Test with end index at front subexpression = [testExpression subexpressionToLocation:0]; - XCTAssertEqual([subexpression numberOfElements], 0); + XCTAssertEqual([subexpression countElements], 0); // Test with end index in first Element subexpression = [testExpression subexpressionToLocation:2]; - XCTAssertEqual([subexpression numberOfElements], 1); + XCTAssertEqual([subexpression countElements], 1); XCTAssertEqualObjects([subexpression elementAtIndex:0], @"12"); // Test with end index in middle Element ending with a literal subexpression = [testExpression subexpressionToLocation:6]; - XCTAssertEqual([subexpression numberOfElements], 3); + XCTAssertEqual([subexpression countElements], 3); XCTAssertEqualObjects([subexpression elementAtIndex:2], @"1"); // Test with end index in middle Element ending with a function subexpression = [testExpression subexpressionToLocation:5]; - XCTAssertEqual([subexpression numberOfElements], 2); + XCTAssertEqual([subexpression countElements], 2); XCTAssertEqualObjects([subexpression elementAtIndex:1], [[MPFunction alloc] init]); // Test with end index at end subexpression = [testExpression subexpressionToLocation:8]; - XCTAssertEqual([subexpression numberOfElements], 4); + XCTAssertEqual([subexpression countElements], 4); XCTAssertEqualObjects([subexpression elementAtIndex:3], [[MPFunction alloc] init]); /********** subexpressionWithRange: **********/ // Test with empty range subexpression = [testExpression subexpressionWithRange:NSMakeRange(4, 0)]; - XCTAssertEqual([subexpression numberOfElements], 0); + XCTAssertEqual([subexpression countElements], 0); // Test with start and end in first element subexpression = [testExpression subexpressionWithRange:NSMakeRange(1, 2)]; - XCTAssertEqual([subexpression numberOfElements], 1); + XCTAssertEqual([subexpression countElements], 1); XCTAssertEqualObjects([subexpression elementAtIndex:0], @"23"); // Test with start in first and end in middle after function subexpression = [testExpression subexpressionWithRange:NSMakeRange(2, 3)]; - XCTAssertEqual([subexpression numberOfElements], 2); + XCTAssertEqual([subexpression countElements], 2); XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34"); XCTAssertEqualObjects([subexpression elementAtIndex:1], [[MPFunction alloc] init]); // Test with start in first and end in middle after literal subexpression = [testExpression subexpressionWithRange:NSMakeRange(2, 4)]; - XCTAssertEqual([subexpression numberOfElements], 3); + XCTAssertEqual([subexpression countElements], 3); XCTAssertEqualObjects([subexpression elementAtIndex:0], @"34"); XCTAssertEqualObjects([subexpression elementAtIndex:2], @"1"); } @@ -211,25 +211,25 @@ MPExpression *testExpression = [[MPExpression alloc] initWithElements:@[@"1234", [[MPFunction alloc] init], @"5678", [[MPFunction alloc] init]]]; [testExpression appendElement:@"90"]; - XCTAssertEqual([testExpression numberOfElements], 5); + XCTAssertEqual([testExpression countElements], 5); // 1234 [] 5678 [] 90 [testExpression deleteElementsInRange:NSMakeRange(2, 4)]; - XCTAssertEqual([testExpression numberOfElements], 3); + XCTAssertEqual([testExpression countElements], 3); // 12678 [] 90 [testExpression deleteElementsInRange:NSMakeRange(0, 2)]; - XCTAssertEqual([testExpression numberOfElements], 3); + XCTAssertEqual([testExpression countElements], 3); XCTAssertEqualObjects([testExpression elementAtIndex:0], @"678"); [testExpression insertElement:[[MPFunction alloc] init] atLocation:2]; - XCTAssertEqual([testExpression numberOfElements], 5); + XCTAssertEqual([testExpression countElements], 5); // 67 [] 8 [] 90 [testExpression replaceSymbolsInRange:NSMakeRange(2, 5) withElements:@[[[MPFunction alloc] init]]]; - XCTAssertEqual([testExpression numberOfElements], 2); + XCTAssertEqual([testExpression countElements], 2); XCTAssertEqualObjects([testExpression elementAtIndex:0], @"67"); // 67 [] }