diff --git a/MathPad.xcodeproj/project.pbxproj b/MathPad.xcodeproj/project.pbxproj index dafd4a9..0b0c1ce 100644 --- a/MathPad.xcodeproj/project.pbxproj +++ b/MathPad.xcodeproj/project.pbxproj @@ -60,7 +60,7 @@ 3B74BFB319A4C51800E5B5DE /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B74BFB219A4C51800E5B5DE /* CoreText.framework */; }; 3B78C85419C2DA5300516335 /* MPEvaluationContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B78C85219C2DA5300516335 /* MPEvaluationContext.h */; }; 3B78C85519C2DA5300516335 /* MPEvaluationContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B78C85319C2DA5300516335 /* MPEvaluationContext.m */; }; - 3B7B3A1819CC44E4005849E5 /* MPExpressionTokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B7B3A1619CC44E4005849E5 /* MPExpressionTokenizer.h */; }; + 3B7B3A1819CC44E4005849E5 /* MPExpressionTokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B7B3A1619CC44E4005849E5 /* MPExpressionTokenizer.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3B7B3A1919CC44E4005849E5 /* MPExpressionTokenizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7B3A1719CC44E4005849E5 /* MPExpressionTokenizer.m */; }; 3B7B3A2C19CC467E005849E5 /* MPToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B7B3A2A19CC467E005849E5 /* MPToken.h */; }; 3B7B3A2D19CC467E005849E5 /* MPToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B7B3A2B19CC467E005849E5 /* MPToken.m */; }; @@ -425,7 +425,8 @@ 3BC46B4B19B38CB60033F13A /* Base Expression Classes */, 3B69B68219E6E8A50028E608 /* Expression Tree */, 3BB09EBC1905EF210080A5ED /* Functions */, - 3BC46B4C19B38CD20033F13A /* Independant Classes */, + 3B87E35B1900933200259938 /* MPRangePath.h */, + 3B87E35C1900933200259938 /* MPRangePath.m */, 3BC46B4D19B38CFB0033F13A /* Helpers */, ); name = Model; @@ -531,15 +532,6 @@ name = "Base Expression Classes"; sourceTree = ""; }; - 3BC46B4C19B38CD20033F13A /* Independant Classes */ = { - isa = PBXGroup; - children = ( - 3B87E35B1900933200259938 /* MPRangePath.h */, - 3B87E35C1900933200259938 /* MPRangePath.m */, - ); - name = "Independant Classes"; - sourceTree = ""; - }; 3BC46B4D19B38CFB0033F13A /* Helpers */ = { isa = PBXGroup; children = ( diff --git a/MathPad/MPExpression.h b/MathPad/MPExpression.h index 564631f..1ac3ee1 100644 --- a/MathPad/MPExpression.h +++ b/MathPad/MPExpression.h @@ -6,11 +6,65 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // + + +/*! + @const MPIllegalElementException + @brief Name for an exception that is raised if an invalid element is + added to an expression. + + @discussion This exception may be raised during initialization of an + expression or when the expression is mutated. This exception + always contains the @c MPIllegalElementExceptionElementKey key in + its @c userInfo dictionary. + */ +FOUNDATION_EXPORT NSString *const MPIllegalElementException; + + +/*! + @const MPIllegalElementExceptionElementKey + @brief Predefined key for the invalid element that caused an exception + to be raised. + + @discussion The invalid element can be of any type. + */ FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey; -FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain; -FOUNDATION_EXPORT NSString *const MPExpressionPathErrorKey; +/*! + @const MPMathKitErrorDomain + @brief Predefined error domain for errors from the MathKit framework. + + @discussion Errors in MathKit can occur during parsing of expressions or + during evaluation of expressions. These two can be distinguished + by the error code. Parsing errors have lower error codes. + Evaluation errors (math errors) have error codes from @c 100 upwards. + */ +FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain; + + +// TODO: Deal with this: +// FOUNDATION_EXPORT NSString *const MPPathToExpressionKey; +// FOUNDATION_EXPORT NSString *const MPRangeKey; + + +/*! + @function MPParseError + @brief Creates an @c NSError object in the @c MPMathKitErrorDomain. + + @discussion The created error is filled with the passed data if present. + + @param errorCode + The error code of the created error. + + @param errorDescription + The localized description of the created error. May be @c nil. + + @param underlyingError + The underlying error of the created error. May be @c nil. + + @return An @c NSError object initialized with the given data. + */ NS_INLINE NSError * MPParseError(NSInteger errorCode, NSString *errorDescription, NSError *underlyingError) { @@ -27,12 +81,41 @@ NS_INLINE NSError * MPParseError(NSInteger errorCode, return error; } + +/*! + @typedef MPReferenceFrame + @brief A reference frame describes what an @em item is. + + @discussion An item may be an element, a symbol or a token. A symbol is the + smallest possible size for an item. An element as well as a token + consist of one or more symbols. Similarly an element consists of + one or more tokens. + + @constant MPElementReferenceFrame + Specifies that items should be interpreted as elements. An + element is either a @c NSString object or a @c MPFunction object. + + @constant MPSymbolReferenceFrame + Specifies that items should be interpreted as symbols. A symbol + can be a single character (as they are for example typed in by + the user) or a @c MPFunction object. + + @constant MPTokenReferenceFrame + Specifies that items should be interpreted as tokens. A token is + a logical unit of content in an expression. This can be a single + character (e.g. '+'), an arbitrary number of characters (e.g. a + number) or a @c MPFunction object. + + All tokens must conform to the @c MPToken protocol. + */ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { MPElementReferenceFrame, MPSymbolReferenceFrame, MPTokenReferenceFrame }; + + @class MPExpression, MPFunction, MPRangePath, MPExpressionTree, MPExpressionEvaluator; @protocol MPExpressionElement, MPToken; @@ -42,45 +125,28 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { expression. @discussion Every expression consists of string elements (represented by the - @c NSString class) and functions (represented by the @c - MPFunction class) elements which both can be contained within an - expression. Functions in turn can have expressions as elements - (also called 'children' in this context). Both expressions and - functions are mutable. + @c NSString class) and function elements (represented by the @c + MPFunction class). Functions likewise can have expressions as + elements (also called @em children in this context). Both + expressions and functions are mutable. Through this organization + expression are organized in a tree-like structure called the + 'expression tree'. - Through this organization expression are organized in a tree-like - structure (called the 'expression tree') allowing easy and - logical access to each element. + @note There is also the @c MPExpressionTree class. That class provides + an alternative representation of an expression. The 'expression + tree' does not refer to that class. - To query the contents of an expressions there are two options: - You can query on a per-element basis (that is string elements and - function elements as described before). This is called the @em - index reference frame (or @em indexed reference frame). You also - can query an expression on a per-symbol basis. This is called the - @em location reference frame (or @c located reference frame). - Using the located reference frame is useful if you are dealing - with user actions since the user does not necessarily see a - visual separation between different elements but only sees the - symbols that are drawn on the screen. You can convert between the - two reference frames using the following methods: - @code -- (NSUInteger)indexOfElementAtSymbolLocation:offset: // Converts from the located to the indexed reference frame -- (NSUInteger)locationOfElementAtIndex: // Converts from the indexed to the located reference frame - @endcode - - In the indexed reference frame both functions and strings have a - dimension of 1. In the located reference frame the length of a - string is determined by sending it a @c -length message. In that - frame functions still have a length of 1. - An expression can evaluate itself giving you either a result or possibly an error if the expression was not constructed - correctly. + correctly or could not be evaluated. */ @interface MPExpression : NSObject #pragma mark Creation Methods +/*! + @methodgroup Creation Methods + */ /*! @@ -128,12 +194,15 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { element is copied and the copy is then added to the expression. The object in the @c elements array is not modified. - @return An expression containing the elements from @c elements. + @return An expression containing @c elements. */ - (instancetype)initWithElements:(NSArray *)elements; /* designated initializer */ #pragma mark Querying Expressions +/*! + @methodgroup Querying Expressions + */ /*! @@ -142,8 +211,9 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { @discussion Expressions are organized in a tree-like structure. Through this property an expression's containing function can be accessed. - @warning You should not set this property manually unless you are - implementing a subclass of @c MPFunction. + @warning You should never set this property manually. If you are + implementing a custom subclass of @c MPFunction use the @c + MPFunctionAccessorImplementation macro. @return The parent of the receiver or @c nil if the receiver is the root expression. @@ -170,7 +240,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { @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 + expressed in the element reference frame. If any of the indexes exceed the respective receiver's bounds a @c NSRangeException is raised. @@ -180,41 +250,62 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { /*! - @method numberOfElements - @brief Returns the number of elements in the receiver. + @method countItemsInReferenceFrame: + @brief Returns the number of items in the receiver in the respective + reference frame. - @discussion The number of elements may vary from the number of elements that - were added to the receiver (using either @c -initWithElements: or - one of the several mutation methods). The number of elements is - expressed in the indexed reference frame. (The respective method - for the located reference frame is @c -length). + @param referenceFrame + The reference frame that should be used to count the items. - @return The current number of elements in the receiver. + @return The current number of items in the receiver counted in the given + reference frame. */ - (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame; /*! - @method elementAtIndex: - @brief Returns the element at @c anIndex. + @method itemAtIndex:referenceFrame: + @brief Returns the item at @c anIndex. - @discussion The element is not copied before it is returned. So be aware that + @discussion The item is not copied before it is returned. So be aware that if you mutate a @c MPFunction object returned from this function the changes will be reflected in the receiver. - @note This method can also be called using indexed subscript - getter syntax. - @param anIndex - The index of the element expressed in the indexed reference - frame. If the index is greater than or equal to the number of - elements in the receiver an @c NSRangeException is raised. + The index of the item. If the index is greater than the number of + items for the respective reference frame an @c NSRangeException + is raised. - @return The element at @c anIndex. + @param referenceFrame + The reference frame that should be used to identify the item at + @c anIndex. + + @return The item at @c anIndex. */ - (id)itemAtIndex:(NSUInteger)anIndex referenceFrame:(MPReferenceFrame)referenceFrame; + +/*! + @method elementAtIndex:referenceFrame: + @brief Returns the element at the specified index. + + @discussion An element is either a string or a function. If @c anIndex + specifies a position inside a string the complete string element + is returned. + + This method does not return any item in any reference frame but + only complete elements. Use @c -itemAtIndex:referenceFrame: for + that purpose. + + @param anIndex + The index of the element. + + @param referenceFrame + The reference frame @c anIndex is specified in. + + @return The item at @c anIndex. + */ - (id)elementAtIndex:(NSUInteger)anIndex referenceFrame:(MPReferenceFrame)referenceFrame; @@ -226,45 +317,48 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { @param element The element to find. - @return The index of @c element expressed in the indexed reference frame. + @return The index of @c element expressed in the element reference frame. */ - (NSUInteger)indexOfElement:(id)element; /*! - @method elementsInIndexedRange: - @brief Returns an array of the elements that are located in the - specified range. The range is specified in the indexed reference - frame. + @method itemsInRange:referenceFrame: + @brief Returns an array of the items that are located in the specified + range. @discussion The objects in the returned array are not copied before they are returned. You should be aware of the fact that mutations to any - returned element will be reflected in the receiver. + returned objects will be reflected in the receiver. If the @c range exceeds the receiver's bounds an @c NSRangeException is raised. - @param range - The requested range within the receiver's bounds expressed in the - indexed reference frame. + @param aRange + The requested range within the receiver's bounds. + + @param referenceFrame + The referenceFrame @c aRange is expressed in. @return An array of objects that conform to the @c MPExpressionElement protocol (that is @c NSString objects and @c MPFunction objects). The length of the returned array is equal to the length of the specified range. */ -- (NSArray *)itemsInRange:(NSRange)range +- (NSArray *)itemsInRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame; /*! - @method elements - @brief Returns an array of all elements in the receiver. + @method allItemsInReferenceFrame: + @brief Returns an array of all items in the receiver for the specified + reference frame. @discussion The elements in the returned array are not copied before they are - returned. + returned so be aware that mutations to any of the returned + objects will be reflected in the receiver. - @return An array of all elements from the receiver. + @return An array of all items in the receiver. */ - (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame; @@ -285,7 +379,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { @param indexPath The index path the required object is located at. The indexes are - expressed in the indexed reference frame. + expressed in the element reference frame. @return The element located at @c indexPath. The element is not copied before it is returned. Be aware of the fact that any mutations @@ -293,19 +387,127 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { */ - (id)elementAtIndexPath:(NSIndexPath *)indexPath; -- (NSUInteger)convertIndex:(NSUInteger)index + +/*! + @method convertIndex:fromReferenceFrame:toReferenceFrame: + @brief Converts an index from one reference frame to another. + + @discussion This method ignores any offsets into items the conversion between + reference frames can cause. If you are interested in the offset + use @c -convertIndex:fromReferenceFrame:toReferenceFrame:offset: + instead. + + @param anIndex + The index to be converted. + + @param fromReferenceFrame + The reference frame @c anIndex is specified in. + + @param toReferenceFrame + The reference frame @c anIndex should be converted to. + + @return An index specified in the @c toReferenceFrame. If @c anIndex + specifies a location inside an item in the @c toReferenceFrame + the index of the corresponding item is returned. + */ +- (NSUInteger)convertIndex:(NSUInteger)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame; -- (NSUInteger)convertIndex:(NSUInteger)index + +/*! + @method convertIndex:fromReferenceFrame:toReferenceFrame:offset: + @brief Converts an index from one reference frame to another. + + @discussion If @c anIndex specifies a location inside an item in the @c + toReferenceFrame the index of the corresponding item is returned. + + @param anIndex + The index to be converted. + + @param fromReferenceFrame + The reference frame @c anIndex is specified in. + + @param toReferenceFrame + The reference frame @c anIndex should be converted to. + + @param offset + This output parameter will be set to the number of symbols that + are between the location specified by @c anIndex and the index + that is actually returned. The offset is specified in the symbol + reference frame. If you are not interested in the offset pass + @c NULL. + + @return An index specified in the @c toReferenceFrame corresponding to + @c anIndex. + */ +- (NSUInteger)convertIndex:(NSUInteger)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame - offset:(NSUInteger *)offset; + offset:(out NSUInteger *)offset; + +/*! + @method convertRange:fromReferenceFrame:toReferenceFrame: + @brief Converts a range from one reference frame to another. + + @discussion This method just converts the location and target of the range + separately using @c + -convertIndex:fromReferenceFrame:toReferenceFrame:. This method + ensures that the returned range definitely includes all items + that were specified by @c aRange. + + This method ignores the possibility that the returned range may + include more than @a aRange. If you need that information use + @c -convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:. + + @param aRange + The range to be converted. + + @param fromReferenceFrame + The reference frame @c aRange is specified in. + + @param toReferenceFrame + The reference frame @c aRange is to be converted to. + + @return A range in the @c toReferenceFrame that includes the the items + specified by @c aRange. + */ - (NSRange)convertRange:(NSRange)aRange fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame; + +/*! + @method convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset: + @brief Converts a range from one reference frame to another. + + @discussion This method just converts the location and target of the range + separately using @c + -convertIndex:fromReferenceFrame:toReferenceFrame:. This method + ensures that the returned range definitely includes all items + that were specified by @c aRange. + + @param aRange + The range to be converted. + + @param fromReferenceFrame + The reference frame @c aRange is specified in. + + @param toReferenceFrame + The reference frame @c aRange is to be converted to. + + @param leadingOffset + The offset of the location of the returned range in respect to + the location of @c aRange. + + @param trailingOffset + The offset of the last index in the range in respect to the last + index of @c aRange. + + @return A range in the @c toReferenceFrame that includes the the items + specified by @c aRange. + */ - (NSRange)convertRange:(NSRange)aRange fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame @@ -314,17 +516,19 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { #pragma mark Mutating Expressions +/*! + @methodgroup Mutating Expressions + */ /*! - @method replaceSymbolsInRange:withElements: + @method replaceItemsInRange:referenceFrame:withElements: @brief Replaces the elements in the given range with the contents of the @c elements array. @discussion This is the most primitive mutation method of @c MPExpression. Every other mutating method utlimately must call this method. - After the receiver has been mutated the integrety of the receiver is restored. That basically means that subsequent strings are joined and empty strings removed. After restoring integrity the @@ -333,35 +537,98 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { itself. For more information see the documentation on that method. - @param range - The @c range of symbols (including functions) to replace - specified in the located reference frame. + @param aRange + The @c range of symbols (including functions) to replace. + + @param referenceFrame + The reference frame @c aRange is specified in. @param elements The elements that should replace the symbols specified by @c range. */ -- (void)replaceItemsInRange:(NSRange)range +- (void)replaceItemsInRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame withElements:(NSArray *)elements; + +/*! + @method subexpressionFromIndex:referenceFrame: + @brief Creates an expression from the items in the receiver from the + specified index to the end. + + @discussion The items from the receiver are copied. Mutations to the returned + expression will not change the receiver. + + @param from + The index from which to start constructing the subexpression. + + @param referenceFrame + The reference frame @c from is specified in. + + @return An expression containing the items from the given index to the + end. + */ - (MPExpression *)subexpressionFromIndex:(NSUInteger)from referenceFrame:(MPReferenceFrame)referenceFrame; + + +/*! + @method subexpressionToIndex:referenceFrame: + @brief Creates an expression from the items in the receiver from the + first item to the end. + + @discussion The items from the receiver are copied. Mutations to the returned + expression will not change the receiver. + + @param to + The index of the first element not to include in the newly + constructed subexpression. + + @param referenceFrame + The reference frame @c to is specified in. + + @return An expression containing the items from the first item to the + given index. + */ - (MPExpression *)subexpressionToIndex:(NSUInteger)to referenceFrame:(MPReferenceFrame)referenceFrame; -- (MPExpression *)subexpressionWithRange:(NSRange)range + + +/*! + @method subexpressionWithRange:referenceFrame: + @brief Creates an expression from the items in the receiver within the + specified range. + + @discussion The items from the receiver are copied. Mutations to the returned + expression will not change the receiver. + + @param aRange + Specifies the items to be included in the newly created + subexpression. + + @param referenceFrame + The reference frame @c aRange is specified in. + + @return An expression containing the items in @c aRange. + */ +- (MPExpression *)subexpressionWithRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame; + #pragma mark Evaluating Expressions +/*! + @methodgroup Evaluating Expressions + */ /*! @method evaluateWitError: - @brief Evaluates the receiving expression. + @brief Evaluates the receiver. @discussion This is a convenience method for evaluating an expression. If you - want more control over the evaluation process use the @c - evaluator property of the receiver. + want more control over the evaluation process use @c -parse + instead. @param error If the receiver (or any of its elements) contains a syntax error @@ -371,18 +638,36 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { @return The result of the evaluation or @c nil of the receiver could not be evaluated. In that case the @c error parameter is set to an - appropriate value. + appropriate value. If @c NaN is returned it means there was a + math error. In most cases the error parameter contains an + appropriate description of the problem. */ - (NSDecimalNumber *)evaluateWithError:(NSError *__autoreleasing *)error; + +/*! + @method parse + @brief Parses the receiver. + + @discussion This method returns a valid object even if the expression + contains syntax errors. Send the returned object a @c -validate: + message to check for syntax errors before you evaluate it. For + more information on evaluation see the documentation of @c + MPExpressionTree. + + @return A @c MPExpressionTree object that can evaluate the receiver. + */ - (MPExpressionTree *)parse; + #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 +/*! + @methodgroup Notifications + */ /*! - @method didChangeElementsInIndexedRangePath:replacementLength: + @method didChangeElementsInRangePath:replacementLength: @brief Called after the receiver has been mutated. @discussion This method does nothing more than notify it's parent that it has @@ -395,77 +680,165 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { @param rangePath The range path at which the receiver was changed starting at the receiver. The range addressed by @c rangePath is expressed in the - indexed reference frame. + element reference frame. @param replacementLength The number of elements replacing the elements specified by @c - rangePath. + rangePath (also specified in the element reference frame). */ - (void)didChangeElementsInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)replacementLength; - -#pragma mark Basic NSObject Methods -// TODO: Check this -// - (BOOL)isEqualToExpression:(MPExpression *)anExpression; - @end + + @interface MPExpression (MPExpressionConvenience) - #pragma mark Querying Expressions +/*! + @methodgroup Querying Expressions + */ + +/*! + @method countElements + @brief Returns the number of elements in the receiver. + + @return The number of elements in the receiver expressed in the element + reference frame. + */ - (NSUInteger)countElements; + + +/*! + @method countSymbols + @brief Returns the number of symbols in the receiver. + + @return The number of symbols in the receiver expressed in the symbol + reference frame. + */ - (NSUInteger)countSymbols; + + +/*! + @method countTokens + @brief Returns the number of tokens in the receiver. + + @return The number of tokens in the receiver expressed in the token + reference frame. + */ - (NSUInteger)countTokens; -- (id)elementAtIndex:(NSUInteger)index; -- (id)symbolAtIndex:(NSUInteger)index; -- (id)tokenAtIndex:(NSUInteger)index; + +/*! + @method elementAtIndex: + @brief Returns the element at the specified index. + + @param anIndex + The index of the element specified in the element reference + frame. + + @return The element at @c anIndex. + */ +- (id)elementAtIndex:(NSUInteger)anIndex; + + +/*! + @method symbolAtIndex: + @brief Returns the symbol at the specified index. + + @param anIndex + The index of the symbol specified in the symbol reference frame. + + @return The symbol at @c anIndex. + */ +- (id)symbolAtIndex:(NSUInteger)anIndex; + + +/*! + @method tokenAtIndex: + @brief Returns the token at the specified index. + + @param anIndex + The index of the token specified in the token reference frame. + + @return The token at @c anIndex. + */ +- (id)tokenAtIndex:(NSUInteger)anIndex; #pragma mark Mutating Expressions +/*! + @methodgroup Mutating Expressions + */ + /*! - @method appendElement: - @brief Appends @c anElement to the receiver. + @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; + +/*! + @method insertElement:atIndex:referenceFrame: + @brief Inserts @c anElement at the specified index. + + @param anElement + The element to be inserted. + + @param index + The index where to insert @c anElement. + + @param referenceFrame + The reference frame @c index is specified in. + */ - (void)insertElement:(id)anElement atIndex:(NSUInteger)index referenceFrame:(MPReferenceFrame)referenceFrame; + +/*! + @method insertElements:atIndex:referenceFrame: + @brief Inserts @c elements at the specified index. + + @param elements + The elements to be inserted. + + @param index + The index where to insert @c elements. + + @param referenceFrame + The reference frame @c index is specified in. + */ - (void)insertElements:(NSArray *)elements atIndex:(NSUInteger)index referenceFrame:(MPReferenceFrame)referenceFrame; /*! - @method deleteElementsInRange: - @brief Removes the elements specified by @c range from the receiver. + @method deleteElementsInRange: + @brief Removes the elements specified by @c range from the receiver. - @discussion The range is specified in the length reference frame. + @param range + The range of items to remove from the receiver. - If @c range exceeds the receiver's bounds a @c NSRangeException - is raised. - - @param range - The range to remove from the receiver. + @param referenceFrame + The reference frame @c range is specified in. */ - (void)deleteElementsInRange:(NSRange)range referenceFrame:(MPReferenceFrame)referenceFrame; diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m index b98c1f3..1f36d39 100644 --- a/MathPad/MPExpression.m +++ b/MathPad/MPExpression.m @@ -20,12 +20,16 @@ #import "NSIndexPath+MPAdditions.h" + + +NSString *const MPIllegalElementException = @"MPIllegalElementException"; NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey"; NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain"; NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; + @interface MPExpression () { NSMutableArray * _elements; } @@ -62,11 +66,13 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return [self initWithElements:@[]]; } + - (instancetype)initWithElement:(id)element { return [self initWithElements:@[element]]; } + - (instancetype)initWithElements:(NSArray *)elements { self = [super init]; @@ -96,7 +102,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; { for (id element in elements) { if (![element conformsToProtocol:@protocol(MPExpressionElement)]) { - @throw [NSException exceptionWithName:@"MPIllegalElementException" + @throw [NSException exceptionWithName:MPIllegalElementException reason:@"Elements must conform to the MPExpressionElement protocol." userInfo:@{MPIllegalElementExceptionElementKey: element}]; } @@ -141,6 +147,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; } } + #pragma mark Querying Expressions @@ -152,6 +159,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return [self.parent rootExpression]; } + - (NSIndexPath *)indexPath { if (self.parent) { @@ -162,6 +170,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; } } + - (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame { switch (referenceFrame) { @@ -182,6 +191,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; } } + - (id)itemAtIndex:(NSUInteger)anIndex referenceFrame:(MPReferenceFrame)referenceFrame { @@ -210,6 +220,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; } } + - (id)elementAtIndex:(NSUInteger)anIndex referenceFrame:(MPReferenceFrame)referenceFrame { @@ -219,20 +230,23 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return _elements[elementIndex]; } + #warning If multiple equal expressions exist errors may occur... - (NSUInteger)indexOfElement:(id)element { return [_elements indexOfObject:element]; } -- (NSArray *)itemsInRange:(NSRange)range + +- (NSArray *)itemsInRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame { - MPExpression *subexpression = [self subexpressionWithRange:range + MPExpression *subexpression = [self subexpressionWithRange:aRange referenceFrame:referenceFrame]; return [subexpression allItemsInReferenceFrame:referenceFrame]; } + - (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame { switch (referenceFrame) { @@ -259,6 +273,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; } } + - (id)elementAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.length == 0) { @@ -275,26 +290,28 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return nil; } -- (NSUInteger)convertIndex:(NSUInteger)index + +- (NSUInteger)convertIndex:(NSUInteger)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame { - return [self convertIndex:index + return [self convertIndex:anIndex fromReferenceFrame:fromReferenceFrame toReferenceFrame:toReferenceFrame offset:NULL]; } -- (NSUInteger)convertIndex:(NSUInteger)index + +- (NSUInteger)convertIndex:(NSUInteger)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame - offset:(NSUInteger *)offset + offset:(out NSUInteger *)offset { - if (fromReferenceFrame == toReferenceFrame || index == 0) { + if (fromReferenceFrame == toReferenceFrame || anIndex == 0) { if (offset) { *offset = 0; } - return index; + return anIndex; } NSUInteger symbolIndex __block = 0; @@ -302,16 +319,16 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; case MPElementReferenceFrame: [_elements enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { symbolIndex += obj.length; - *stop = idx >= index - 1; + *stop = idx >= anIndex - 1; }]; break; case MPSymbolReferenceFrame: - symbolIndex = index; + symbolIndex = anIndex; break; case MPTokenReferenceFrame: - symbolIndex = [self.tokens[index] range].location; + symbolIndex = [self.tokens[anIndex] range].location; break; } @@ -351,7 +368,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; if (NSMaxRange(token.range) < symbolIndex || token.range.location > symbolIndex) { continue; } - NSUInteger offsetInToken = index - token.range.location; + NSUInteger offsetInToken = anIndex - token.range.location; if (offsetInToken == token.range.length) { offsetInToken = 0; tokenIndex++; @@ -367,6 +384,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; } } + - (NSRange)convertRange:(NSRange)aRange fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame @@ -378,6 +396,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; trailingOffset:NULL]; } + - (NSRange)convertRange:(NSRange)aRange fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame @@ -399,20 +418,21 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; #pragma mark Mutating Expressions -- (void)replaceItemsInRange:(NSRange)range +- (void)replaceItemsInRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame withElements:(NSArray *)elements { - NSUInteger start = [self convertIndex:range.location + NSUInteger start = [self convertIndex:aRange.location fromReferenceFrame:referenceFrame toReferenceFrame:MPSymbolReferenceFrame]; - NSUInteger end = [self convertIndex:NSMaxRange(range) + NSUInteger end = [self convertIndex:NSMaxRange(aRange) fromReferenceFrame:referenceFrame toReferenceFrame:MPSymbolReferenceFrame]; [self _replaceSymbolsInRange:NSMakeRange(start, end - start) withElements:elements]; } + - (void)_replaceSymbolsInRange:(NSRange)range withElements:(NSArray *)elements { @@ -439,8 +459,14 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; // Perform the replacement NSMutableArray *newElements = [[NSMutableArray alloc] initWithArray:elements copyItems:YES]; + NSArray *removedElements = [_elements subarrayWithRange:NSMakeRange(startIndex, endIndex-startIndex)]; [_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex) withObjectsFromArray:newElements]; + for (id element in removedElements) { + if ([element isFunction]) { + ((MPFunction *)element).parent = nil; + } + } _tokenCache = nil; @@ -469,6 +495,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; replacementLength:_replacementLength]; } + - (BOOL)_splitElementsAtLocation:(NSUInteger)location insertionIndex:(out NSUInteger *)insertionIndex { @@ -494,6 +521,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return splitOffset != 0; } + - (MPExpression *)subexpressionFromIndex:(NSUInteger)from referenceFrame:(MPReferenceFrame)referenceFrame { @@ -501,6 +529,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; referenceFrame:referenceFrame]; } + - (MPExpression *)subexpressionToIndex:(NSUInteger)to referenceFrame:(MPReferenceFrame)referenceFrame { @@ -508,12 +537,13 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; referenceFrame:referenceFrame]; } -- (MPExpression *)subexpressionWithRange:(NSRange)range + +- (MPExpression *)subexpressionWithRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame { MPExpression *subexpression = [self copy]; - NSRange preceedingRange = NSMakeRange(0, range.location); - NSUInteger firstOut = NSMaxRange(range); + NSRange preceedingRange = NSMakeRange(0, aRange.location); + NSUInteger firstOut = NSMaxRange(aRange); NSRange exceedingRange = NSMakeRange(firstOut, [self countItemsInReferenceFrame:referenceFrame] - firstOut); [subexpression deleteElementsInRange:exceedingRange referenceFrame:referenceFrame]; @@ -522,8 +552,10 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return subexpression; } + #pragma mark Evaluating Expressions + - (NSDecimalNumber *)evaluateWithError:(NSError *__autoreleasing *)error { MPExpressionTree *tree = [self parse]; @@ -537,6 +569,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return [[MPExpressionTree alloc] initWithTokenStream:tokenStream]; } + #pragma mark Notifications @@ -553,27 +586,6 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; #pragma mark Basic NSObject Methods -/* -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - if (object == nil) { - return NO; - } - if (![object isKindOfClass:[MPExpression class]]) { - return NO; - } - return [self isEqualToExpression:(MPExpression *)object]; -} - -- (BOOL)isEqualToExpression:(MPExpression *)anExpression -{ - return [self.elements isEqualToArray:anExpression.elements]; -} -*/ - - (NSString *)description { #warning Bad Implementation @@ -608,6 +620,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return description; } + - (NSUInteger)hash { return [_elements hash]; @@ -633,6 +646,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; return [self initWithElements:[aDecoder decodeObject]]; } + - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_elements]; @@ -641,51 +655,60 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; @end + @implementation MPExpression (MPExpressionConvenience) #pragma mark Querying Expressions + - (NSUInteger)countElements { return [self countItemsInReferenceFrame:MPElementReferenceFrame]; } + - (NSUInteger)countSymbols { return [self countItemsInReferenceFrame:MPSymbolReferenceFrame]; } + - (NSUInteger)countTokens { return [self countItemsInReferenceFrame:MPTokenReferenceFrame]; } -- (id)elementAtIndex:(NSUInteger)index + +- (id)elementAtIndex:(NSUInteger)anIndex { - return [self itemAtIndex:index + return [self itemAtIndex:anIndex referenceFrame:MPElementReferenceFrame]; } -- (id)symbolAtIndex:(NSUInteger)index + +- (id)symbolAtIndex:(NSUInteger)anIndex { - return [self itemAtIndex:index + return [self itemAtIndex:anIndex referenceFrame:MPSymbolReferenceFrame]; } -- (id)tokenAtIndex:(NSUInteger)index + +- (id)tokenAtIndex:(NSUInteger)anIndex { - return [self itemAtIndex:index + return [self itemAtIndex:anIndex referenceFrame:MPTokenReferenceFrame]; } #pragma mark Mutating Expressions + - (void)appendElement:(id)anElement { [self appendElements:@[anElement]]; } + - (void)appendElements:(NSArray *)elements { [self replaceItemsInRange:NSMakeRange([self countItemsInReferenceFrame:MPSymbolReferenceFrame], 0) @@ -693,6 +716,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; withElements:elements]; } + - (void)insertElement:(id)anElement atIndex:(NSUInteger)index referenceFrame:(MPReferenceFrame)referenceFrame @@ -702,6 +726,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; referenceFrame:referenceFrame]; } + - (void)insertElements:(NSArray *)elements atIndex:(NSUInteger)index referenceFrame:(MPReferenceFrame)referenceFrame @@ -711,6 +736,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; withElements:elements]; } + - (void)deleteElementsInRange:(NSRange)range referenceFrame:(MPReferenceFrame)referenceFrame { diff --git a/MathPad/MPExpressionElement.h b/MathPad/MPExpressionElement.h index 3ffac4a..12f89d7 100644 --- a/MathPad/MPExpressionElement.h +++ b/MathPad/MPExpressionElement.h @@ -6,11 +6,52 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // + + +/*! + @protocol MPExpressionElement + @brief This protocol defines the functionality an element in an + expression must have. + */ @protocol MPExpressionElement + +/*! + @method isString + @brief Returns wether the receiver is a string. + + @discussion A string is defined by being an instance of @c NSString. If this + method returns @c YES you can be sure it is an @c NSString + instance. + + @return @c YES if the receiver is a string, @c NO otherwise. + */ - (BOOL)isString; + + +/*! + @method isFunction + @brief Returns wether the receiver is a function. + + @discussion A function is defined by being an instance of @c MPFunction. If + this method returns @c YES you can be sure it is a @c MPFunction + instance. + + @return @c YES if the receiver is a function, @c NO otherwise. + */ - (BOOL)isFunction; + +/*! + @method length + @brief Calculates the length of the receiver. + + @discussion The length of a @c MPExpressionElement is the number of symbols + it consists of. For strings this is the number of characters in + it. Functions have a length of @c 1. + + @return The receiver's length. + */ - (NSUInteger)length; @end diff --git a/MathPad/MPExpressionTokenizer.h b/MathPad/MPExpressionTokenizer.h index 0cb064f..53d7d80 100644 --- a/MathPad/MPExpressionTokenizer.h +++ b/MathPad/MPExpressionTokenizer.h @@ -6,10 +6,32 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // + + @class MPExpressionTokenizer, MPExpression; + +/*! + @class MPExpressionTokenizer + @brief The expression tokenizer class convers an @c MPExpression + instance into an array of tokens. + */ @interface MPExpressionTokenizer : NSObject + +/*! + @method tokenizeExpression: + @brief Converts an @c MPExpression instance into an array of tokens. + + @discussion The objects in the returned array all conform to the @c MPToken + protocol. Function tokens are not copied from the @c expression + so they can still be mutated. + + @param expression + The expression to be tokenized. + + @return An array of objects that conform to the @c MPToken protocol. + */ + (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's @end diff --git a/MathPad/MPExpressionTokenizer.m b/MathPad/MPExpressionTokenizer.m index b383f95..f28db3f 100644 --- a/MathPad/MPExpressionTokenizer.m +++ b/MathPad/MPExpressionTokenizer.m @@ -15,8 +15,11 @@ #import "NSRegularExpression+MPParsingAdditions.h" + #define MPRangeExists(range) (range.location != NSNotFound) + + @implementation MPExpressionTokenizer + (NSArray *)tokenizeExpression:(MPExpression *)expression @@ -36,6 +39,8 @@ return tokens; } + + + (NSArray *)tokenizeElement:(NSString *)element elementSymbolIndex:(NSUInteger)symbolIndex { diff --git a/MathPad/MPExpressionTree.h b/MathPad/MPExpressionTree.h index 790f76b..cccba4c 100644 --- a/MathPad/MPExpressionTree.h +++ b/MathPad/MPExpressionTree.h @@ -8,13 +8,71 @@ #import "MPExpressionTreeElement.h" + + @class MPExpressionTree; + +/*! + @class MPExpressionTree + @brief The @c MPExpressionTree class is the main interface for working + with the mathematical representation of an expression. + + @discussion Expressions are represented as a tree structure of elements (all + of which must conform to the @c MPExpressionTreeElement + protocol). Most messages sent to instances of the @c + MPExpressionTree class are cascaded down different parts of an + expression in some way. + */ @interface MPExpressionTree : NSObject +/*! + @property definedVariable + @brief The variable this expression defines. + + @discussion A variable definition must be at the beginning of an expression. + If it defines a variable it must start with a single letter + followed by an equals sign. The single letter is the name of the + variable that is defined. + */ @property (nonatomic, copy) NSString *definedVariable; + + +/*! + @property summands + @brief The summands that make up the receiver. + + @discussion A expression mathematically can be interpreted as a series of + summands. Summands are the different pars of an expression that + are separated by + or - symbols. Every object in the returned + array is guaranteed to conform to the @c MPExpressionTreeElement + protocol. + */ @property (readonly, nonatomic, strong) NSArray *summands; + +/*! + @method validateExpectingVariableDefinition:error: + @brief Validates the receiver. + + @discussion Using this method you can validate an expression that contains a + variable definition. If a variable definition is expected but not + found this method returns @c NO. Likewise @c NO is returned if + you do not expect a variable definition but one is found. + + @param flag + Specifies wether or not to expect a variable definition at the + beginning of the expression. + + @param error + If there is a syntax error in the receiver this parameter will be + set to an appropriate value. If you are not interested in the + type of syntax error pass @c NULL. + + @return @c YES if the receiver is valid, @c NO otherwise. If @c NO is + returned the @c error parameter should be set to an appropriate + value. + */ - (BOOL)validateExpectingVariableDefinition:(BOOL)flag error:(NSError *__autoreleasing *)error; diff --git a/MathPad/MPExpressionTree.m b/MathPad/MPExpressionTree.m index f7bf903..cacd4e3 100644 --- a/MathPad/MPExpressionTree.m +++ b/MathPad/MPExpressionTree.m @@ -15,6 +15,8 @@ #import "MPExpression.h" + + @implementation MPExpressionTree { NSMutableArray *_summands; } @@ -28,6 +30,7 @@ return self; } + - (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream { self = [self init]; @@ -51,17 +54,20 @@ return self; } + - (NSArray *)summands { return _summands; } + - (BOOL)validate:(NSError *__autoreleasing *)error { return [self validateExpectingVariableDefinition:NO error:error]; } + - (BOOL)validateExpectingVariableDefinition:(BOOL)flag error:(NSError *__autoreleasing *)error { @@ -100,6 +106,7 @@ return YES; } + - (NSDecimalNumber *)evaluate { NSDecimalNumber *value = [NSDecimalNumber zero]; diff --git a/MathPad/MPExpressionTreeElement.h b/MathPad/MPExpressionTreeElement.h index c958f80..28787bb 100644 --- a/MathPad/MPExpressionTreeElement.h +++ b/MathPad/MPExpressionTreeElement.h @@ -5,16 +5,85 @@ // Created by Kim Wittenburg on 09.10.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // +// TODO: Replace internal inconsistency exception with something else + + @class MPTokenStream; + +/*! + @protocol MPExpressionTreeElement + @brief This protocol defines the methods that are essential for dealing + with an expression in a mathematical context. + + @discussion Dealing with an expression in a mathematical context involves + anything from evaluation to transformation of expressions and + equations. + */ @protocol MPExpressionTreeElement @required -- (instancetype)init; + +/*! + @method initWithTokenStream: + @brief Initializes the expression tree element from the given token + strem. + + @discussion This method consumes the tokens that make up this element. If the + token stream does not start with a token that is appropriate for + the initializied expression tree element a @c + NSInternalInconsistency exception is raised. + + @param tokenStream + The token stream the receiver is to be initialized from. + + @return A new instance. + */ - (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream; + +/*! + @method validate: + @brief Validates the receiver. + + @discussion Validation should only check for syntax errors. Mathematical + errors like a division by @c 0 should be handled with in @c + -evaluate. + + @param error + If there is a syntax error in the receiver this parameter should + be set to an appropriate value. If you are not interested in the + type of syntax error pass @c NULL. + + @return @c YES if the receiver is valid, @c NO otherwise. If @c NO is + returned the @c error parameter should be set to an appropriate + value. + */ - (BOOL)validate:(NSError *__autoreleasing *)error; -- (NSDecimalNumber *)evaluate; + + +/*! + @method evaluate: + @brief Evaluates the receiver. + + @discussion Evaluation does not take syntax errors into account. Before this + method is called you must call @c validate: to make sure that the + reciever is valid for evaluation. The result of evaluation with + @c -validate: returning @c NO may be unexpected or evaluation may + completely fail. + + @param error + If any errors occur during evaluation (such as division by zero) + this parameter will be set to an appropriate value. If you are + not interested in errors pass @c NULL. + @warning This method is not responsible for syntax validation. Use @c + -validate: instead. + + @return The result of the evaluationa or @c nil or @c NaN if evaluation + fails. If evaluation fails the @c error parameter will be set to + an apporpriate value. + */ +- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error; @end diff --git a/MathPad/MPFunction+MPToken.h b/MathPad/MPFunction+MPToken.h index 9265abc..864957a 100644 --- a/MathPad/MPFunction+MPToken.h +++ b/MathPad/MPFunction+MPToken.h @@ -9,6 +9,13 @@ #import "MPFunction.h" #import "MPToken.h" + + +/*! + @category MPFunction (MPToken) + @brief This category adds @c MPToken protocol conformance to the @c + MPFunction class. + */ @interface MPFunction (MPToken) @end diff --git a/MathPad/MPFunction+MPToken.m b/MathPad/MPFunction+MPToken.m index 27f249a..2a6b430 100644 --- a/MathPad/MPFunction+MPToken.m +++ b/MathPad/MPFunction+MPToken.m @@ -17,6 +17,7 @@ return MPGenericFunctionToken; } + - (NSRange)range { NSUInteger selfIndex = [self.parent indexOfElement:self]; @@ -26,11 +27,13 @@ 1); } + - (BOOL)exists { return YES; } + - (NSString *)stringValue { return [self description]; diff --git a/MathPad/MPFunction.h b/MathPad/MPFunction.h index 92d3b94..c445974 100644 --- a/MathPad/MPFunction.h +++ b/MathPad/MPFunction.h @@ -8,6 +8,13 @@ #import "MPExpressionElement.h" + + +/* Use this macro to implement a custom setter for children of custom MPFunction + * subclasses. This is necessary to maintain the consistency of the expression + * tree. Not implementing a custom setter using this macro or a similar custom + * method will lead to unexpected behaviour. + */ #define MPFunctionAccessorImplementation(Accessor, variableName) \ - (void)set##Accessor:(MPExpression *)value \ { \ @@ -17,51 +24,297 @@ [self didChangeChildAtIndex:[self indexOfChild:variableName]]; \ } + + @class MPFunction, MPExpression, MPRangePath; @protocol MPExpressionElement; + +/*! + @class MPFunction + @brief The @c MPFunction class represents a mathematical function. + + @discussion A mathematical function can be anything that exceeds the + graphical capability of text. + */ @interface MPFunction : NSObject -#pragma mark Creation Methods - -- (instancetype)init; - #pragma mark Properties -// Subclasses should define accessor properties for all sub expressions, which, in the setter, should send didChangeElementAtRangePath:replacementLength: to self with a replacement length of 1. +/* Subclasses should define readwrite properties for all child expressions they + * can have. In the implementation file the property setter should be overridden + * using the @c MPFunctionAccessorImplementation macro. The macro takes two + * parameters: The capitalized name of the property and the name of the + * property's backing variable (usually the property's name prefixed with an + * underscore. + * + * Note that none of the children may be nil at any time. + */ + #pragma mark Working With the Expression Tree -@property (nonatomic, weak) MPExpression *parent; // Documentation: Do not set +/*! + @methodgroup Working With the Expression Tree + */ + + +/*! + @property parent + @brief The receiver's parent. + + @discussion Expressions and functions are organized in a tree-like structure. + Through this property a function's containing expression can be + accessed. + @warning Do not set this property manually. It will automatically be set + when the function is added to an expression. + @note If you need to know when a function is added to or removed from + an expression you can observe this property. + + @return The parent of the receiver or @c nil if the receiver does not + have a parent. + */ +@property (nonatomic, weak) MPExpression *parent; + + +/*! + @method rootExpression + @brief Returns the root expression from the receiver's expression tree. + + @discussion The root expression is the ultimate parent of all expressions and + functions in the expression tree. A root expression does not have + a parent. + + @return The root expression from the receiver's expression tree or @c nil + if this function does not have a parent. + */ - (MPExpression *)rootExpression; + + +/*! + @method indexPath + @brief Returns the index path of the receiver in the expression tree. + + @discussion The index path is calculated by going up the expression tree + collecting the respective index of the receiver. The indexes are + expressed in the element reference frame. If any of the indexes + exceed the respective receiver's bounds a @c NSRangeException is + raised. + + @return The index path of the receiver in the expression tree or @c nil + if this function does not have a parent. + */ - (NSIndexPath *)indexPath; -- (NSArray *)childrenAccessors; // Override +/*! + @method numberOfChildren + @brief Returns the number of children the receiver has. + + @discussion The number of children must be equal to the number of child + accessors as determined by the @c -childrenAccessors method. + + @return The number of children of the receiving function. + */ - (NSUInteger)numberOfChildren; + + +/*! + @method childAtIndex: + @brief Returns the child at the given index. + + @discussion The ordering of children is determined by the order of the + children returned from the @c -childrenAccessors method. + + @param index + The index of the requested child. + + @return The expression that corresponds to the child at @c index. + */ - (MPExpression *)childAtIndex:(NSUInteger)index; + + +/*! + @method setChild:atIndex: + @brief Sets the child at the specified index. + + @discussion Although this method does @b not call the respective accessor + methods it behaves exactly as the setter method implemented using + the @c MPFunctionAccessorImplementation macro. + + @param child + The child that should replace the previous one. + + @param index + The index of the child to be replaced. + */ - (void)setChild:(MPExpression *)child atIndex:(NSUInteger)index; -- (NSArray *)children; // Indexes must equal the ones from the other methods + +/*! + @method children + @brief Returns the receiver's children. + + @discussion The ordering of the children is the same as in the array returned + from @c -childrenAccessors. + + @return An array containing all the function's children. + */ +- (NSArray *)children; + + +/*! + @method indexOfChild: + @brief Returns the index of @c child in the receiver. + + @param child + The child to be searched. + + @return The index of @c child or @c NSNotFound if none of the receiver's + children is equal to @c child. + */ - (NSUInteger)indexOfChild:(MPExpression *)child; + +/*! + @method elementAtIndexPath: + @brief Returns the element at the specified index path. + + @discussion This method @em walks down the expression tree (including other + functions) using the specified index path and finds the + corresponding element. The returned object can be an @c NSString, + a @c MPFunction or an @c MPExpression depending on the element @c + indexPath points to. If any of the indexes exceed the bounds of + the respective receiver an @c NSRangeException is raised. + + If the index path does not contain any indexes the receiver + itself is returned. + + @param indexPath + The index path the required object is located at. The indexes are + expressed in the element reference frame. + + @return The element located at @c indexPath. The element is not copied + before it is returned. Be aware of the fact that any mutations + made to the returned object are reflected in the receiver. + */ - (id)elementAtIndexPath:(NSIndexPath *)indexPath; -#pragma mark Evaluating Functions -- (BOOL)validate:(NSError *__autoreleasing *)error; // Override -- (NSDecimalNumber *)evaluate; // Override +#pragma mark Notifications +/*! + @methodgroup Notifications + */ -#pragma mark Messages + +/*! + @method didChangeElementsInRangePath:replacementLength: + @brief Notification method that is called if one of the children was + mutated. + + @discussion This method is also called when one of the children is changed + via one of the accessor methods. + + @param rangePath + The location of the change that happened. Because it may have + happened somewhere deep down in the expression tree a range path + is used. + + @param replacementLength + The number of elements that replaced the elements addressed by + the range path. + */ - (void)didChangeElementsInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)replacementLength; + + +/*! + @method didChangeChildAtIndex: + @brief Convenience method that calls @c + -didChangeElementsInRangePath:replacementLength: + + @discussion This method is automatically called when one of the children is + changed using one of the accessor methods or @c + -setChild:atIndex:. + + @param index + The index of the child that was changed. + */ - (void)didChangeChildAtIndex:(NSUInteger)index; -#pragma mark Working With Functions -// - (BOOL)isEqualToFunction:(MPFunction *)aFunction; // Override +@end -- (NSString *)description; // Should be overridden -- (NSUInteger)hash; -- (id)copyWithZone:(NSZone *)zone; + +/*! + @category MPFunction (MPSubcalssOverride) + @brief The methods in this category must be implemented by any concrete + subclasses of @c MPFunction. + + @discussion @c MPFunction does not implement the functions itself but calls + them at various points. If a subclass does not provide + implementations your app will most likely crash. + */ +@interface MPFunction (MPSubclassOverride) + +#pragma mark Working With the Expression Tree +/*! + @methodgroup Working With the Expression Tree + */ + + +/*! + @method childrenAccessors + @brief Returns the names of the children properties. + + @discussion A @c MPFunction subclass should declare a public propertiy for + every child it can have. This method should return the names as + they would be used for key-value-coding. + + @return An array of strings describing the names of children a function + has. + */ +- (NSArray *)childrenAccessors; + + +#pragma mark Evaluating Functions +/*! + @methodgroup Evaluating Functions + */ + + +/*! + @method validate: + @brief Validates the function and all its children for syntax errors. + + @param error + If the validation fails this parameter should be set to an + appropriate value. + + @return @c YES if the validation was successful, @c NO otherwise. + */ +- (BOOL)validate:(NSError *__autoreleasing *)error; + + +/*! + @method evaluate: + @brief Evaluates the function. + + @discussion Function evaluation should actually perform math. Also the + implementation of this method assumes that the function and all + children are valid (as determined by the @c -validate: method). + + @param error + If a mathematical error occurs it is reflected in this parameter. + Depending on the error this method then either returns @c nil or + @c NaN. + + @return The result of the evaluation or @c nil or @c NaN if an evaluation + error occured (such as a division by @c 0). + */ +- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error; + +/* In Addition to the above methods MPFunction subclasses should also override + * the NSObject methods -description and -hash. + */ @end diff --git a/MathPad/MPFunction.m b/MathPad/MPFunction.m index b3e37e4..875e86b 100644 --- a/MathPad/MPFunction.m +++ b/MathPad/MPFunction.m @@ -12,9 +12,13 @@ #import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" + + @implementation MPFunction #pragma mark Creation Methods + + - (instancetype)init { self = [super init]; @@ -29,35 +33,49 @@ return self; } + #pragma mark Working With the Expression Tree + + - (MPExpression *)rootExpression { return [self.parent rootExpression]; } + - (NSIndexPath *)indexPath { + if (!self.parent) { + return nil; + } NSUInteger selfIndex = [self.parent indexOfElement:self]; return [[self.parent indexPath] indexPathByAddingIndex:selfIndex]; } + - (NSUInteger)numberOfChildren { return self.childrenAccessors.count; } + - (MPExpression *)childAtIndex:(NSUInteger)index { return [self valueForKey:self.childrenAccessors[index]]; } + - (void)setChild:(MPExpression *)child atIndex:(NSUInteger)index { - [self setValue:child forKey:self.childrenAccessors[index]]; + [[self valueForKey:self.childrenAccessors[index]] setParent:nil]; + [self setValue:child + forKey:self.childrenAccessors[index]]; + child.parent = self; [self didChangeChildAtIndex:index]; } + - (NSArray *)children { NSUInteger childCount = [self numberOfChildren]; @@ -68,8 +86,10 @@ return [children copy]; } + - (NSUInteger)indexOfChild:(MPExpression *)child { + NSUInteger index = 0; for (; index < [self numberOfChildren]; index++) { if ([self childAtIndex:index] == child) { @@ -79,6 +99,7 @@ return NSNotFound; } + - (id)elementAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.length == 0) { @@ -88,7 +109,10 @@ return [child elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; } + #pragma mark Notifications + + - (void)didChangeElementsInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)replacementLength { @@ -98,6 +122,7 @@ replacementLength:replacementLength]; } + - (void)didChangeChildAtIndex:(NSUInteger)index { MPRangePath *path = [[MPRangePath alloc] initWithRange:NSMakeRange(index, 1)]; @@ -105,39 +130,26 @@ replacementLength:1]; } -#pragma mark Working With Functions -/* -- (BOOL)isEqual:(id)object -{ - if (self == object) { - return YES; - } - if (object == nil) { - return NO; - } - if (![object isKindOfClass:[MPFunction class]]) { - return NO; - } - return [self isEqualToFunction:(MPFunction *)object]; -} -- (BOOL)isEqualToFunction:(MPFunction *)aFunction -{ - return [aFunction isMemberOfClass:[MPFunction class]] && [self isMemberOfClass:[MPFunction class]]; -}*/ +#pragma mark Working With Functions + - (NSString *)description { return @"[]"; } + - (NSUInteger)hash { #warning Unimplemented Method return 0; } + #pragma mark - NSCopying + + - (id)copyWithZone:(NSZone *)zone { id copy = [[self.class allocWithZone:zone] init]; @@ -148,7 +160,10 @@ return copy; } + #pragma mark - NSCoding + + - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; @@ -163,22 +178,28 @@ return self; } + - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.children]; } + #pragma mark - MPExpressionElement + + - (BOOL)isString { return NO; } + - (BOOL)isFunction { return YES; } + - (NSUInteger)length { return 1; diff --git a/MathPad/MPProduct.m b/MathPad/MPProduct.m index 2d7ffe8..eacec83 100644 --- a/MathPad/MPProduct.m +++ b/MathPad/MPProduct.m @@ -35,7 +35,7 @@ if (self) { [tokenStream beginIgnoringWhitespaceTokens]; - while (tokenStream.currentToken.tokenType != MPOperatorListToken && tokenStream.currentToken.tokenType != MPEOFToken) { + while ([tokenStream hasMoreTokens] && tokenStream.currentToken.tokenType != MPOperatorListToken) { [_factors addObject:[[MPFactor alloc] initWithTokenStream:tokenStream]]; } [tokenStream endIgnoringOrAcceptingWhitespaceTokens]; diff --git a/MathPad/MPRangePath.h b/MathPad/MPRangePath.h index 05ee62d..6c797bd 100644 --- a/MathPad/MPRangePath.h +++ b/MathPad/MPRangePath.h @@ -8,8 +8,10 @@ #import "MPExpression.h" +/// Convenience Constructor Macro #define MPMakeRangePath(loc, len) [MPRangePath rangePathWithLocation:(loc) length:(len)] + @class MPRangePath, MPExpression; @@ -27,7 +29,11 @@ */ @interface MPRangePath : NSObject + #pragma mark Creation Methods +/*! + @methodgroup Creation Methods + */ /*! @@ -80,7 +86,7 @@ /*! - @method emptyRangePath + @method rangePath @brief Allocates and initializes an empty @c NSRangePath instance. @discussion An empty range path's location has @c 0 indexes and a length of @@ -88,7 +94,23 @@ @return A newly created @c MPRangePath instance. */ -+ (instancetype)emptyRangePath; ++ (instancetype)rangePath; + + +/*! + @method rangePathWithRange: + @brief Allocates a new @c MPRangePath instance and initializes it with + the given location and length. + + @discussion The location of @c aRange is translated into an index path for + the range path. + + @param aRange + The range to be converted into a range path. + + @return A newly created @c MPRangePath instance. + */ ++ (instancetype)rangePathWithRange:(NSRange)aRange; /*! @@ -110,22 +132,10 @@ length:(NSUInteger)length; -/*! - @method rangePathWithRange: - @brief Allocates a new @c MPRangePath instance and initializes it with - the given location and length. - - @discussion The location of @c aRange is translated into an index path for - the range path. - - @param aRange - The range to be converted into a range path. - - @return A newly created @c MPRangePath instance. - */ -+ (instancetype)rangePathWithRange:(NSRange)aRange; - #pragma mark Properties +/*! + @methodgroup Properties + */ /*! @@ -165,26 +175,11 @@ */ - (NSRange)rangeAtLastIndex; -#pragma mark Working with Ranges - +#pragma mark Working With Ranges /*! - @method containsRangePath: - @brief Checks wether the receiver completely contains @c aRangePath. - - @discussion A range path is contained by another range path if either the - receiver's range at its last index contains the index at the - respective location of the location path of @c aRangePath or (if - the locations are of the same length) the receiver's range at its - last index contains the last index's range of @c aRangePath. - - @param aRangePath - The range path to check wether it is contained in the receiver. - - @return @c YES if the range path addressed by the receiver also includes - @c aRangePath, @c NO otherwise. + @methodgroup Working With Ranges */ -- (BOOL)containsRangePath:(MPRangePath *)aRangePath; /*! @@ -207,7 +202,23 @@ - (BOOL)containsLocation:(NSIndexPath *)location; -- (BOOL)isEqual:(id)object; +/*! + @method containsRangePath: + @brief Checks wether the receiver completely contains @c aRangePath. + + @discussion A range path is contained by another range path if either the + receiver's range at its last index contains the index at the + respective location of the location path of @c aRangePath or (if + the locations are of the same length) the receiver's range at its + last index contains the last index's range of @c aRangePath. + + @param aRangePath + The range path to check wether it is contained in the receiver. + + @return @c YES if the range path addressed by the receiver also includes + @c aRangePath, @c NO otherwise. + */ +- (BOOL)containsRangePath:(MPRangePath *)aRangePath; /*! @@ -229,6 +240,8 @@ @end + + @interface MPExpression (MPRangeExtension) @@ -252,6 +265,27 @@ - (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath referenceFrame:(MPReferenceFrame)referenceFrame; + +/*! + @method replaceItemsInRangePath:referenceFrame:withElements: + @brief Replaces the items in the specified range path with the contents + of @c elements. + + @discussion All objects in the @c elements array must conform to the @c + MPExpressionElement protocol. If one or more objects do not + conform to the protocol a @c MPIllegalElementException will be + raised. + + @param rangePath + The range path to be replaced. The path of the range path is + expressed in the element reference frame. The range of the range + path is expressed in the specified @c referenceFrame. + + @param referenceFrame + The reference frame to use. This only applies to the range at the + last index of the range path. The path of the range path is + always expressed in the element reference frame. + */ - (void)replaceItemsInRangePath:(MPRangePath *)rangePath referenceFrame:(MPReferenceFrame)referenceFrame withElements:(NSArray *)elements; diff --git a/MathPad/MPRangePath.m b/MathPad/MPRangePath.m index be6eba0..7c34ae7 100644 --- a/MathPad/MPRangePath.m +++ b/MathPad/MPRangePath.m @@ -8,16 +8,21 @@ #import "MPRangePath.h" + + @implementation MPRangePath + #pragma mark Creation Methods + - (instancetype)init { return [self initWithLocation:[[NSIndexPath alloc] init] length:0]; } + - (instancetype)initWithLocation:(NSIndexPath *)location length:(NSUInteger)length { @@ -29,17 +34,26 @@ return self; } + - (instancetype)initWithRange:(NSRange)aRange { return [self initWithLocation:[[NSIndexPath alloc] initWithIndex:aRange.location] length:aRange.length]; } -+ (instancetype)emptyRangePath + ++ (instancetype)rangePath { return [[self alloc] init]; } + ++ (instancetype)rangePathWithRange:(NSRange)aRange +{ + return [[self alloc] initWithRange:aRange]; +} + + + (instancetype)rangePathWithLocation:(NSIndexPath *)location length:(NSUInteger)length { @@ -47,13 +61,10 @@ length:length]; } -+ (instancetype)rangePathWithRange:(NSRange)aRange -{ - return [[self alloc] initWithRange:aRange]; -} #pragma mark Properties + - (NSIndexPath *)maxRangePath { NSUInteger lastIndex = [self.location indexAtPosition:self.location.length-1]; @@ -61,12 +72,21 @@ return [[self.location indexPathByRemovingLastIndex] indexPathByAddingIndex:newLastIndex]; } + - (NSRange)rangeAtLastIndex { return NSMakeRange([self.location indexAtPosition:self.location.length-1], self.length); } -#pragma mark Working with Ranges + +#pragma mark Working With Ranges + + +- (BOOL)containsLocation:(NSIndexPath *)location +{ + return [self containsRangePath:[[MPRangePath alloc] initWithLocation:location length:0]]; +} + - (BOOL)containsRangePath:(MPRangePath *)aRangePath { @@ -91,10 +111,6 @@ } } -- (BOOL)containsLocation:(NSIndexPath *)location -{ - return [self containsRangePath:[[MPRangePath alloc] initWithLocation:location length:0]]; -} - (BOOL)isEqual:(id)object { @@ -110,11 +126,13 @@ return [self isEqualToRangePath:(MPRangePath *)object]; } + - (BOOL)isEqualToRangePath:(MPRangePath *)aRangePath { return [self.location isEqual:aRangePath.location] && self.length == aRangePath.length; } + - (NSString *)description { NSMutableString *description = [[NSMutableString alloc] initWithString:@"MPRangePath baseValue; @end diff --git a/MathPad/MPSuffixFunction.m b/MathPad/MPSuffixFunction.m index 993998b..85401cf 100644 --- a/MathPad/MPSuffixFunction.m +++ b/MathPad/MPSuffixFunction.m @@ -8,6 +8,8 @@ #import "MPSuffixFunction.h" + + @implementation MPSuffixFunction @end diff --git a/MathPad/MPSumFunction.h b/MathPad/MPSumFunction.h index dcbeb0b..560b8b6 100644 --- a/MathPad/MPSumFunction.h +++ b/MathPad/MPSumFunction.h @@ -8,12 +8,54 @@ #import "MPFunction.h" + + @class MPSumFunction, MPExpression; + +/*! + @class MPSumFunction + @brief A sum function (generally noted using a capital sigma) evaluates + a term repeatedly and sums up the results. + + @discussion A sum function has a start and a target expression indicating how + often the sum expression should be evaluated. Both the value of + the start expression and the target expressions are included in + the iterations. + + Each iteration the sum value is incremented by @c 1. If the start + and target expression evaluate to the same value the sum is + evaluated once. + */ @interface MPSumFunction : MPFunction -@property (nonatomic, strong) MPExpression *startExpression; // Index 0 -@property (nonatomic, strong) MPExpression *targetExpression; // Index 1 -@property (nonatomic, strong) MPExpression *sumExpression; // Index 2 + +/*! + @property startExpression + @brief The value of the first iteration. + + @discussion The start expression must define a variable that may be used in + the sum expression. If the start expression does not define a + variable the sum function will fail on validation. + */ +@property (nonatomic, strong) MPExpression *startExpression; /* Index 0 */ + + +/*! + @property startExpression + @brief The value if the last iteration. + */ +@property (nonatomic, strong) MPExpression *targetExpression; /* Index 1 */ + + +/*! + @property sumExpression + @brief The sum expression evaluated multiple times. + + @discussion During evaluation of the sum expression the variable defined in + the start expression is available. That variable always contains + the value of the current iteration. + */ +@property (nonatomic, strong) MPExpression *sumExpression; /* Index 2 */ @end diff --git a/MathPad/MPSumFunction.m b/MathPad/MPSumFunction.m index 29008f4..c6f7661 100644 --- a/MathPad/MPSumFunction.m +++ b/MathPad/MPSumFunction.m @@ -12,19 +12,25 @@ #import "MPExpressionTree.h" #import "MPEvaluationContext.h" + + @implementation MPSumFunction + MPFunctionAccessorImplementation(StartExpression, _startExpression) MPFunctionAccessorImplementation(TargetExpression, _targetExpression) MPFunctionAccessorImplementation(SumExpression, _sumExpression) + - (NSArray *)childrenAccessors { return @[@"startExpression", @"targetExpression", @"sumExpression"]; } + #pragma mark Evaluating Functions + - (BOOL)validate:(NSError *__autoreleasing *)error { MPExpressionTree *startTree = [self.startExpression parse]; @@ -47,6 +53,7 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression) return YES; } + - (NSDecimalNumber *)evaluate { MPExpressionTree *startTree = [self.startExpression parse]; @@ -67,8 +74,10 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression) return value; } + #pragma mark Working With Functions + - (NSString *)description { return [NSString stringWithFormat:@"Sum(From: %@; To: %@; Using: %@)", self.startExpression, self.targetExpression, self.sumExpression]; diff --git a/MathPad/MPSummand.h b/MathPad/MPSummand.h index e46fbcb..14a3a20 100644 --- a/MathPad/MPSummand.h +++ b/MathPad/MPSummand.h @@ -8,11 +8,35 @@ #import "MPExpressionTreeElement.h" + + @class MPSummand, MPOperatorChain, MPProduct; + +/*! + @class MPSummand + @brief A summand is a part of an expression that consists of a list of + addition and subtraction operators and a product. + */ @interface MPSummand : NSObject + +/*! + @property operatorChain + @brief The summand's preceeding operators. + + @discussion The operator chain is interpreted as a factor for the @c product + property of the summand during evaluation. + */ @property (readonly, nonatomic, strong) MPOperatorChain *operatorChain; + + +/*! + @property product + @brief The summand's product. + + @discussion The product is the @em value of a summand. + */ @property (readonly, nonatomic, strong) MPProduct *product; @end diff --git a/MathPad/MPToken.h b/MathPad/MPToken.h index 067d1d7..49f8ac5 100644 --- a/MathPad/MPToken.h +++ b/MathPad/MPToken.h @@ -6,11 +6,66 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // + + @class MPToken; @protocol MPToken; + +/*! + @typedef MPTokenType + @brief A type of a token identifies its behaviour in a mathematical + context. + + @constant MPMultiplicationSymbolToken + A multiplication symbol. + + @constant MPOperatorListToken + A list of operators (+ and - symbols). The token may be longer + than the number of operators if there are spaces between them. + + @constant MPSinToken + The sin function. + + @constant MPCosToken + The cos function. + + @constant MPTanToken + The tan function. + + @constant MPASinToken + The asin function. + + @constant MPACosToken + The acos function. + + @constant MPATanToken + The atan function. + + @constant MPNumberToken + A number. This may be an integer or a floating point number. + Floating point numbers contain a @c NSLocaleDecimalSeparator. + + @constant MPVariableToken + A variable. A variable is exactly one character long. + + @constant MPFactorialToken + The factorial symbol (!). + + @constant MPEqualsToken + The equals sign. + + @constant MPGenericFunctionToken + A function represented by the @c MPFunction class. A token with + this token type is guaranteed to be a @c MPFunction instance. + + @constant MPWhitespaceToken + A whitespace. This token can typically be ignored. + + @constant MPUnidentifiedToken + Any symbol that does not match any other token. + */ typedef NS_ENUM(NSUInteger, MPTokenType) { - MPEOFToken = 0, MPMultiplicationSymbolToken, MPOperatorListToken, MPSinToken, @@ -29,21 +84,85 @@ typedef NS_ENUM(NSUInteger, MPTokenType) { MPUnidentifiedToken, }; + + +/*! + @protocol MPToken + @brief Tokens represent logical units in an expresion. + */ @protocol MPToken + +/*! + @method tokenType + @brief Returns the receiver's token type. + + @discussion The token type identifies what kind of token the receiver is. For + more information see the documentation on the @c MPTokenType + enum. + + @return The receiver's token type. + */ - (MPTokenType)tokenType; + +/*! + @method range + @brief Returns the receiver's range. + + @discussion The range identifies where the token is in the expression. It is + specified in the symbol reference frame. + + @return The range the token occupies in the expression it was parsed + from specified in the symbol reference frame. + */ - (NSRange)range; + + +/*! + @method stringValue + @brief The string that caused the token to be parsed. + + @discussion Depending on the type of the token the string value can have a + fixed or variable length. For example the equals token always has + a length of @c 1 whereas a number or whitespace token can be much + longer. + + @return The receiver's string value. + */ - (NSString *)stringValue; @end + +/*! + @class MPToken + @brief The @c MPToken class implements the functionality of the @c + MPToken protocol. Most tokens are instances of the @c MPToken + class. + */ @interface MPToken : NSObject -- (instancetype)initEOFTokenAtLocation:(NSUInteger)eofLocation; + +/*! + @method initWithTokenType:range:stringValue: + @brief Creates a new @c MPToken instance. + + @param tokenType + The type of the token. + + @param range + The range of the token in the expression. Specified in the symbol + reference frame. + + @param input + The string value of the token. + + @return A newly initialized token. + */ - (instancetype)initWithTokenType:(MPTokenType)tokenType range:(NSRange)range - stringValue:(NSString *)input; + stringValue:(NSString *)input; /* designated initializer */ @end diff --git a/MathPad/MPToken.m b/MathPad/MPToken.m index 8ada595..fcfc301 100644 --- a/MathPad/MPToken.m +++ b/MathPad/MPToken.m @@ -8,21 +8,14 @@ #import "MPToken.h" + + @implementation MPToken { NSRange _range; MPTokenType _tokenType; NSString *_stringValue; } -- (instancetype)initEOFTokenAtLocation:(NSUInteger)eofLocation -{ - self = [super init]; - if (self) { - _range = NSMakeRange(eofLocation, 0); - } - return self; -} - - (instancetype)initWithTokenType:(MPTokenType)tokenType range:(NSRange)range stringValue:(NSString *)input @@ -36,21 +29,25 @@ return self; } + - (NSRange)range { return _range; } + - (MPTokenType)tokenType { return _tokenType; } + - (NSString *)stringValue { return _stringValue; } + - (NSString *)description { return self.stringValue; diff --git a/MathPad/MPTokenStream.h b/MathPad/MPTokenStream.h index a65965c..6deec6f 100644 --- a/MathPad/MPTokenStream.h +++ b/MathPad/MPTokenStream.h @@ -6,23 +6,132 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // + + @class MPTokenStream; @protocol MPToken; + +/*! + @class MPTokenStream + @brief This class is a helper class for processing tokens from the + beginning of an expression to the end. + + @discussion A token stream is a single-use object. After the stream has + reached the end there is no way to reset it. + */ @interface MPTokenStream : NSObject -- (instancetype)initWithTokens:(NSArray *)tokens; -@property (nonatomic, copy) NSArray *tokens; +/*! + @method initWithTokens: + @brief Creates a new token stream that @em streams the contents of the + @c tokens array. + + @param tokens + The tokens to be @em streamed. All objects in the array must + conform to the @c MPToken protocol. + + @return A new token stream. + */ +- (instancetype)initWithTokens:(NSArray *)tokens; /* designated initializer */ + +/*! + @method beginIgnoringWhitespaceTokens + @brief After calling this method a @c -currentToken message will skip + whitespace characters before returning the current token. + + @discussion Any call to this method or @c -beginAcceptingWhitespaceTokens + must be matched by a @c -endIgnoringOrAcceptingWhitespaceTokens + message. Calls to both methods can be nested. + + By default whitespace tokens are accepted. + + @see -beginAcceptingWhitespaceTokens + @see -endIgnoringOrAcceptingWhitespaceTokens + */ - (void)beginIgnoringWhitespaceTokens; + + +/*! + @method beginAcceptingWhitespaceTokens + @brief After calling this method @c -currentToken may return a token of + type @c MPWhitespaceToken. + + @discussion Any call to this method or @c -beginAcceptingWhitespaceTokens + must be matched by a @c -endIgnoringOrAcceptingWhitespaceTokens + message. Calls to both methods can be nested. + + By default whitespace tokens are accepted. + + @see -beginIgnoringWhitespaceTokens + @see -endIgnoringOrAcceptingWhitespaceTokens + */ - (void)beginAcceptingWhitespaceTokens; + + +/*! + @method endIgnoringOrAcceptingWhitespaceTokens + @brief This method balances @c -beginIgnoringWhitespaceTokens and @c + -beginAcceptingWhitespaceTokens messages. + + @discussion After this method was + calles the whitespace behaviour falls back to the way it behaved + before the last @c -beginIgnoringWhitespaceTokens or @c + -beginAcceptingWhitespaceTokens message was sent. + */ - (void)endIgnoringOrAcceptingWhitespaceTokens; + + +/*! + @method hasMoreTokens + @brief Use this method to check wether there are any tokens left. + + @discussion This method repects the whitespace skipping behaviour. That means + that if the token stream currently ignores whitespaces and there + are only whitespaces left in the stream this method returns @c + NO. + + @return @c YES if there are more tokens, @c NO otherwise. + */ - (BOOL)hasMoreTokens; + +/*! + @method currentToken + @brief Returns the receiver's current token. + + @discussion This is one of the core methods of @c MPTokenStream. Subsequent + calls to this method will return the same value until @c + -currentTokenConsumed is called. + + @return The current token or @c nil if there are no more tokens. + */ - (id)currentToken; + + +/*! + @method peekNextToken + @brief Looks at the next token and returns it. + + @discussion This method behaves as if you called @c -currentTokenConsumed + followed by @c -currentToken except that it does not consume the + current token. That means that you can use this method to look at + the next token (respecting the current whitespace ignoring + behaviour) without changing the @c currentToken. + + @return The next token or @c nil if there is no token after the current + one. + */ - (id)peekNextToken; + + +/*! + @method currentTokenConsumed + @brief Marks the current token as consumed and discards it. + */ - (void)currentTokenConsumed; @end diff --git a/MathPad/MPTokenStream.m b/MathPad/MPTokenStream.m index e7f3249..42ea0d1 100644 --- a/MathPad/MPTokenStream.m +++ b/MathPad/MPTokenStream.m @@ -10,67 +10,82 @@ #import "MPToken.h" -@implementation MPTokenStream { - NSUInteger _currentTokenIndex; - NSUInteger eofLocation; - - BOOL *whitespaceIgnores; - NSUInteger maxWhitespaceIgnores; - NSUInteger currentWhitespaceState; -} + + +@interface MPTokenStream () + +@property (nonatomic, copy) NSArray *tokens; +@property (nonatomic) NSUInteger currentTokenIndex; +@property (nonatomic) NSUInteger eofLocation; + +@property (nonatomic) BOOL *whitespaceIgnores; +@property (nonatomic) NSUInteger maxWhitespaceIgnores; +@property (nonatomic) NSUInteger currentWhitespaceIgnoreState; + +- (void)pushWhitespaceState:(BOOL)state; + +@end + + +@implementation MPTokenStream - (instancetype)initWithTokens:(NSArray *)tokens { self = [super init]; if (self) { self.tokens = tokens; - whitespaceIgnores = malloc(10 * sizeof(BOOL)); - maxWhitespaceIgnores = 10; - currentWhitespaceState = 0; + self.whitespaceIgnores = malloc(10 * sizeof(BOOL)); + self.maxWhitespaceIgnores = 10; + self.currentWhitespaceIgnoreState = 0; [self beginAcceptingWhitespaceTokens]; _currentTokenIndex = 0; if (tokens.count > 0) { - eofLocation = NSMaxRange([tokens.lastObject range]); + self.eofLocation = NSMaxRange([tokens.lastObject range]); } else { - eofLocation = 0; + self.eofLocation = 0; } } return self; } + - (void)beginAcceptingWhitespaceTokens { [self pushWhitespaceState:YES]; } + - (void)beginIgnoringWhitespaceTokens { [self pushWhitespaceState:NO]; } + - (void)pushWhitespaceState:(BOOL)state { - currentWhitespaceState++; - if (currentWhitespaceState >= maxWhitespaceIgnores) { - maxWhitespaceIgnores += 10; - BOOL *reallocatedWhitespaceIgnores = realloc(whitespaceIgnores, maxWhitespaceIgnores); + self.currentWhitespaceIgnoreState++; + if (self.currentWhitespaceIgnoreState >= self.maxWhitespaceIgnores) { + self.maxWhitespaceIgnores += 10; + BOOL *reallocatedWhitespaceIgnores = realloc(self.whitespaceIgnores, self.maxWhitespaceIgnores); if (reallocatedWhitespaceIgnores) { - whitespaceIgnores = reallocatedWhitespaceIgnores; + self.whitespaceIgnores = reallocatedWhitespaceIgnores; } else { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Realloc Failed" userInfo:nil]; } } - whitespaceIgnores[currentWhitespaceState] = state; + self.whitespaceIgnores[self.currentWhitespaceIgnoreState] = state; } + - (void)endIgnoringOrAcceptingWhitespaceTokens { - currentWhitespaceState--; + self.currentWhitespaceIgnoreState--; } + - (void)skipWhitespaces { - if (whitespaceIgnores[currentWhitespaceState]) { + if (self.whitespaceIgnores[self.currentWhitespaceIgnoreState]) { return; } while (_currentTokenIndex < self.tokens.count) { @@ -82,21 +97,24 @@ } } + - (BOOL)hasMoreTokens { [self skipWhitespaces]; return _currentTokenIndex < self.tokens.count; } + - (id)currentToken { [self skipWhitespaces]; if (_currentTokenIndex >= _tokens.count) { - return [[MPToken alloc] initEOFTokenAtLocation:eofLocation]; + return nil; } return _tokens[_currentTokenIndex]; } + - (id)peekNextToken { NSUInteger currentTokenIndex = _currentTokenIndex; @@ -106,15 +124,17 @@ return token; } + - (void)currentTokenConsumed { [self currentToken]; ++_currentTokenIndex; } + - (void)dealloc { - free(whitespaceIgnores); + free(self.whitespaceIgnores); } @end diff --git a/MathPad/NSIndexPath+MPAdditions.h b/MathPad/NSIndexPath+MPAdditions.h index 1e0e21c..500a702 100644 --- a/MathPad/NSIndexPath+MPAdditions.h +++ b/MathPad/NSIndexPath+MPAdditions.h @@ -6,36 +6,158 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // + + @interface NSIndexPath (MPAdditions) -- (NSUInteger)firstIndex; -- (NSUInteger)lastIndex; - -- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index; /*! - @method indexPathByRemovingFirstIndex - @brief Provides an index path with the indexes in the receiving index path, excluding the first one. - @discussion Returns an empty NSIndexPath instance if the receiving index path’s length is 1 or less. - @return A new index path with the receiving index path’s indexes, excluding the first one. + @property firstIndex + @brief The first index from the index path. + + @discussion If the index path is empty @c NSNotFound is returned. + */ +@property (readonly, nonatomic) NSUInteger firstIndex; + + +/*! + @property lastIndex + @brief The last index from the index path. + + @discussion If the index path is empty @c NSNotFound is returned. + */ +@property (readonly, nonatomic) NSUInteger lastIndex; + + +/*! + @method indexPathByReplacingLastIndexWithIndex: + @brief Provides an index path with the index in the receiving index path + where the last one is replaced by @c index. + + @discussion If the receiving index path is empty an index path of length @c 1 + is returned. The last index in the returned index path is @c + index. + + @param index + The index with which to replace the last index in the receiving + index path. + + @return A new index path with @c index as its last index. + */ +- (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index; + + +/*! + @method indexPathByRemovingFirstIndex + @brief Provides an index path with the indexes in the receiving index + path, excluding the first one. + + @discussion Returns an empty NSIndexPath instance if the receiving index + path’s length is 1 or less. + + @return A new index path with the receiving index path’s indexes, + excluding the first one. */ - (NSIndexPath *)indexPathByRemovingFirstIndex; + /*! - @method indexPathByPreceedingIndex: - @brief Provides an index path with the given index followed by the indexes of the receiver. - @discussion If the receiver does not contain any indexes the given index is the only index contained in the returned index path. - @param index The index new index preceeding all others - @return A new index path with all the receiver's indexes preceeded by @c index. + @method indexPathByPreceedingIndex: + @brief Provides an index path with the given index followed by the + indexes of the receiver. + + @discussion If the receiver does not contain any indexes the given index is + the only index contained in the returned index path. + + @param index + The index new index preceeding all others + + @return A new index path with all the receiver's indexes preceeded by @c + index. */ - (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index; + +/*! + @method indexPathByIncrementingLastIndex + @brief Provides an index path with the indexes in the receiving index + path where the last one is incremented by @c 1. + + @discussion If the receiver does not contain any indexes an empty index path + is returned. + + @return A new index path with all the receiver's indexes and the last one + incremented by @c 1. + */ - (NSIndexPath *)indexPathByIncrementingLastIndex; + + +/*! + @method indexPathByDecrementingLastIndex + @brief Provides an index path with the indexes in the receiving index + path where the last one is decremented by @c 1. + + @discussion If the receiver does not contain any indexes an empty index path + is returned. + + @return A new index path with all the receiver's indexes and the last one + decremented by @c 1. + */ - (NSIndexPath *)indexPathByDecrementingLastIndex; -- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)length; // use length indexes from the receiver, exception if too much -- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)length; // number of indexes from the beginning to exclude +/*! + @method indexPathByRemovingIndexesFrom: + @brief Provides an index path with the indexes in the recieving index + path up to the index at the given position. + + @discussion If @c from is greater or equal to the number of indexes in the + receiving index path only the indexes to the end of the receiver + are removed. + + @param from + The position of the first index to be excluded in the returned + index path. + + @return An index path with all indexes from the receiver up to position + @c from. + */ +- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)from; + + +/*! + @method indexPathByRemovingIndexesTo: + @brief Provides an index path with the indexes in the receiving index + path where the first indexes are removed. + + + @discussion @c to specifies the number of indexes to be removed from the + front. Thus the index at position @c to will be included in the + returned index path. + + @param to + The number of indexes to remove from the front. + + @return A new index path with all the receiver's indexes exept the first + @c to ones. + */ +- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)to; + + +/*! + @method commonIndexPathWith: + @brief Provides an index path that contains the first indexes of the + receiver that are equal to the given index path. + + @discussion If one index path is completely included in the other a new index + path is returned that is equal to the contained index path. + + @param indexPath + The index path to compare the receiver against. + + @return A new index path with the first indexes of the receiver that are + also present in @c indexPath. + */ - (NSIndexPath *)commonIndexPathWith:(NSIndexPath *)indexPath; @end diff --git a/MathPad/NSIndexPath+MPAdditions.m b/MathPad/NSIndexPath+MPAdditions.m index 14a57a0..56607a7 100644 --- a/MathPad/NSIndexPath+MPAdditions.m +++ b/MathPad/NSIndexPath+MPAdditions.m @@ -8,23 +8,29 @@ #import "NSIndexPath+MPAdditions.h" + + @implementation NSIndexPath (MPAdditions) + - (NSUInteger)firstIndex { return [self indexAtPosition:0]; } + - (NSUInteger)lastIndex { return [self indexAtPosition:self.length-1]; } + - (NSIndexPath *)indexPathByReplacingLastIndexWithIndex:(NSUInteger)index { return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:index]; } + - (NSIndexPath *)indexPathByRemovingFirstIndex { if (self.length <= 1) { @@ -39,6 +45,7 @@ return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length-1]; } + - (NSIndexPath *)indexPathByPreceedingIndex:(NSUInteger)index { NSUInteger newIndexes[self.length+1]; @@ -49,8 +56,12 @@ return [[NSIndexPath alloc] initWithIndexes:newIndexes length:self.length+1]; } + - (NSIndexPath *)indexPathByIncrementingLastIndex { + if (self.length < 1) { + return [[NSIndexPath alloc] init]; + } NSUInteger lastIndex = [self lastIndex]; lastIndex++; return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex]; @@ -58,29 +69,35 @@ - (NSIndexPath *)indexPathByDecrementingLastIndex { + if (self.length < 1) { + return [[NSIndexPath alloc] init]; + } NSUInteger lastIndex = [self lastIndex]; lastIndex--; return [[self indexPathByRemovingLastIndex] indexPathByAddingIndex:lastIndex]; } -- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)length + +- (NSIndexPath *)indexPathByRemovingIndexesFrom:(NSUInteger)from { NSIndexPath *indexPath = [[NSIndexPath alloc] init]; - for (NSUInteger position = 0; position < length; position++) { + for (NSUInteger position = 0; position < MIN(from, self.length); position++) { indexPath = [indexPath indexPathByAddingIndex:[self indexAtPosition:position]]; } return indexPath; } -- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)length + +- (NSIndexPath *)indexPathByRemovingIndexesTo:(NSUInteger)to { NSIndexPath *indexPath = [[NSIndexPath alloc] init]; - for (NSUInteger position = length; position < self.length; position++) { + for (NSUInteger position = to; position < self.length; position++) { indexPath = [indexPath indexPathByAddingIndex:[self indexAtPosition:position]]; } return indexPath; } + - (NSIndexPath *)commonIndexPathWith:(NSIndexPath *)indexPath { NSIndexPath *commonPath = [[NSIndexPath alloc] init]; diff --git a/MathPad/NSRegularExpression+MPParsingAdditions.h b/MathPad/NSRegularExpression+MPParsingAdditions.h index bb98bb3..c6ad829 100644 --- a/MathPad/NSRegularExpression+MPParsingAdditions.h +++ b/MathPad/NSRegularExpression+MPParsingAdditions.h @@ -6,14 +6,102 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -#define MPStringRange(string) NSMakeRange(0, [string length]) -#define MPStringRangeFrom(from, string) NSMakeRange(from, [string length]-from) + @interface NSRegularExpression (MPParsingAdditions) + +/*! + @method firstMathInString: + @brief Returns the first match of the regular expression within the + specified string. + + @discussion This is a convenience method that calls @c + -firstMatchInString:options:range: + + @param string + The string to search. + + @return An NSTextCheckingResult object. This result gives the overall + matched range via its range property, and the range of each + individual capture group via its rangeAtIndex: method. The range + @c {NSNotFound, 0} is returned if one of the capture groups did + not participate in this particular match. + */ - (NSTextCheckingResult *)firstMatchInString:(NSString *)string; + + +/*! + @method firstMathInString:fromIndex + @brief Returns the first match of the regular expression from the + specified index in the string. + + @discussion This is a convenience method that calls @c + -firstMatchInString:options:range: + + @param string + The string to search. + + @param start + The index from which to start searching. + + @return An NSTextCheckingResult object. This result gives the overall + matched range via its range property, and the range of each + individual capture group via its rangeAtIndex: method. The range + @c {NSNotFound, 0} is returned if one of the capture groups did + not participate in this particular match. + */ - (NSTextCheckingResult *)firstMatchInString:(NSString *)string fromIndex:(NSUInteger)start; + + +/*! + @method firstMathInString:options: + @brief Returns the first match of the regular expression within the + specified string. + + @discussion This is a convenience method that calls @c + -firstMatchInString:options:range: + + @param string + The string to search. + + @param options + The matching options to use. See @c NSMatchingOptions for + possible values. + + @return An NSTextCheckingResult object. This result gives the overall + matched range via its range property, and the range of each + individual capture group via its rangeAtIndex: method. The range + @c {NSNotFound, 0} is returned if one of the capture groups did + not participate in this particular match. + */ - (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options; + + +/*! + @method firstMathInString:fromIndex + @brief Returns the first match of the regular expression from the + specified index in the string. + + @discussion This is a convenience method that calls @c + -firstMatchInString:options:range: + + @param string + The string to search. + + @param options + The matching options to use. See @c NSMatchingOptions for + possible values. + + @param start + The index from which to start searching. + + @return An NSTextCheckingResult object. This result gives the overall + matched range via its range property, and the range of each + individual capture group via its rangeAtIndex: method. The range + @c {NSNotFound, 0} is returned if one of the capture groups did + not participate in this particular match. + */ - (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options fromIndex:(NSUInteger)start; @end diff --git a/MathPad/NSRegularExpression+MPParsingAdditions.m b/MathPad/NSRegularExpression+MPParsingAdditions.m index d3f0f42..923df4c 100644 --- a/MathPad/NSRegularExpression+MPParsingAdditions.m +++ b/MathPad/NSRegularExpression+MPParsingAdditions.m @@ -8,23 +8,28 @@ #import "NSRegularExpression+MPParsingAdditions.h" + + @implementation NSRegularExpression (MPParsingAdditions) - (NSTextCheckingResult *)firstMatchInString:(NSString *)string { - return [self firstMatchInString:string options:0 range:MPStringRange(string)]; + return [self firstMatchInString:string options:0 range:NSMakeRange(0, string.length)]; } + - (NSTextCheckingResult *)firstMatchInString:(NSString *)string fromIndex:(NSUInteger)start { return [self firstMatchInString:string options:0 range:NSMakeRange(start, [string length]-start)]; } + - (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options { - return [self firstMatchInString:string options:options range:MPStringRange(string)]; + return [self firstMatchInString:string options:options range:NSMakeRange(0, string.length)]; } + - (NSTextCheckingResult *)firstMatchInString:(NSString *)string options:(NSMatchingOptions)options fromIndex:(NSUInteger)start { return [self firstMatchInString:string options:options range:NSMakeRange(start, [string length]-start)]; diff --git a/MathPad/NSString+MPExpressionElement.h b/MathPad/NSString+MPExpressionElement.h index 96d138b..a19f440 100644 --- a/MathPad/NSString+MPExpressionElement.h +++ b/MathPad/NSString+MPExpressionElement.h @@ -8,6 +8,13 @@ #import "MPExpressionElement.h" + + +/*! + @category NSString (MPExpressionElement) + @brief This category adds @c MPExpressionElement protocol conformance to + the @c NSString class. + */ @interface NSString (MPExpressionElement) @end diff --git a/MathPad/NSString+MPExpressionElement.m b/MathPad/NSString+MPExpressionElement.m index a8f5214..8550a70 100644 --- a/MathPad/NSString+MPExpressionElement.m +++ b/MathPad/NSString+MPExpressionElement.m @@ -8,6 +8,8 @@ #import "NSString+MPExpressionElement.h" + + @implementation NSString (MPExpressionElement) - (BOOL)isString @@ -15,6 +17,7 @@ return YES; } + - (BOOL)isFunction { return NO; diff --git a/MathPadTests/MPRangeTests.m b/MathPadTests/MPRangeTests.m index 0ef142a..9ebb409 100644 --- a/MathPadTests/MPRangeTests.m +++ b/MathPadTests/MPRangeTests.m @@ -25,7 +25,7 @@ XCTAssertEqualObjects(rangePath.location, [[NSIndexPath alloc] initWithIndex:3]); XCTAssertEqual(rangePath.length, 2); - rangePath = [MPRangePath emptyRangePath]; + rangePath = [MPRangePath rangePath]; XCTAssertEqualObjects(rangePath.location, [[NSIndexPath alloc] init]); XCTAssertEqual(rangePath.length, 0); }