// // MPExpression.h // MathPad // // Created by Kim Wittenburg on 10.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // /*! @header The MPExpression class is used to represent a mathematical expression. It is used in the storage layer of the MathKit Expression System. An instance of the MPExpression class stores all contents related to an expression and can manipulate them. However it is not able to evaluate the represented expression by itself.

Structure of an Expression

Expressions consist of two types of elements: string elements and function elements. String elements are represented by the NSString class. In a valid expression string elements can only contain ASCII valid characters. (and some other special characters) that make sense in a mathematical context. Functions are represented by the MPFunction class. Functions represent everything beyond simple text (such as powers, parenthesis and roots). An expression should be pictured as a sequence of symbols that can either be characters or functions. For the purposes of easier processing of expressions it is possible to access complete string or function elements. Also there is a third way of accessing an expression's contents: You can query individual tokens of an expression. Tokens represent logical units inside an expression. For more information on tokens see the MPToken class.

The Expression Tree

Like expressions functions can likewise have expressions as elements (also called 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. See MPFunction for details.

Terminology

When working with expressions note that there is a special terminology for an expression's contents: Element:
An element is either an instance of the NSString or MPFunction class. String elements can be multiple characters long. For example the expression 4+2² consists of one string element ("4+2") and one function element (the square function). All elements conform to the MPExpressionElement protocol. Token:
Tokens are logical units within an expression. For example the expression 4+2² consists of 4 tokens: A number token (4), a plus sign token (+), another number (2) and a power token (the square function). All tokens conform to the MPToken protocol. Symbol:
Symbols are the smallest possible part of an expression. A symbol is either a single character or a single function. For example the expression 4+2² consists of 4 symbols: The characters "4", "+" and "2" and the square function. Item:
The term item is used to describe any of the previous. When the term item is used in the name of a function or method it necessary to specify what exactly an item is supposed to be. Usually this is done through reference frames. Reference Frame:
A reference frame is a way to specify what an item is supposed to mean in a specific context. It can either be an element, a symbol or a token. For more information about reference frames see MPReferenceFrame. Children:
Children are sub-elements of functions. The term children normally refers to any kind of elements in the context of the expression tree. This terminology is consistent within the MathKit Expression System. Because symbols are the smalles possible unit of measurement elements and tokens both consist of one or more symbols. Similarly elements consist of one or more tokens. You can convert between different reference frames using one the following methods:
 @textblock
- convertIndexFromReferenceFrame:toReferenceFrame:
- convertIndexFromReferenceFrame:toReferenceFrame:offset:
 @/textblock
 

Evaluating Expressions

A MPExpression instance can not evaluate itself. There are however the -parse: and -parseExpectingVariableDefinition:errors: methods. These parse and thereby convert a MPExpression instance into a MPParsedExpression instance that can be evaluated. For more information on evaluation see the MPParsedExpression and MPTerm class as well as the previously mentioned methods. */ /*! @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 contains the invalid element in its userInfo dictionary. You can query it using the MPIllegalElementExceptionElementKey key. */ FOUNDATION_EXPORT NSString *const MPIllegalElementException; /*! @const MPIllegalElementExceptionElementKey @brief Predefined key for an invalid element that caused a MPIllegalElementException to be raised. @discussion The invalid element can be of any type. Numbers and structs are wrapped in an NSNumber or NSValue instance respectively. */ FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey; /*! @typedef MPReferenceFrame @brief A reference frame specifies the way an item is to be interpreted. @constant MPElementReferenceFrame Specifies that items should be interpreted as elements. @constant MPSymbolReferenceFrame Specifies that items should be interpreted as symbols. @constant MPTokenReferenceFrame Specifies that items should be interpreted as tokens. */ typedef enum { MPElementReferenceFrame, MPSymbolReferenceFrame, MPTokenReferenceFrame } MPReferenceFrame; @class MPExpression, MPFunction, MPRangePath, MPParsedExpression; @protocol MPExpressionElement, MPToken; /*! @class MPExpression @brief A MPExpression instance represents a mathematical expression. @discussion Every expression consists of string elements (represented by the NSString class) and function elements (represented by the MPFunction class). Functions and expressions make up the expression tree. */ @interface MPExpression : NSObject #pragma mark Creation Methods /*! @methodgroup Creation Methods */ /*! @method init @brief Initlializes a new expression. @discussion This method is a convenience initializer to initialize an expression with 0 elements. @return An empty expression. */ - (instancetype)init; /*! @method initWithElement: @brief Initializes a new expression with the specified element. @discussion This method is a convenience initializer to initialize an expression with a single element. @param element The element to be added to the expression. The element will be copied. @return An expression initialized with element. */ - (instancetype)initWithElement:(id)element; /*! @method initWithElements: @brief Initializes a new expression with the specified elements. @discussion All elements must conform to the MPExpressionElement protocol. If one or more objects do not conform to that protocol a MPIllegalElementException is raised. This method is the designated initializer for the MPExpression class. @param elements The elements that should be added to the expression. Every element must conform to the MPExpressionElement protocol. Each element is copied and the copy is then added to the expression. The object in the elements array is not modified. @return An expression containing elements. */ - (instancetype)initWithElements:(NSArray *)elements; /* designated initializer */ #pragma mark Querying Expressions /*! @methodgroup Querying Expressions */ /*! @property parent @brief The receiver's parent. @discussion The receiver's parent is the function in the expression tree that contains the receiver. @warning You should never set this property manually. If you are implementing a custom subclass of MPFunction use the MPFunctionAccessorImplementation macro. @return The parent of the receiver or nil if the receiver is the root expression. */ @property (nonatomic, weak) MPFunction *parent; /*! @method rootExpression @brief Returns the root expression from the receiver's expression tree. @discussion The root expression is the ultimate parent of all expressions and functions in the expression tree. A root expression does not have a parent. @return The root expression from the receiver's expression tree. */ - (MPExpression *)rootExpression; /*! @method indexPath @brief Returns the index path of the receiver in the expression tree. @discussion The index path contains the indexes (starting from the root expression of the receiver's expression tree) that lead to the receiver. The indexes are expressed in the element reference frame. @return The index path of the receiver in the expression tree. */ - (NSIndexPath *)indexPath; /*! @method countItemsInReferenceFrame: @brief Returns the number of items in the receiver in the specified referenceFrame. @param referenceFrame The reference frame that should be used to count the items. @return The current number of items in the receiver counted in the specified referenceFrame. */ - (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame; /*! @method itemAtIndex:referenceFrame: @brief Returns the item at anIndex. @discussion The item is not copied before it is returned. So be aware that if you mutate a MPFunction instance returned from this function the receiver's expression tree will be updated to reflect the change. @param anIndex The index of the item. If the index is greater than the number of items for the respective reference frame a NSRangeException is raised. @param referenceFrame The reference frame that should be used to identify the item at anIndex. @return The item at 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 anIndex identifies a position inside a string element the complete element is returned. This method always returns elements. To query items in the specified reference frame use -itemAtIndex:referenceFrame:. @param anIndex The index of the element. @param referenceFrame The reference frame anIndex is specified in. @return The element at anIndex. */ - (id)elementAtIndex:(NSUInteger)anIndex referenceFrame:(MPReferenceFrame)referenceFrame; /*! @method indexOfElement: @brief Returns the index of element or NSNotFound if it was not found. @param element The element to find. @return The index of element expressed in the element reference frame. */ - (NSUInteger)indexOfElement:(id)element; /*! @method itemsInRange:referenceFrame: @brief Returns an array of the items that are located in the specified range. @discussion The objects in the returned array are copied before they are returned. If the range exceeds the receiver's bounds a NSRangeException is raised. @param aRange The requested range within the receiver's bounds. @param referenceFrame The referenceFrame aRange is expressed in. @return An array containing all elements in the specified range. The type of the objects depends on the specified referenceFrame. */ - (NSArray *)itemsInRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame; /*! @method allItemsInReferenceFrame: @brief Returns an array of all items in the receiver for the specified reference frame. @discussion The elements in the returned array are not copied before they are returned so be aware that mutations to any of the returned objects will update the receiver's expression tree to reflect the change. @return An array of all items in the receiver. */ - (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame; /*! @method elementAtIndexPath: @brief Returns the element at the specified index path. @discussion This method finds the elements at the respective indexes starting at the root expression from the receiver's expression tree. The returned object can be a NSString, a MPFunction or an MPExpression instance depending on the specified indexPath. If any of the indexes exceed the bounds of the respective receiver a 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 indexPath. The element is not copied before it is returned. Be aware of the fact that any mutations made to the returned object will update the receiver's expression tree to reflect the change. */ - (id)elementAtIndexPath:(NSIndexPath *)indexPath; /*! @method convertIndex:fromReferenceFrame:toReferenceFrame: @brief Converts an index from one reference frame to another. @discussion This method ignores any offsets into items the conversion between reference frames can cause. If you are interested in the offset use -convertIndex:fromReferenceFrame:toReferenceFrame:offset: instead. @param anIndex The index to be converted. @param fromReferenceFrame The reference frame anIndex is specified in. @param toReferenceFrame The reference frame anIndex should be converted to. @return An index specified in the toReferenceFrame. If anIndex identifies a location inside an item in the toReferenceFrame the index of the corresponding item is returned. */ - (NSUInteger)convertIndex:(NSUInteger)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame; /*! @method convertIndex:fromReferenceFrame:toReferenceFrame:offset: @brief Converts an index from one reference frame to another. @discussion If anIndex identifies a location inside an item in the toReferenceFrame the index of the corresponding item is returned. In that case the offset parameter will be set to the number of symbols the offset goes into the item at the returned index. @param anIndex The index to be converted. @param fromReferenceFrame The reference frame anIndex is specified in. @param toReferenceFrame The reference frame anIndex should be converted to. @param offset This output parameter will be set to the number of symbols that are between the location identified by 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 NULL. @return An index specified in the toReferenceFrame corresponding to anIndex. */ - (NSUInteger)convertIndex:(NSUInteger)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame offset:(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 -convertIndex:fromReferenceFrame:toReferenceFrame:. It ensures that the returned range definitely includes all items that were specified by aRange. The possibility that the returned range may include more symbols than aRange is ignored. If you need that information use -convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:. @param aRange The range to be converted. @param fromReferenceFrame The reference frame aRange is specified in. @param toReferenceFrame The reference frame aRange is to be converted to. @return A range in the toReferenceFrame that includes the the items identified by 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 -convertIndex:fromReferenceFrame:toReferenceFrame:. It ensures that the returned range definitely includes all items that were specified by aRange. @param aRange The range to be converted. @param fromReferenceFrame The reference frame aRange is specified in. @param toReferenceFrame The reference frame aRange is to be converted to. @param leadingOffset The offset of the location of the returned range in respect to the location of aRange. @param trailingOffset The offset of the last index in the range in respect to the last index of aRange. @return A range in the toReferenceFrame that includes the the items specified by aRange. */ - (NSRange)convertRange:(NSRange)aRange fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame leadingOffset:(NSUInteger *)leadingOffset trailingOffset:(NSUInteger *)trailingOffset; #pragma mark Mutating Expressions /*! @methodgroup Mutating Expressions */ /*! @method replaceItemsInRange:referenceFrame:withElements: @brief Replaces the elements in the specified range with the contents of the elements array. @discussion This is the most primitive mutation method of MPExpression. Every other mutating method must ultimately call this method. After the receiver has been mutated the integrety of the receiver is restored. That basically means that subsequent strings are joined and empty strings removed. After restoring integrity the receiver sends a -changedElementsInIndexedRangePath:replacementLength: to itself. For more information see the documentation on that method. @param aRange The range of symbols (including functions) to replace. @param referenceFrame The reference frame aRange is specified in. @param elements The elements that should replace the symbols specified by range. */ - (void)replaceItemsInRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame withElements:(NSArray *)elements; /*! @method changedElementsInRangePath:replacementLength: @brief This is a notification message that is sent from the receiver to itself after it has been mutated. @discussion This method does nothing more than notify it's parent that it has been mutated at the respective range path. If you want to get notified about changes in an expression you should override this method instead of -replaceSymbolsInRange:withElements: because this method gives you information about the elements that changed during the mutation. @param rangePath The range path at which the receiver was changed starting at the receiver. The range addressed by rangePath is expressed in the element reference frame. @param replacementLength The number of elements replacing the elements specified by rangePath (also specified in the element reference frame). */ - (void)changedElementsInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)replacementLength; /*! @method subexpressionFromIndex:referenceFrame: @brief Creates an expression from the items in the receiver from the specified index to the end. @discussion The items from the receiver are copied. Mutations to the returned expression will not change the receiver's expression tree. @param from The index from which to start constructing the subexpression. @param referenceFrame The reference frame from is specified in. @return An expression containing the items from the specified index to the end of the receiver. */ - (MPExpression *)subexpressionFromIndex:(NSUInteger)from referenceFrame:(MPReferenceFrame)referenceFrame; /*! @method subexpressionToIndex:referenceFrame: @brief Creates an expression from the items in the receiver from the first to the specified item. @discussion The items from the receiver are copied. Mutations to the returned expression will not change the receiver's expression tree. @param to The index of the first element not to include in the newly constructed subexpression. @param referenceFrame The reference frame to is specified in. @return An expression containing the items from the first item to the one at the specified index. */ - (MPExpression *)subexpressionToIndex:(NSUInteger)to referenceFrame:(MPReferenceFrame)referenceFrame; /*! @method subexpressionWithRange:referenceFrame: @brief Creates an expression from the items in the receiver within the specified range. @discussion The items from the receiver are copied. Mutations to the returned expression will not change the receiver's expression tree. @param aRange Specifies the items to be included in the newly created subexpression. @param referenceFrame The reference frame aRange is specified in. @return An expression containing the items in aRange. */ - (MPExpression *)subexpressionWithRange:(NSRange)aRange referenceFrame:(MPReferenceFrame)referenceFrame; #pragma mark Evaluating Expressions /*! @methodgroup Evaluating Expressions */ /*! @method parse: @brief Parses the receiver. @discussion This is a convenience method that calls -parseExpectingVariable:errors: with NO as the first argument. @param errors If the receiver (or any of its elements) contain syntax errors this parameter is set to an appropriate value. All items in the array are NSError instances. This parameter is never set to an empty array. Pass NULL if you are not interested in any errors that might occur. @return A MPParsedExpression instance that represents the receiver and can be evaluated or nil if an error occurs. In that case the errors parameter is set to an array containing at least one NSError instance. */ - (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors; /*! @method parseExpectingVariable:errors: @brief Parses the receiver. @param flag If YES the receiver must (exept for whitespaces) begin with a single letter followed by an equals sign. If it doesn't the expression is considered invalid. Specify NO If the expression should be evaluatable by itself. @param errors If the receiver (or any of its elements) contain syntax errors this parameter is set to an appropriate value. All items in the array are NSError instances. This parameter is never set to an empty array. Pass NULL if you are not interested in any errors that might occur. @return A MPParsedExpression instance that represents the receiver and can be evaluated or nil if an error occurs. In that case the errors parameter is set to an array containing at least one NSError instance. */ - (MPParsedExpression *)parseExpectingVariable:(BOOL)flag errors:(NSArray *__autoreleasing *)errors; @end /*! @category MPExpression (MPExpressionConvenience) @brief This category defines convenience methods for the MPExpression class. @discussion All convenience methods are completely defined in terms of other methods of the MPExpression class. */ @interface MPExpression (MPExpressionConvenience) #pragma mark Querying Expressions /*! @methodgroup Querying Expressions */ /*! @method countElements @brief Returns the number of elements in the receiver. @return The number of elements in the receiver expressed in the element reference frame. */ - (NSUInteger)countElements; /*! @method countSymbols @brief Returns the number of symbols in the receiver. @return The number of symbols in the receiver expressed in the symbol reference frame. */ - (NSUInteger)countSymbols; /*! @method countTokens @brief Returns the number of tokens in the receiver. @return The number of tokens in the receiver expressed in the token reference frame. */ - (NSUInteger)countTokens; /*! @method elementAtIndex: @brief Returns the element at the specified index. @param anIndex The index of the element specified in the element reference frame. @return The element at 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 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 anIndex. */ - (id)tokenAtIndex:(NSUInteger)anIndex; #pragma mark Mutating Expressions /*! @methodgroup Mutating Expressions */ /*! @method appendElement: @brief Appends anElement to the receiver. @param anElement The element to append to the receiver. */ - (void)appendElement:(id)anElement; /*! @method appendElements: @brief Appends the objects from elements to the receiver. @discussion All objects in the elements array must conform to the MPExpressionElement protocol. If at least one element does not conform to that protocol a MPIllegalElementException is raised. @param elements The elements to append to the receiver. */ - (void)appendElements:(NSArray *)elements; /*! @method insertElement:atIndex:referenceFrame: @brief Inserts anElement at the specified index. @param anElement The element to be inserted. @param index The index where to insert anElement. @param referenceFrame The reference frame index is specified in. */ - (void)insertElement:(id)anElement atIndex:(NSUInteger)index referenceFrame:(MPReferenceFrame)referenceFrame; /*! @method insertElements:atIndex:referenceFrame: @brief Inserts elements at the specified index. @discussion All objects in the elements array must conform to the MPExpressionElement protocol. If at least one element does not conform to that protocol a MPIllegalElementException is raised. @param elements The elements to be inserted. @param index The index where to insert elements. @param referenceFrame The reference frame index is specified in. */ - (void)insertElements:(NSArray *)elements atIndex:(NSUInteger)index referenceFrame:(MPReferenceFrame)referenceFrame; /*! @method deleteElementsInRange: @brief Removes the elements identified by range from the receiver. @discussion If range exceeds the receiver's bounds a NSRangeException is raised. @param range The range of items to remove from the receiver. @param referenceFrame The reference frame range is specified in. */ - (void)deleteElementsInRange:(NSRange)range referenceFrame:(MPReferenceFrame)referenceFrame; @end