Fundamental Redesign of the Model
Started to add Documentation
This commit is contained in:
@@ -12,36 +12,281 @@
|
|||||||
@class MPExpression, MPFunction, MPRangePath;
|
@class MPExpression, MPFunction, MPRangePath;
|
||||||
@protocol MPExpressionElement;
|
@protocol MPExpressionElement;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@class MPExpression
|
||||||
|
@brief An expression is the base object for any mathematical expression.
|
||||||
|
|
||||||
|
@discussion Every expression consists of string elements (represented by the
|
||||||
|
@c NSString class) and function (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.
|
||||||
|
|
||||||
|
Through this organization expression are organized in a tree-like
|
||||||
|
structure (called the 'expression tree') allowing easy and
|
||||||
|
logical access to each element.
|
||||||
|
|
||||||
|
An expression can evaluate itself giving you either a
|
||||||
|
result or possibly an error if the expression was not constructed
|
||||||
|
correctly.
|
||||||
|
*/
|
||||||
@interface MPExpression : NSObject <NSCopying, NSCoding>
|
@interface MPExpression : NSObject <NSCopying, NSCoding>
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Creation Methods
|
#pragma mark Creation Methods
|
||||||
- (instancetype)init; // Convenience
|
|
||||||
- (instancetype)initWithElement:(id<MPExpressionElement>)element; // Convenience
|
|
||||||
- (instancetype)initWithElements:(NSArray *)elements; // Designated Initializer
|
/*!
|
||||||
|
@method init
|
||||||
|
@brief Initlializes a newly created expression.
|
||||||
|
|
||||||
|
@discussion This method is a convenience initializer to initialize an empty
|
||||||
|
expression.
|
||||||
|
|
||||||
|
@return An expression.
|
||||||
|
*/
|
||||||
|
- (instancetype)init;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method initWithElement:
|
||||||
|
@brief Initializes a newly created expression with one 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 @c element will be
|
||||||
|
copied.
|
||||||
|
|
||||||
|
@return An expression initialized with @c element.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithElement:(id<MPExpressionElement>)element;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method initWithElements:
|
||||||
|
@brief Initializes a newly created expression with the given elements.
|
||||||
|
|
||||||
|
@discussion This method is the designated initializer for the @c MPExpression
|
||||||
|
class.
|
||||||
|
|
||||||
|
@param elements
|
||||||
|
The elements that should be added to the expression. Each element
|
||||||
|
is copied and the copy is then added to the expression.
|
||||||
|
|
||||||
|
@return An expression containing the elements from @c elements.
|
||||||
|
*/
|
||||||
|
- (instancetype)initWithElements:(NSArray *)elements; /* designated initializer */
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Working With the Expression Tree
|
#pragma mark Working With the Expression Tree
|
||||||
@property (nonatomic, weak) MPFunction *parent; // Set automatically, nil for root expression
|
|
||||||
|
|
||||||
- (void)fixElements; // Called automatically, removes empty elements, joins subsequent strings
|
|
||||||
|
/*!
|
||||||
|
@property parent
|
||||||
|
@brief The receiver's parent.
|
||||||
|
|
||||||
|
@discussion Expressions are organized in a tree-like structure. Through this
|
||||||
|
property an expression's containing function can be accessed.
|
||||||
|
You should not set this property manually because that can cause
|
||||||
|
inconsistencies in the expression tree.
|
||||||
|
|
||||||
|
@return The parent of the receiver or @c nil if the receiver is the root
|
||||||
|
expression.
|
||||||
|
*/
|
||||||
|
@property (nonatomic, weak) MPFunction *parent;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method fixElements
|
||||||
|
@brief Repairs any inconsistencies in the receiver.
|
||||||
|
|
||||||
|
@discussion This method goes over all elements in the receiver and tries to
|
||||||
|
repair inconsistencies that occured when mutating the receiver.
|
||||||
|
|
||||||
|
Since this method is called automatically everytime the receiver
|
||||||
|
is mutated there should be little need for you to call it
|
||||||
|
yourself.
|
||||||
|
*/
|
||||||
|
- (void)fixElements;
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Primitive Methods
|
#pragma mark Primitive Methods
|
||||||
- (NSUInteger)length;
|
|
||||||
- (NSUInteger)numberOfElements;
|
|
||||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)index;
|
|
||||||
- (NSArray *)elementsInRange:(NSRange)range;
|
|
||||||
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;
|
|
||||||
- (void)replaceElementsInRange:(NSRange)range withElements:(NSArray *)elements;
|
|
||||||
// TODO: - (NSUInteger)indexOfElementAtLocation:(NSUInteger)location;
|
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method length
|
||||||
|
@brief Returns the length of the receiver.
|
||||||
|
|
||||||
|
@discussion The length of an expression is calculated by walking over each
|
||||||
|
element in the receiver and sending it a @c length message. This
|
||||||
|
method should be used to determine the number of digits or
|
||||||
|
symbols in an expression.
|
||||||
|
|
||||||
|
To address a symbol in the expression counted in the @c length
|
||||||
|
reference frame of an expression the word 'location' is used.
|
||||||
|
|
||||||
|
@return The length of the receiver. This is the number of symbols in all
|
||||||
|
elements in the receiver where a function element is counted as a
|
||||||
|
single symbol.
|
||||||
|
*/
|
||||||
|
- (NSUInteger)length;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method numberOfElements
|
||||||
|
@brief Returns the number of elements in the receiver.
|
||||||
|
|
||||||
|
@discussion The number of elements may vary from the number of elements that
|
||||||
|
were added to the receiver (using either @c -initWithElements: or
|
||||||
|
@ -replaceElementsInRange:withElements:)
|
||||||
|
|
||||||
|
To address a specific symbol in an expression the word 'index' is
|
||||||
|
used. The index of elements is used when you access an
|
||||||
|
expression's elements using subscript syntax.
|
||||||
|
|
||||||
|
@return The current number of elements in the receiver.
|
||||||
|
*/
|
||||||
|
- (NSUInteger)numberOfElements;
|
||||||
|
|
||||||
|
|
||||||
|
/* Subscripting is supported for indexes and elements */
|
||||||
|
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||||
|
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method elementAtIndex:
|
||||||
|
@brief Returns the element at @c anIndex.
|
||||||
|
|
||||||
|
@discussion The element 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.
|
||||||
|
|
||||||
|
This method can also be called using indexed subscript getter
|
||||||
|
syntax.
|
||||||
|
|
||||||
|
@param anIndex
|
||||||
|
The index of the element. If the index is greater than or equal
|
||||||
|
to the number of elements in the receiver an @c NSRangeException
|
||||||
|
is raised.
|
||||||
|
|
||||||
|
@return The element located at @c anIndex.
|
||||||
|
*/
|
||||||
|
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method elementsInRange:
|
||||||
|
@brief Returns an array of the elements that are located in the
|
||||||
|
specified range. The range is specified in indexes.
|
||||||
|
|
||||||
|
@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.
|
||||||
|
|
||||||
|
If the @c range exceeds the receiver's bounds an @c
|
||||||
|
NSRangeException is raised.
|
||||||
|
|
||||||
|
@param range
|
||||||
|
The requested range within the receiver's bounds.
|
||||||
|
|
||||||
|
@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 *)elementsInRange:(NSRange)range;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method indexOfElement:
|
||||||
|
@brief Returns the index of @c element or @c NSNotFound if it was not
|
||||||
|
found.
|
||||||
|
|
||||||
|
@discussion
|
||||||
|
|
||||||
|
@param
|
||||||
|
|
||||||
|
@return
|
||||||
|
*/
|
||||||
|
#warning Implementation may be faulty
|
||||||
|
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method replaceSymbolsInRange: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 @c -fixElements is called to
|
||||||
|
restore integrity of the receiver.
|
||||||
|
|
||||||
|
After the receiver has been mutated (and integrity has been
|
||||||
|
restored) the receiver sends a @c
|
||||||
|
-didChangeElementsInRangePath:replacementLength: to itself. For
|
||||||
|
more information see the documentation on that method.
|
||||||
|
|
||||||
|
@param range
|
||||||
|
The @c range is specified in the length reference
|
||||||
|
frame. Because of this this method can be directly used for user
|
||||||
|
interaction.
|
||||||
|
|
||||||
|
@param elements
|
||||||
|
The elements that should replace the symbols specified by @c
|
||||||
|
range.
|
||||||
|
*/
|
||||||
|
- (void)replaceSymbolsInRange:(NSRange)range
|
||||||
|
withElements:(NSArray *)elements;
|
||||||
|
// TODO: - (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location;
|
||||||
|
|
||||||
|
#warning Evaluating must possibly return error
|
||||||
- (double)doubleValue; // Evaluates Expression
|
- (double)doubleValue; // Evaluates Expression
|
||||||
|
|
||||||
#pragma mark Notifications
|
#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
|
// 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
|
||||||
// TODO: More notifications
|
// TODO: More notifications
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@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
|
||||||
|
been mutated at the receiver's index. If you need to know about
|
||||||
|
changes in an expression you should override this method instead
|
||||||
|
of @c -replaceSymbolsInRange:withElements because this method
|
||||||
|
gives you information about the number of elements changed during
|
||||||
|
the mutation.
|
||||||
|
|
||||||
|
@param rangePath
|
||||||
|
The range path at which the receiver was changed starting at the
|
||||||
|
receiver.
|
||||||
|
|
||||||
|
@param replacementLength
|
||||||
|
The number of elements replacing the elements specified by @c
|
||||||
|
rangePath.
|
||||||
|
*/
|
||||||
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
||||||
replacementLength:(NSUInteger)replacementLength;
|
replacementLength:(NSUInteger)replacementLength;
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Basic NSObject Methods
|
#pragma mark Basic NSObject Methods
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method isEqualToExpression:
|
||||||
|
@brief Returns wether the receiver is equal to @c anExpression.
|
||||||
|
|
||||||
|
@param anExpression
|
||||||
|
The expression the receiver should be compared to.
|
||||||
|
|
||||||
|
@return @c YES if @c anExpression is equal to the receiver, @c NO
|
||||||
|
otherwise.
|
||||||
|
*/
|
||||||
- (BOOL)isEqualToExpression:(MPExpression *)anExpression;
|
- (BOOL)isEqualToExpression:(MPExpression *)anExpression;
|
||||||
|
|
||||||
- (NSString *)description;
|
- (NSString *)description;
|
||||||
@@ -49,21 +294,152 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------- */
|
||||||
|
/* Extension Methods */
|
||||||
|
/* --------------------------------------------------------------------------- */
|
||||||
|
|
||||||
@interface MPExpression (MPExpressionExtension)
|
@interface MPExpression (MPExpressionExtension)
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Working With the Expression Tree
|
#pragma mark Working With the Expression Tree
|
||||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath; // Returns an MPExpression or id<MPExpressionElement>
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method elementAtIndexPath:
|
||||||
|
@brief Returns the element at the specified index path.
|
||||||
|
|
||||||
|
@discussion 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.
|
||||||
|
|
||||||
|
@return The element located at @c indexPath. The element is not copied
|
||||||
|
before it is returned. Be aware of the fact that any mutations
|
||||||
|
made to the returned object are reflected in the receiver.
|
||||||
|
*/
|
||||||
|
- (id)elementAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method elementsInRangePath:
|
||||||
|
@brief Returns the elements in the specified range path.
|
||||||
|
|
||||||
|
@discussion If any of the indexes or the range exceed the bounds of the
|
||||||
|
respective receiver an @c NSRangeException is raised.
|
||||||
|
|
||||||
|
@param rangePath
|
||||||
|
The range path the requested objects are located at.
|
||||||
|
|
||||||
|
@return An array of objects specified by the range path. The returned
|
||||||
|
elements are not copied before they are returned. Be aware that
|
||||||
|
any mutations made to the returned objects are reflected in the
|
||||||
|
receiver.
|
||||||
|
*/
|
||||||
- (NSArray *)elementsInRangePath:(MPRangePath *)rangePath;
|
- (NSArray *)elementsInRangePath:(MPRangePath *)rangePath;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method indexPath
|
||||||
|
@brief Returns the index path of the receiver in the expression tree.
|
||||||
|
|
||||||
|
@discussion The index path is calculated by walking up the expression tree
|
||||||
|
collecting the respective index of the receiver.
|
||||||
|
|
||||||
|
@return The index path of the receiver in the expression tree.
|
||||||
|
*/
|
||||||
|
- (NSIndexPath *)indexPath;
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Working With Expressions
|
#pragma mark Working With Expressions
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method subexpressionFromLocation:
|
||||||
|
@brief Creates a new expression from the specified index (inclusive) to
|
||||||
|
the end of the receiver.
|
||||||
|
|
||||||
|
@discussion The elements in the newly created expression are copied to the
|
||||||
|
new expression. The location is specified in the length reference
|
||||||
|
frame.
|
||||||
|
|
||||||
|
If the given location exceeds the receiver's bounds a @c
|
||||||
|
NSRangeException is raised.
|
||||||
|
|
||||||
|
@param from
|
||||||
|
The first location to be included in the new expression.
|
||||||
|
|
||||||
|
@return A new expression.
|
||||||
|
*/
|
||||||
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from;
|
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method subexpressionToLocation:
|
||||||
|
@brief Creates a new expression from the beginning to the specified
|
||||||
|
index (exclusive).
|
||||||
|
|
||||||
|
@discussion The elements in the newly created expression are copied to the
|
||||||
|
new expression. The location is specified in the length reference
|
||||||
|
frame.
|
||||||
|
|
||||||
|
If the given location exceeds the receiver's bounds a @c
|
||||||
|
NSRangeException is raised.
|
||||||
|
|
||||||
|
|
||||||
|
@param to
|
||||||
|
The first location not to be included in the new expression or
|
||||||
|
the length of the new expression.
|
||||||
|
|
||||||
|
@return A new expression.
|
||||||
|
*/
|
||||||
- (MPExpression *)subexpressionToLocation:(NSUInteger)to;
|
- (MPExpression *)subexpressionToLocation:(NSUInteger)to;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method subexpressionWithRange:
|
||||||
|
@brief Creates a new expression with the symbols in the specified range.
|
||||||
|
|
||||||
|
@discussion The elements in the newly created expression are copied to the
|
||||||
|
new exoression. The range is specified in the length reference
|
||||||
|
frame.
|
||||||
|
|
||||||
|
If the given range exceeds the receiver's bounds a @c
|
||||||
|
NSRangeException is raised.
|
||||||
|
|
||||||
|
@param range
|
||||||
|
The range from which to create the new expression.
|
||||||
|
|
||||||
|
@return A new expression.
|
||||||
|
*/
|
||||||
- (MPExpression *)subexpressionWithRange:(NSRange)range;
|
- (MPExpression *)subexpressionWithRange:(NSRange)range;
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Mutating Expressions
|
#pragma mark Mutating Expressions
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method appendElement:
|
||||||
|
@brief Appends @c anElement to the receiver.
|
||||||
|
|
||||||
|
@param anElement
|
||||||
|
The element to append to the receiver.
|
||||||
|
*/
|
||||||
- (void)appendElement:(id<MPExpressionElement>)anElement;
|
- (void)appendElement:(id<MPExpressionElement>)anElement;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@method appendElements:
|
||||||
|
@brief Appends the objects from @c elements to the receiver.
|
||||||
|
|
||||||
|
@param elements
|
||||||
|
The elements to append to the receiver.
|
||||||
|
*/
|
||||||
- (void)appendElements:(NSArray *)elements;
|
- (void)appendElements:(NSArray *)elements;
|
||||||
|
|
||||||
- (void)insertElement:(id<MPExpressionElement>)anElement atLocation:(NSUInteger)index;
|
- (void)insertElement:(id<MPExpressionElement>)anElement atLocation:(NSUInteger)index;
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
|
|
||||||
@interface MPExpression (MPExpressionPrivate)
|
@interface MPExpression (MPExpressionPrivate)
|
||||||
|
|
||||||
|
- (NSUInteger)lengthOfElements:(NSArray *)elements;
|
||||||
|
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location;
|
||||||
|
|
||||||
- (void)validateElements:(NSArray *)elements;
|
- (void)validateElements:(NSArray *)elements;
|
||||||
- (BOOL)splitElementsAtLocation:(NSUInteger)location
|
- (BOOL)splitElementsAtLocation:(NSUInteger)location
|
||||||
insertionIndex:(out NSUInteger *)insertionIndex;
|
insertionIndex:(out NSUInteger *)insertionIndex;
|
||||||
@@ -29,6 +32,22 @@
|
|||||||
|
|
||||||
@implementation MPExpression (MPExpressionPrivate)
|
@implementation MPExpression (MPExpressionPrivate)
|
||||||
|
|
||||||
|
- (NSUInteger)lengthOfElements:(NSArray *)elements
|
||||||
|
{
|
||||||
|
NSUInteger length = 0;
|
||||||
|
for (id<MPExpressionElement> element in elements) {
|
||||||
|
length += element.length;
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)indexOfElementAtLocation:(NSUInteger)location
|
||||||
|
{
|
||||||
|
NSUInteger index = 0;
|
||||||
|
[self calculateSplitOffsetForSplitLocation:location inElementAtIndex:&index];
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)validateElements:(NSArray *)elements
|
- (void)validateElements:(NSArray *)elements
|
||||||
{
|
{
|
||||||
for (id element in elements) {
|
for (id element in elements) {
|
||||||
@@ -89,8 +108,9 @@
|
|||||||
|
|
||||||
@implementation MPExpression {
|
@implementation MPExpression {
|
||||||
NSUInteger _cachedLength;
|
NSUInteger _cachedLength;
|
||||||
NSRange editedRange;
|
NSRange _editedRange;
|
||||||
NSRange replacementRange;
|
BOOL _didSplitEndOnEditing;
|
||||||
|
NSUInteger _replacementLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize elements = _elements;
|
@synthesize elements = _elements;
|
||||||
@@ -111,7 +131,6 @@
|
|||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
_cachedLength = 0;
|
_cachedLength = 0;
|
||||||
_elements = [[NSMutableArray alloc] initWithCapacity:elements.count];
|
|
||||||
_elements = [[NSMutableArray alloc] initWithArray:elements
|
_elements = [[NSMutableArray alloc] initWithArray:elements
|
||||||
copyItems:YES];
|
copyItems:YES];
|
||||||
[self fixElements];
|
[self fixElements];
|
||||||
@@ -123,29 +142,29 @@
|
|||||||
- (void)fixElements
|
- (void)fixElements
|
||||||
{
|
{
|
||||||
for (NSUInteger index = 0; index < self.elements.count; index++) {
|
for (NSUInteger index = 0; index < self.elements.count; index++) {
|
||||||
id<MPExpressionElement> next = index+1 < self.elements.count ? self.elements[index+1] :nil;
|
id<MPExpressionElement> next = index+1 < self.elements.count ? self.elements[index+1] : nil;
|
||||||
id<MPExpressionElement> current = self.elements[index];
|
id<MPExpressionElement> current = self.elements[index];
|
||||||
if ([current isString]) {
|
if ([current isString]) {
|
||||||
if (current.length == 0) {
|
if (current.length == 0) {
|
||||||
[self.elements removeObjectAtIndex:index];
|
[self.elements removeObjectAtIndex:index];
|
||||||
if (index < replacementRange.location) {
|
if (index >= _editedRange.location && index < NSMaxRange(_editedRange)) {
|
||||||
replacementRange.location--;
|
--_replacementLength;
|
||||||
} else if (index < NSMaxRange(replacementRange)-1) {
|
|
||||||
replacementRange.length--;
|
|
||||||
} else if (index == NSMaxRange(replacementRange)) {
|
|
||||||
editedRange.length++;
|
|
||||||
}
|
}
|
||||||
--index;
|
--index;
|
||||||
} else if ([next isString]) {
|
} else if ([next isString]) {
|
||||||
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
|
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
|
||||||
[self.elements replaceObjectsInRange:NSMakeRange(index, 2)
|
[self.elements replaceObjectsInRange:NSMakeRange(index, 2)
|
||||||
withObjectsFromArray:@[new]];
|
withObjectsFromArray:@[new]];
|
||||||
if (index < replacementRange.location) {
|
NSUInteger maxReplacementIndex = _editedRange.location + _replacementLength;
|
||||||
replacementRange.location--;
|
if (index == _editedRange.location - 1) {
|
||||||
} else if (index < NSMaxRange(replacementRange)-1) {
|
--_editedRange.location;
|
||||||
replacementRange.length--;
|
++_editedRange.length;
|
||||||
} else if (index == NSMaxRange(replacementRange)-1) {
|
} else if (index >= _editedRange.location && index < maxReplacementIndex - 1) {
|
||||||
editedRange.length++;
|
--_replacementLength;
|
||||||
|
} else if (index == maxReplacementIndex - 1) {
|
||||||
|
if (!_didSplitEndOnEditing) {
|
||||||
|
++_editedRange.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
--index;
|
--index;
|
||||||
}
|
}
|
||||||
@@ -159,9 +178,7 @@
|
|||||||
- (NSUInteger)length
|
- (NSUInteger)length
|
||||||
{
|
{
|
||||||
if (_cachedLength == 0) {
|
if (_cachedLength == 0) {
|
||||||
for (id<MPExpressionElement> element in self.elements) {
|
_cachedLength = [self lengthOfElements:self.elements];
|
||||||
_cachedLength += element.length;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return _cachedLength;
|
return _cachedLength;
|
||||||
}
|
}
|
||||||
@@ -171,9 +188,20 @@
|
|||||||
return self.elements.count;
|
return self.elements.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)index
|
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
|
||||||
{
|
{
|
||||||
return self.elements[index];
|
[self replaceSymbolsInRange:NSMakeRange(idx, 1)
|
||||||
|
withElements:@[obj]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)objectAtIndexedSubscript:(NSUInteger)idx
|
||||||
|
{
|
||||||
|
return [self elementAtIndex:idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
|
||||||
|
{
|
||||||
|
return self.elements[anIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)elementsInRange:(NSRange)range
|
- (NSArray *)elementsInRange:(NSRange)range
|
||||||
@@ -181,12 +209,13 @@
|
|||||||
return [self.elements subarrayWithRange:range];
|
return [self.elements subarrayWithRange:range];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#warning If multiple equal expressions exist errors may occur...
|
||||||
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
|
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
|
||||||
{
|
{
|
||||||
return [self.elements indexOfObject:element];
|
return [self.elements indexOfObject:element];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)replaceElementsInRange:(NSRange)range
|
- (void)replaceSymbolsInRange:(NSRange)range
|
||||||
withElements:(NSArray *)elements
|
withElements:(NSArray *)elements
|
||||||
{
|
{
|
||||||
if (NSMaxRange(range) > self.length) {
|
if (NSMaxRange(range) > self.length) {
|
||||||
@@ -197,7 +226,7 @@
|
|||||||
[self validateElements:elements];
|
[self validateElements:elements];
|
||||||
|
|
||||||
// Locate the position, split the elements
|
// Locate the position, split the elements
|
||||||
NSUInteger startIndex;
|
NSUInteger startIndex; // startIndex is inclusive
|
||||||
BOOL didSplitStart = NO;
|
BOOL didSplitStart = NO;
|
||||||
if ([self numberOfElements] == 0) {
|
if ([self numberOfElements] == 0) {
|
||||||
startIndex = 0;
|
startIndex = 0;
|
||||||
@@ -205,27 +234,30 @@
|
|||||||
didSplitStart = [self splitElementsAtLocation:range.location
|
didSplitStart = [self splitElementsAtLocation:range.location
|
||||||
insertionIndex:&startIndex];
|
insertionIndex:&startIndex];
|
||||||
}
|
}
|
||||||
NSUInteger endIndex;
|
NSUInteger endIndex; // endIndex is exclusive
|
||||||
BOOL didSplitEnd = [self splitElementsAtLocation:NSMaxRange(range)
|
BOOL didSplitEnd = [self splitElementsAtLocation:NSMaxRange(range)
|
||||||
insertionIndex:&endIndex];
|
insertionIndex:&endIndex];
|
||||||
|
|
||||||
// Perform the replacement
|
// Perform the replacement
|
||||||
NSArray *newElements = [[NSArray alloc] initWithArray:elements
|
NSMutableArray *newElements = [[NSMutableArray alloc] initWithArray:elements
|
||||||
copyItems:YES];
|
copyItems:YES];
|
||||||
[self.elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
|
[self.elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
|
||||||
withObjectsFromArray:newElements];
|
withObjectsFromArray:newElements];
|
||||||
|
|
||||||
|
|
||||||
_cachedLength = 0;
|
_cachedLength = 0;
|
||||||
NSUInteger editingStart = startIndex - (didSplitStart?1:0);
|
|
||||||
NSUInteger editingLength = endIndex - startIndex + (didSplitStart?1:0) + (didSplitEnd?1:0);
|
NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0);
|
||||||
editedRange = NSMakeRange(editingStart, editingLength);
|
NSUInteger editLength = range.length > 0 ? (endIndex - startIndex) : 0;
|
||||||
replacementRange = NSMakeRange(startIndex, elements.count);
|
_editedRange = NSMakeRange(editLocation, editLength);
|
||||||
|
_didSplitEndOnEditing = didSplitEnd;
|
||||||
|
_replacementLength = elements.count + (didSplitStart ? 1 : 0) + (didSplitEnd ? 1 : 0);
|
||||||
|
|
||||||
[self fixElements];
|
[self fixElements];
|
||||||
MPRangePath *changePath = [[MPRangePath alloc] initWithRange:editedRange];
|
[self didChangeElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange]
|
||||||
[self didChangeElementsInRangePath:changePath replacementLength:replacementRange.length];
|
replacementLength:_replacementLength];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (double)doubleValue
|
- (double)doubleValue
|
||||||
{
|
{
|
||||||
#warning Unimplemented Method
|
#warning Unimplemented Method
|
||||||
@@ -266,6 +298,7 @@
|
|||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description
|
||||||
{
|
{
|
||||||
|
#warning Bad Implementation
|
||||||
NSMutableString *description = [[NSMutableString alloc] init];
|
NSMutableString *description = [[NSMutableString alloc] init];
|
||||||
NSUInteger index = 0;
|
NSUInteger index = 0;
|
||||||
for (id element in self.elements) {
|
for (id element in self.elements) {
|
||||||
@@ -347,6 +380,16 @@
|
|||||||
return [targetExpression elementsInRange:rangePath.rangeAtLastIndex];
|
return [targetExpression elementsInRange:rangePath.rangeAtLastIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
if (self.parent) {
|
||||||
|
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
||||||
|
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
|
||||||
|
} else {
|
||||||
|
return [[NSIndexPath alloc] init];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark Working With Expressions
|
#pragma mark Working With Expressions
|
||||||
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from
|
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from
|
||||||
{
|
{
|
||||||
@@ -377,7 +420,7 @@
|
|||||||
|
|
||||||
- (void)appendElements:(NSArray *)elements
|
- (void)appendElements:(NSArray *)elements
|
||||||
{
|
{
|
||||||
[self replaceElementsInRange:NSMakeRange(self.length, 0) withElements:elements];
|
[self replaceSymbolsInRange:NSMakeRange(self.length, 0) withElements:elements];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)insertElement:(id<MPExpressionElement>)anElement atLocation:(NSUInteger)index
|
- (void)insertElement:(id<MPExpressionElement>)anElement atLocation:(NSUInteger)index
|
||||||
@@ -387,12 +430,12 @@
|
|||||||
|
|
||||||
- (void)insertElements:(NSArray *)elements atLocation:(NSUInteger)index
|
- (void)insertElements:(NSArray *)elements atLocation:(NSUInteger)index
|
||||||
{
|
{
|
||||||
[self replaceElementsInRange:NSMakeRange(index, 0) withElements:elements];
|
[self replaceSymbolsInRange:NSMakeRange(index, 0) withElements:elements];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)deleteElementsInRange:(NSRange)range
|
- (void)deleteElementsInRange:(NSRange)range
|
||||||
{
|
{
|
||||||
[self replaceElementsInRange:range withElements:@[]];
|
[self replaceSymbolsInRange:range withElements:@[]];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark Evaluating Expressions
|
#pragma mark Evaluating Expressions
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
@import Foundation;
|
@import Foundation;
|
||||||
|
|
||||||
@protocol MPExpressionElement <NSObject, NSCoding>
|
@protocol MPExpressionElement <NSObject, NSCopying, NSCoding>
|
||||||
|
|
||||||
- (BOOL)isString;
|
- (BOOL)isString;
|
||||||
- (BOOL)isFunction;
|
- (BOOL)isFunction;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#pragma mark Working With the Expression Tree
|
#pragma mark Working With the Expression Tree
|
||||||
@property (nonatomic, weak) MPExpression *parent; // Documentation: Do not set
|
@property (nonatomic, weak) MPExpression *parent; // Documentation: Do not set
|
||||||
|
- (NSIndexPath *)indexPath;
|
||||||
|
|
||||||
- (NSUInteger)numberOfChildren; // Override
|
- (NSUInteger)numberOfChildren; // Override
|
||||||
- (MPExpression *)childAtIndex:(NSUInteger)index; // Override
|
- (MPExpression *)childAtIndex:(NSUInteger)index; // Override
|
||||||
|
|||||||
@@ -24,6 +24,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark Working With the Expression Tree
|
#pragma mark Working With the Expression Tree
|
||||||
|
- (NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
NSUInteger selfIndex = [self.parent indexOfElement:self];
|
||||||
|
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSUInteger)numberOfChildren
|
- (NSUInteger)numberOfChildren
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
#pragma mark Evaluating Functions
|
#pragma mark Evaluating Functions
|
||||||
- (double)doubleValue
|
- (double)doubleValue
|
||||||
{
|
{
|
||||||
#warning Implementation
|
#warning Unimplemented Method
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -218,15 +218,20 @@
|
|||||||
XCTAssertEqual([testExpression numberOfElements], 3);
|
XCTAssertEqual([testExpression numberOfElements], 3);
|
||||||
// 12678 [] 90
|
// 12678 [] 90
|
||||||
|
|
||||||
|
[testExpression deleteElementsInRange:NSMakeRange(0, 2)];
|
||||||
|
XCTAssertEqual([testExpression numberOfElements], 3);
|
||||||
|
XCTAssertEqualObjects([testExpression elementAtIndex:0], @"678");
|
||||||
|
|
||||||
[testExpression insertElement:[[MPFunction alloc] init]
|
[testExpression insertElement:[[MPFunction alloc] init]
|
||||||
atLocation:2];
|
atLocation:2];
|
||||||
XCTAssertEqual([testExpression numberOfElements], 5);
|
XCTAssertEqual([testExpression numberOfElements], 5);
|
||||||
// 12 [] 678 [] 90
|
// 67 [] 8 [] 90
|
||||||
|
|
||||||
[testExpression replaceElementsInRange:NSMakeRange(2, 5)
|
[testExpression replaceSymbolsInRange:NSMakeRange(2, 5)
|
||||||
withElements:@[[[MPFunction alloc] init]]];
|
withElements:@[[[MPFunction alloc] init]]];
|
||||||
XCTAssertEqual([testExpression numberOfElements], 3);
|
XCTAssertEqual([testExpression numberOfElements], 2);
|
||||||
// 12 [] 90
|
XCTAssertEqualObjects([testExpression elementAtIndex:0], @"67");
|
||||||
|
// 67 []
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testInvalidMutatingRange {
|
- (void)testInvalidMutatingRange {
|
||||||
|
|||||||
Reference in New Issue
Block a user