Archived
1

Added Documentation

This commit is contained in:
Kim Wittenburg
2014-11-10 21:45:50 +01:00
parent f4f924bd71
commit 10f0e73ad3
32 changed files with 1847 additions and 318 deletions

View File

@@ -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 = "<group>";
};
3BC46B4C19B38CD20033F13A /* Independant Classes */ = {
isa = PBXGroup;
children = (
3B87E35B1900933200259938 /* MPRangePath.h */,
3B87E35C1900933200259938 /* MPRangePath.m */,
);
name = "Independant Classes";
sourceTree = "<group>";
};
3BC46B4D19B38CFB0033F13A /* Helpers */ = {
isa = PBXGroup;
children = (

View File

@@ -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.
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.
@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.
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 <NSCopying, NSCoding>
#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<MPExpressionElement>)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<MPExpressionElement>)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<MPExpressionElement>)elementAtIndex:(NSUInteger)index;
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)index;
- (id<MPToken>)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<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex;
/*!
@method symbolAtIndex:
@brief Returns the symbol at the specified index.
@param anIndex
The index of the symbol specified in the symbol reference frame.
@return The symbol at @c anIndex.
*/
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)anIndex;
/*!
@method tokenAtIndex:
@brief Returns the token at the specified index.
@param anIndex
The index of the token specified in the token reference frame.
@return The token at @c anIndex.
*/
- (id<MPToken>)tokenAtIndex:(NSUInteger)anIndex;
#pragma mark Mutating Expressions
/*!
@methodgroup Mutating Expressions
*/
/*!
@method appendElement:
@brief Appends @c anElement to the receiver.
@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<MPExpressionElement>)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<MPExpressionElement>)anElement
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method insertElements:atIndex:referenceFrame:
@brief Inserts @c elements at the specified index.
@param elements
The elements to be inserted.
@param index
The index where to insert @c elements.
@param referenceFrame
The reference frame @c index is specified in.
*/
- (void)insertElements:(NSArray *)elements
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method deleteElementsInRange:
@brief Removes the elements specified by @c range from the receiver.
@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;

View File

@@ -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<MPExpressionElement>)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<MPExpressionElement>)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<MPExpressionElement>)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<MPExpressionElement> 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<MPExpressionElement> 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<MPExpressionElement>)elementAtIndex:(NSUInteger)index
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
{
return [self itemAtIndex:index
return [self itemAtIndex:anIndex
referenceFrame:MPElementReferenceFrame];
}
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)index
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)anIndex
{
return [self itemAtIndex:index
return [self itemAtIndex:anIndex
referenceFrame:MPSymbolReferenceFrame];
}
- (id<MPToken>)tokenAtIndex:(NSUInteger)index
- (id<MPToken>)tokenAtIndex:(NSUInteger)anIndex
{
return [self itemAtIndex:index
return [self itemAtIndex:anIndex
referenceFrame:MPTokenReferenceFrame];
}
#pragma mark Mutating Expressions
- (void)appendElement:(id<MPExpressionElement>)anElement
{
[self appendElements:@[anElement]];
}
- (void)appendElements:(NSArray *)elements
{
[self replaceItemsInRange:NSMakeRange([self countItemsInReferenceFrame:MPSymbolReferenceFrame], 0)
@@ -693,6 +716,7 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey";
withElements:elements];
}
- (void)insertElement:(id<MPExpressionElement>)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
{

View File

@@ -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 <NSObject, NSCopying, NSCoding>
/*!
@method isString
@brief Returns wether the receiver is a string.
@discussion A string is defined by being an instance of @c NSString. If this
method returns @c YES you can be sure it is an @c NSString
instance.
@return @c YES if the receiver is a string, @c NO otherwise.
*/
- (BOOL)isString;
/*!
@method isFunction
@brief Returns wether the receiver is a function.
@discussion A function is defined by being an instance of @c MPFunction. If
this method returns @c YES you can be sure it is a @c MPFunction
instance.
@return @c YES if the receiver is a function, @c NO otherwise.
*/
- (BOOL)isFunction;
/*!
@method length
@brief Calculates the length of the receiver.
@discussion The length of a @c MPExpressionElement is the number of symbols
it consists of. For strings this is the number of characters in
it. Functions have a length of @c 1.
@return The receiver's length.
*/
- (NSUInteger)length;
@end

View File

@@ -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

View File

@@ -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
{

View File

@@ -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 <MPExpressionTreeElement>
/*!
@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;

View File

@@ -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];

View File

@@ -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 <NSObject>
@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

View File

@@ -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) <MPToken>
@end

View File

@@ -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];

View File

@@ -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 <NSCoding, NSCopying, MPExpressionElement>
#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

View File

@@ -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;

View File

@@ -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];

View File

@@ -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 <NSCopying, NSCoding>
#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;

View File

@@ -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<location="];
@@ -129,30 +147,38 @@
return description.copy;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone
{
MPRangePath *copy = [[MPRangePath allocWithZone:zone] initWithLocation:self.location.copy length:self.length];
return copy;
}
#pragma mark - NSCoding
- (id)initWithCoder:(NSCoder *)aDecoder
{
return [self initWithLocation:[aDecoder decodeObjectForKey:@"location"]
length:[aDecoder decodeIntegerForKey:@"length"]];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.location forKey:@"location"];
[aCoder encodeInteger:self.length forKey:@"length"];
}
@end
@implementation MPExpression (MPRangeExtension)
- (NSArray *)itemsInRangePath:(MPRangePath *)rangePath
@@ -162,6 +188,7 @@
return [targetExpression itemsInRange:rangePath.rangeAtLastIndex referenceFrame:referenceFrame];
}
- (MPExpression *)subexpressionWithRangePath:(MPRangePath *)aRangePath
referenceFrame:(MPReferenceFrame)referenceFrame
{
@@ -174,6 +201,7 @@
referenceFrame:referenceFrame];
}
- (void)replaceItemsInRangePath:(MPRangePath *)rangePath
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements

View File

@@ -8,11 +8,37 @@
#import "MPFunction.h"
@class MPSuffixFunction;
@protocol MPValue;
/*!
@class MPSuffixFunction
@brief This is the common superclass for all functions that apply to the
value preceeding them.
@discussion One example for a suffix function is a power. Powers apply to a
base value and are evaluated with very high priority. In fact the
base value may not be used without previously having evaluated
the suffix function.
Another special thing about suffix functions is that they need to
know more than their own children (namely the base value) to be
evaluated. To be able to do this suffix functions have a special
property @c baseValue that is set before the function is
evaluated.
*/
@interface MPSuffixFunction : MPFunction
/*!
@property baseValue
@brief The receiver's base value.
@discussion The base value is the thing a suffix function applies to (e.g.
a power's base).
*/
@property (nonatomic, strong) id<MPValue> baseValue;
@end

View File

@@ -8,6 +8,8 @@
#import "MPSuffixFunction.h"
@implementation MPSuffixFunction
@end

View File

@@ -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

View File

@@ -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];

View File

@@ -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 <MPExpressionTreeElement>
/*!
@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

View File

@@ -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 <NSObject>
/*!
@method tokenType
@brief Returns the receiver's token type.
@discussion The token type identifies what kind of token the receiver is. For
more information see the documentation on the @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 <MPToken>
- (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

View File

@@ -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;

View File

@@ -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<MPToken>)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<MPToken>)peekNextToken;
/*!
@method currentTokenConsumed
@brief Marks the current token as consumed and discards it.
*/
- (void)currentTokenConsumed;
@end

View File

@@ -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<MPToken>)currentToken
{
[self skipWhitespaces];
if (_currentTokenIndex >= _tokens.count) {
return [[MPToken alloc] initEOFTokenAtLocation:eofLocation];
return nil;
}
return _tokens[_currentTokenIndex];
}
- (id<MPToken>)peekNextToken
{
NSUInteger currentTokenIndex = _currentTokenIndex;
@@ -106,15 +124,17 @@
return token;
}
- (void)currentTokenConsumed
{
[self currentToken];
++_currentTokenIndex;
}
- (void)dealloc
{
free(whitespaceIgnores);
free(self.whitespaceIgnores);
}
@end

View File

@@ -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 paths length is 1 or less.
@return A new index path with the receiving index paths 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
paths length is 1 or less.
@return A new index path with the receiving index paths 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

View File

@@ -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];

View File

@@ -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

View File

@@ -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)];

View File

@@ -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) <MPExpressionElement>
@end

View File

@@ -8,6 +8,8 @@
#import "NSString+MPExpressionElement.h"
@implementation NSString (MPExpressionElement)
- (BOOL)isString
@@ -15,6 +17,7 @@
return YES;
}
- (BOOL)isFunction
{
return NO;

View File

@@ -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);
}