//
// MPExpression.h
// MathPad
//
// Created by Kim Wittenburg on 10.08.14.
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
//
/*!
@header
This file contains the MPExpression class and the
MPExpressionElement protocol.
The MPExpression class is used to represent a mathematical
expression. It is used in the storage layer of the MathKit Expression System. An
instance of the MPExpression class stores all contents related to
an expression and can manipulate them. However it is not able to evaluate the
represented expression by itself.
NSString class.
In a valid expression string elements can only contain ASCII valid characters.
(and some other special characters) that make sense in a mathematical context.
Functions are represented by the @link
//apple_ref/occ/cl/MPFunction@/link class. Functions represent everything
beyond simple text (such as powers, parenthesis and roots).
An expression should be pictured as a sequence of symbols that can either be
characters or functions. For the purposes of easier processing of expressions
it is possible to access complete string or function elements. Also there is a
third way of accessing an expression's contents: You can query individual tokens
of an expression. Tokens represent logical units inside an expression. For more
information on tokens see the documentation for @link
//apple_ref/doc/header/MPToken.h MPToken@/link.
@link
//apple_ref/occ/cl/MPFunction@/link for details.
NSString or @link
//apple_ref/occ/cl/MPFunction@/link class. String elements can be
multiple characters long. For example the expression 4+2² consists
of one string element ("4+2") and one function element (the square
function). All elements conform to the MPExpressionElement
protocol.
Token:4+2² consists of 4 tokens: A number token (4), a plus
sign token (+), another number (2) and a power token
(the square function). All tokens conform to the @link
//apple_ref/occ/intf/MPToken@/link protocol.
Symbol:4+2² consists of 4 symbols: The characters "4",
"+" and "2" and the square function.
Item:
The term item is used to describe any of the previous. When the term
item is used in the name of a function or method it necessary to specify
what exactly an item is supposed to be. Usually this is done through reference
frames.
Reference Frame:@link MPReferenceFrame@/link.
Children:@link convertIndex:fromReferenceFrame:toReferenceFrame:@/link@link convertIndex:fromReferenceFrame:toReferenceFrame:offset:@/link@link convertRange:fromReferenceFrame:toReferenceFrame:@/link@link convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:@/linkMPExpression instance can not evaluate itself. There are however
the @link parse:@/link and
@link parseExpectingVariable:errors:@/link methods. These parse and
thereby convert a MPExpression instance into a @link
//apple_ref/occ/cl/MPParsedExpression@/link instance that can be
evaluated. For more information on evaluation see the @link
//apple_ref/occ/cl/MPParsedExpression@/link and @link
//apple_ref/occ/cl/MPTerm@/link class as well as the previously mentioned
methods.
*/
/*!
@const MPIllegalElementException
@abstract Name for an exception that is raised if an invalid element is
added to an expression.
@discussion This exception may be raised during initialization of an
expression or when the expression is mutated. This exception
contains the invalid element in its userInfo
dictionary. You can query it using the
MPIllegalElementExceptionElementKey key.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementException;
/*!
@const MPIllegalElementExceptionElementKey
@abstract Predefined key for an invalid element that caused a
MPIllegalElementException to be raised.
@discussion The invalid element can be of any type. Numbers and structs are
wrapped in an NSNumber or NSValue
instance respectively.
*/
FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey;
/*!
@typedef MPReferenceFrame
@abstract A reference frame specifies the way an item is to be
interpreted.
@constant MPElementReferenceFrame
Specifies that items should be interpreted as elements.
@constant MPSymbolReferenceFrame
Specifies that items should be interpreted as symbols.
@constant MPTokenReferenceFrame
Specifies that items should be interpreted as tokens.
*/
typedef enum {
MPElementReferenceFrame,
MPSymbolReferenceFrame,
MPTokenReferenceFrame
} MPReferenceFrame;
@class MPExpression, MPFunction, MPRangePath, MPParsedExpression;
@protocol MPExpressionElement, MPToken;
/*!
@protocol MPExpressionElement
@abstract This protocol defines the functionality an element in an
expression must have.
*/
@protocol MPExpressionElement NSString. If this method returns YES
you can be sure the receiver is an NSString
instance.
@return YES if the receiver is a string, NO
otherwise.
*/
- (BOOL)isString;
/*!
@method isFunction
@abstract Returns wether the receiver is a function.
@discussion A function is defined by being an instance of
@link //apple_ref/occ/cl/MPFunction@/link. If this
method returns YES you can be sure the receiver is a
@link //apple_ref/occ/cl/MPFunction@/link instance.
@return YES if the receiver is a function, NO
otherwise.
*/
- (BOOL)isFunction;
/*!
@method length
@abstract Calculates the length of the receiver.
@discussion The length of a MPExpressionElement is the number of
symbols it consists of. For strings this is the number of
characters in it. Functions have a length of 1.
@return The receiver's length.
*/
- (NSUInteger)length;
@end
/*!
@class MPExpression
@abstract A MPExpression instance represents a mathematical
expression.
@discussion A expression consists of string elements and function elements.
Functions and expressions together make up the expression tree.
*/
@interface MPExpression : NSObject 0 elements.
@return An empty expression.
*/
- (instancetype)init;
/*!
@method initWithElement:
@abstract Initializes a new expression with the specified
element.
@discussion This method is a convenience initializer to initialize an
expression with a single element.
@param element
The element to be added to the expression. The
element will be copied.
@return An expression initialized with element.
*/
- (instancetype)initWithElement:(idelements.
@discussion All elements must conform to the @link
MPExpressionElement@/link protocol. If one or more objects
do not conform to that protocol a @link
MPIllegalElementException@/link is raised.
This method is the designated initializer for the
MPExpression class.
@param elements
The elements that should be added to the expression. Every
element must conform to the @link
MPExpressionElement@/link protocol. Each element is copied
and the copy is then added to the expression. The object in the
elements array is not modified.
@return An expression containing elements.
*/
- (instancetype)initWithElements:(NSArray *)elements; /* designated initializer */
#pragma mark Querying Expressions
/*!
@methodgroup Querying Expressions
*/
/*!
@property parent
@abstract The receiver's parent.
@discussion The receiver's parent is the function in the expression tree that
contains the receiver.
@warning You should never set this property manually. If you are
implementing a custom subclass of @link
//apple_ref/occ/cl/MPFunction@/link use the @link
//apple_ref/occ/macro/MPFunctionAccessorImplementation@/link
macro.
@return The parent of the receiver or nil if the receiver is
the root expression.
*/
@property (nonatomic, weak) MPFunction *parent;
/*!
@method rootExpression
@abstract Returns the root expression from the receiver's expression tree.
@discussion The root expression is the ultimate parent of all expressions and
functions in the expression tree. A root expression does not have
a parent.
@return The root expression from the receiver's expression tree.
*/
- (MPExpression *)rootExpression;
/*!
@method indexPath
@abstract Returns the index path of the receiver in the expression tree.
@discussion The index path contains the indexes (starting from the root
expression of the receiver's expression tree) that lead to the
receiver. The indexes are expressed in the element reference
frame.
@return The index path of the receiver in the expression tree.
*/
- (NSIndexPath *)indexPath;
/*!
@method countItemsInReferenceFrame:
@abstract Returns the number of items in the receiver in the specified
referenceFrame.
@param referenceFrame
The reference frame that should be used to count the items.
@return The current number of items in the receiver counted in the
specified referenceFrame.
*/
- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method itemAtIndex:referenceFrame:
@abstract Returns the item at anIndex.
@discussion The item is not copied before it is returned. So be aware that
if you mutate a @link
//apple_ref/occ/cl/MPFunction@/link instance returned from
this function the receiver's expression tree will be updated to
reflect the change.
@param anIndex
The index of the item. If the index is greater than the number of
items for the respective reference frame a
NSRangeException is raised.
@param referenceFrame
The reference frame that should be used to identify the item at
anIndex.
@return The item at anIndex.
*/
- (id)itemAtIndex:(NSUInteger)anIndex
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method elementAtIndex:referenceFrame:
@abstract Returns the element at the specified index.
@discussion An element is either a string or a function. If
anIndex identifies a position inside a string
element the complete element is returned.
This method always returns elements. To query items in the
specified reference frame use
@link itemAtIndex:referenceFrame:@/link.
@param anIndex
The index of the element.
@param referenceFrame
The reference frame anIndex is specified in.
@return The element at anIndex.
*/
- (idelement or
NSNotFound if it was not found.
@param element
The element to find.
@return The index of element expressed in the element
reference frame.
*/
- (NSUInteger)indexOfElement:(idrange exceeds the receiver's bounds a
NSRangeException is raised.
@param aRange
The requested range within the receiver's bounds.
@param referenceFrame
The referenceFrame aRange is expressed in.
@return An array containing all elements in the specified range. The type
of the objects depends on the specified
referenceFrame.
*/
- (NSArray *)itemsInRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method allItemsInReferenceFrame:
@abstract Returns an array of all items in the receiver for the specified
reference frame.
@discussion The elements in the returned array are not copied before they are
returned so be aware that mutations to any of the returned
objects will update the receiver's expression tree to reflect the
change.
@return An array of all items in the receiver.
*/
- (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method elementAtIndexPath:
@abstract Returns the element at the specified index path.
@discussion This method finds the elements at the respective indexes starting
at the root expression from the receiver's expression tree.
The returned object can be a NSString, a
@link //apple_ref/occ/cl/MPFunction@/link or a
MPExpression instance depending on the specified
indexPath. If any of the indexes exceed the bounds
of the respective receiver a NSRangeException is
raised.
If the index path does not contain any indexes the receiver
itself is returned.
@param indexPath
The index path the required object is located at. The indexes are
expressed in the element reference frame.
@return The element located at indexPath. The element is not
copied before it is returned. Be aware of the fact that any
mutations made to the returned object will update the receiver's
expression tree to reflect the change.
*/
- (id)elementAtIndexPath:(NSIndexPath *)indexPath;
/*!
@method convertIndex:fromReferenceFrame:toReferenceFrame:
@abstract 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 @link
convertIndex:fromReferenceFrame:toReferenceFrame:offset:@/link
instead.
@param anIndex
The index to be converted.
@param fromReferenceFrame
The reference frame anIndex is specified in.
@param toReferenceFrame
The reference frame anIndex should be converted to.
@return An index specified in the toReferenceFrame. If
anIndex identifies a location inside an item in the
toReferenceFrame the index of the corresponding item
is returned.
*/
- (NSUInteger)convertIndex:(NSUInteger)anIndex
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
/*!
@method convertIndex:fromReferenceFrame:toReferenceFrame:offset:
@abstract Converts an index from one reference frame to another.
@discussion If anIndex identifies a location inside an item in
the toReferenceFrame the index of the corresponding
item is returned. In that case the offset parameter
will be set to the number of symbols the offset goes into the
item at the returned index.
@param anIndex
The index to be converted.
@param fromReferenceFrame
The reference frame anIndex is specified in.
@param toReferenceFrame
The reference frame anIndex should be converted to.
@param offset
This output parameter will be set to the number of symbols that
are between the location identified by anIndex and
the index that is actually returned. The offset is specified in
the symbol reference frame. If you are not interested in the
offset pass NULL.
@return An index specified in the toReferenceFrame
corresponding to anIndex.
*/
- (NSUInteger)convertIndex:(NSUInteger)anIndex
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
offset:(NSUInteger *)offset;
/*!
@method convertRange:fromReferenceFrame:toReferenceFrame:
@abstract Converts a range from one reference frame to another.
@discussion This method just converts the location and target of the range
separately using
@link
convertIndex:fromReferenceFrame:toReferenceFrame:@/link.
It ensures that the returned range definitely includes all items
that were specified by aRange.
The possibility that the returned range may
include more symbols than aRange is ignored. If you
need that information use
@link
convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:@/link.
@param aRange
The range to be converted.
@param fromReferenceFrame
The reference frame aRange is specified in.
@param toReferenceFrame
The reference frame aRange is to be converted to.
@return A range in the toReferenceFrame that includes the
the items identified by aRange.
*/
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame;
/*!
@method convertRange:fromReferenceFrame:toReferenceFrame:leadingOffset:trailingOffset:
@abstract Converts a range from one reference frame to another.
@discussion This method just converts the location and target of the range
separately using
@link
convertIndex:fromReferenceFrame:toReferenceFrame:@/link.
It ensures that the returned range definitely includes all items
that were specified by aRange.
@param aRange
The range to be converted.
@param fromReferenceFrame
The reference frame aRange is specified in.
@param toReferenceFrame
The reference frame aRange is to be converted to.
@param leadingOffset
The offset of the location of the returned range in respect to
the location of aRange.
@param trailingOffset
The offset of the last index in the range in respect to the last
index of aRange.
@return A range in the toReferenceFrame that includes the
the items specified by aRange.
*/
- (NSRange)convertRange:(NSRange)aRange
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
leadingOffset:(NSUInteger *)leadingOffset
trailingOffset:(NSUInteger *)trailingOffset;
#pragma mark Mutating Expressions
/*!
@methodgroup Mutating Expressions
*/
/*!
@method replaceItemsInRange:referenceFrame:withElements:
@abstract Replaces the elements in the specified range with the contents of
the elements array.
@discussion This is the most primitive mutation method of
MPExpression. Every other mutating method must
ultimately call this method.
After the receiver has been mutated the integrety of the receiver
is restored. That basically means that subsequent strings are
joined and empty strings removed. After restoring integrity the
receiver sends a
@link
changedElementsInRangePath:replacementLength:@/link to
itself. For more information see the documentation on that
method.
@param aRange
The range of symbols (including functions) to replace.
@param referenceFrame
The reference frame aRange is specified in.
@param elements
The elements that should replace the symbols specified by
range.
*/
- (void)replaceItemsInRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame
withElements:(NSArray *)elements;
/*!
@method changedElementsInRangePath:replacementLength:
@abstract This is a notification message that is sent from the receiver to
itself after it has been mutated.
@discussion This method does nothing more than notify it's parent that it has
been mutated at the respective range path. If you want to get
notified about changes in an expression you should override this
method instead of @link
replaceItemsInRange:referenceFrame:withElements:@/link
because this method gives you information about the elements that
changed during the mutation.
@param rangePath
The range path at which the receiver was changed starting at the
receiver. The range addressed by rangePath is
expressed in the element reference frame.
@param replacementLength
The number of elements replacing the elements specified by
rangePath (also specified in the element reference
frame).
*/
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
replacementLength:(NSUInteger)replacementLength;
/*!
@method subexpressionFromIndex:referenceFrame:
@abstract Creates an expression from the items in the receiver from the
specified index to the end.
@discussion The items from the receiver are copied. Mutations to the returned
expression will not change the receiver's expression tree.
@param from
The index from which to start constructing the subexpression.
@param referenceFrame
The reference frame from is specified in.
@return An expression containing the items from the specified index to
the end of the receiver.
*/
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method subexpressionToIndex:referenceFrame:
@abstract Creates an expression from the items in the receiver from the
first to the specified item.
@discussion The items from the receiver are copied. Mutations to the returned
expression will not change the receiver's expression tree.
@param to
The index of the first element not to include in the newly
constructed subexpression.
@param referenceFrame
The reference frame to is specified in.
@return An expression containing the items from the first item to the one
at the specified index.
*/
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method subexpressionWithRange:referenceFrame:
@abstract Creates an expression from the items in the receiver within the
specified range.
@discussion The items from the receiver are copied. Mutations to the returned
expression will not change the receiver's expression tree.
@param aRange
Specifies the items to be included in the newly created
subexpression.
@param referenceFrame
The reference frame aRange is specified in.
@return An expression containing the items in aRange.
*/
- (MPExpression *)subexpressionWithRange:(NSRange)aRange
referenceFrame:(MPReferenceFrame)referenceFrame;
#pragma mark Evaluating Expressions
/*!
@methodgroup Evaluating Expressions
*/
/*!
@method parse:
@abstract Parses the receiver.
@discussion This is a convenience method that calls @link
parseExpectingVariable:errors:@/link with NO
as the first argument.
@param errors
If the receiver (or any of its elements) contain syntax errors
this parameter is set to an appropriate value. All items in the
array are NSError instances. This parameter is never
set to an empty array. Pass NULL if you are not
interested in any errors that might occur.
@return A @link //apple_ref/occ/cl/MPParsedExpression@/link
instance that represents the receiver and can be evaluated or
nil if an error occurs. In that case the
errors parameter is set to an array containing at
least one NSError instance.
*/
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors;
/*!
@method parseExpectingVariable:errors:
@abstract Parses the receiver.
@param flag
If YES the receiver must (exept for whitespaces)
begin with a single letter followed by an equals sign. If it
doesn't the expression is considered invalid. Specify
NO If the expression should be evaluatable by itself.
@param errors
If the receiver (or any of its elements) contain syntax errors
this parameter is set to an appropriate value. All items in the
array are NSError instances. This parameter is never
set to an empty array. Pass NULL if you are not
interested in any errors that might occur.
@return A @link //apple_ref/occ/cl/MPParsedExpression@/link
instance that represents the receiver and can be evaluated or
nil if an error occurs. In that case the
errors parameter is set to an array containing at
least one NSError instance.
*/
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
errors:(NSArray *__autoreleasing *)errors;
@end
/*!
@category MPExpression (MPExpressionConvenience)
@abstract This category defines convenience methods for the
MPExpression class.
@discussion All convenience methods are completely defined in terms of other
methods of the MPExpression class.
*/
@interface MPExpression (MPExpressionConvenience)
#pragma mark Querying Expressions
/*!
@methodgroup Querying Expressions
*/
/*!
@method countElements
@abstract 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
@abstract 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
@abstract Returns the number of tokens in the receiver.
@return The number of tokens in the receiver expressed in the token
reference frame.
*/
- (NSUInteger)countTokens;
/*!
@method elementAtIndex:
@abstract Returns the element at the specified index.
@param anIndex
The index of the element specified in the element reference
frame.
@return The element at anIndex.
*/
- (idanIndex.
*/
- (idanIndex.
*/
- (idanElement to the receiver.
@param anElement
The element to append to the receiver.
*/
- (void)appendElement:(idelements to the receiver.
@discussion All objects in the elements array must conform to
the @link MPExpressionElement@/link protocol. If at
least one element does not conform to that protocol a @link
MPIllegalElementException@/link is raised.
@param elements
The elements to append to the receiver.
*/
- (void)appendElements:(NSArray *)elements;
/*!
@method insertElement:atIndex:referenceFrame:
@abstract Inserts anElement at the specified index.
@param anElement
The element to be inserted.
@param index
The index where to insert anElement.
@param referenceFrame
The reference frame index is specified in.
*/
- (void)insertElement:(idelements at the specified index.
@discussion All objects in the elements array must conform to
the @link MPExpressionElement@/link protocol. If at
least one element does not conform to that protocol a @link
MPIllegalElementException@/link is raised.
@param elements
The elements to be inserted.
@param index
The index where to insert elements.
@param referenceFrame
The reference frame index is specified in.
*/
- (void)insertElements:(NSArray *)elements
atIndex:(NSUInteger)index
referenceFrame:(MPReferenceFrame)referenceFrame;
/*!
@method deleteElementsInRange:referenceFrame:
@abstract Removes the elements identified by range from the
receiver.
@discussion If range exceeds the receiver's bounds a
NSRangeException is raised.
@param range
The range of items to remove from the receiver.
@param referenceFrame
The reference frame range is specified in.
*/
- (void)deleteElementsInRange:(NSRange)range
referenceFrame:(MPReferenceFrame)referenceFrame;
@end