Reorganized File Structure
Added Documentation
This commit is contained in:
816
MathKit/MPExpression.m
Normal file
816
MathKit/MPExpression.m
Normal file
@@ -0,0 +1,816 @@
|
||||
//
|
||||
// MPExpression.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 10.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpression.h"
|
||||
|
||||
#import "MPExpressionElement.h"
|
||||
#import "MPFunction.h"
|
||||
|
||||
#import "MPRangePath.h"
|
||||
|
||||
#import "MPExpressionTokenizer.h"
|
||||
#import "MPToken.h"
|
||||
#import "MPExpressionParser.h"
|
||||
#import "MPParsedExpression.h"
|
||||
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
|
||||
|
||||
|
||||
NSString *const MPIllegalElementException = @"Illegal Element Exception";
|
||||
NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey";
|
||||
|
||||
|
||||
@interface MPExpression () {
|
||||
NSMutableArray * _elements;
|
||||
}
|
||||
|
||||
/*!
|
||||
@method tokens
|
||||
@brief Private method. Returns an array containing all tokens from the
|
||||
receiver.
|
||||
|
||||
@return All items in the <code>MPTokenReferenceFrame</code>.
|
||||
*/
|
||||
- (NSArray *)tokens;
|
||||
|
||||
|
||||
/*!
|
||||
@method validateElements:
|
||||
@brief Private method. Checks whether all objects in the specified array
|
||||
are valid expression elements.
|
||||
|
||||
@discussion If an object is not valid a
|
||||
<code>MPIllegalElementException</code> is raised.
|
||||
|
||||
@param elements
|
||||
The array of objects to be validated.
|
||||
*/
|
||||
- (void)validateElements:(NSArray *)elements;
|
||||
|
||||
|
||||
/*!
|
||||
@method fixElements
|
||||
@brief Private method. Restores consistency in the receiver after a
|
||||
change was made.
|
||||
*/
|
||||
- (void)fixElements;
|
||||
|
||||
|
||||
/*!
|
||||
@method _replaceSymbolsInRange:withElements:
|
||||
@brief Private method. Replaces the symbols in the specified range with
|
||||
the elements from the <code>elements</code> array.
|
||||
|
||||
@discussion This is the most primitive mutation method of the @c MPExpression
|
||||
class.
|
||||
|
||||
@param range
|
||||
The range of symbols to be replaced. The range is specified in
|
||||
the symbol reference frame. If the range exceeds the receiver's
|
||||
bounds a <code>NSRangeException</code> is raised.
|
||||
|
||||
@param elements
|
||||
The elements that should replace the symbols in the specified
|
||||
range.
|
||||
*/
|
||||
- (void)_replaceSymbolsInRange:(NSRange)range
|
||||
withElements:(NSArray *)elements;
|
||||
|
||||
|
||||
/*!
|
||||
@method _splitElementsAtLocation:insertionIndex:
|
||||
@brief Splits the receiver's elements at the specified
|
||||
<code>location</code>.
|
||||
|
||||
@discussion The split location can be inside a string element. In that case
|
||||
the string is replaced with two other smaller strings.
|
||||
|
||||
Splitting the elements destroys the receiver's integrity. The
|
||||
receiver of this message must also receive a
|
||||
<code>-fixElements</code> message soon afterwards.
|
||||
|
||||
@param location
|
||||
The index where to split the elements. Specified in the symbol
|
||||
reference frame.
|
||||
|
||||
@param insertionIndex
|
||||
Splitting elements may shift the indexes of the following
|
||||
elements. This parameter is set to the index (specified in the
|
||||
element reference frame) that should be used to insert a new
|
||||
element at the location where the elements were previously
|
||||
splitted.
|
||||
|
||||
@return @c YES if a string element was split into two smaller strings,
|
||||
<code>NO</code> if the split <code>location</code> corresponds to
|
||||
a location between two elements.
|
||||
*/
|
||||
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
|
||||
insertionIndex:(NSUInteger *)insertionIndex;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation MPExpression {
|
||||
NSArray *_tokenCache;
|
||||
|
||||
NSRange _editedRange;
|
||||
BOOL _didSplitStartOnEditing;
|
||||
BOOL _didSplitEndOnEditing;
|
||||
NSUInteger _replacementLength;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Creation Methods
|
||||
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithElements:@[]];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithElement:(id<MPExpressionElement>)element
|
||||
{
|
||||
return [self initWithElements:@[element]];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithElements:(NSArray *)elements
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self validateElements:elements];
|
||||
_elements = [[NSMutableArray alloc] initWithArray:elements
|
||||
copyItems:YES];
|
||||
[self fixElements];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Private Methods
|
||||
|
||||
|
||||
- (NSArray *)tokens
|
||||
{
|
||||
if (!_tokenCache) {
|
||||
_tokenCache = [MPExpressionTokenizer tokenizeExpression:self];
|
||||
}
|
||||
return _tokenCache;
|
||||
}
|
||||
|
||||
|
||||
- (void)validateElements:(NSArray *)elements
|
||||
{
|
||||
for (id element in elements) {
|
||||
if (![element conformsToProtocol:@protocol(MPExpressionElement)]) {
|
||||
@throw [NSException exceptionWithName:MPIllegalElementException
|
||||
reason:@"Elements must conform to the MPExpressionElement protocol."
|
||||
userInfo:@{MPIllegalElementExceptionElementKey: element}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)fixElements
|
||||
{
|
||||
for (NSUInteger index = 0; index < _elements.count; index++) {
|
||||
id<MPExpressionElement> next = index+1 < _elements.count ? _elements[index+1] : nil;
|
||||
id<MPExpressionElement> current = _elements[index];
|
||||
if ([current isString]) {
|
||||
if (current.length == 0) {
|
||||
[_elements removeObjectAtIndex:index];
|
||||
if (index >= _editedRange.location && index < NSMaxRange(_editedRange)) {
|
||||
--_replacementLength;
|
||||
}
|
||||
--index;
|
||||
} else if ([next isString]) {
|
||||
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
|
||||
[_elements replaceObjectsInRange:NSMakeRange(index, 2)
|
||||
withObjectsFromArray:@[new]];
|
||||
NSUInteger maxReplacementIndex = _editedRange.location + _replacementLength;
|
||||
if (index == _editedRange.location-1 && !_didSplitStartOnEditing) {
|
||||
--_editedRange.location;
|
||||
++_editedRange.length;
|
||||
if (index == maxReplacementIndex-1 && !_didSplitEndOnEditing) {
|
||||
++_editedRange.length;
|
||||
++_replacementLength;
|
||||
}
|
||||
} else if (index >= _editedRange.location && index < maxReplacementIndex - 1) {
|
||||
--_replacementLength;
|
||||
} else if (index == maxReplacementIndex-1 && !_didSplitEndOnEditing) {
|
||||
++_editedRange.length;
|
||||
}
|
||||
--index;
|
||||
}
|
||||
} else {
|
||||
[(MPFunction *)current setParent:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Querying Expressions
|
||||
|
||||
|
||||
- (MPExpression *)rootExpression
|
||||
{
|
||||
if (self.parent == nil) {
|
||||
return self;
|
||||
}
|
||||
return [self.parent rootExpression];
|
||||
}
|
||||
|
||||
|
||||
- (NSIndexPath *)indexPath
|
||||
{
|
||||
if (self.parent) {
|
||||
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
||||
return [[self.parent indexPath] indexPathByAddingIndex:selfIndex];
|
||||
} else {
|
||||
return [[NSIndexPath alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)countItemsInReferenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
switch (referenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
return _elements.count;
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
for (id<MPExpressionElement> element in _elements) {
|
||||
count += element.length;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
return self.tokens.count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (id)itemAtIndex:(NSUInteger)anIndex
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
switch (referenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
return _elements[anIndex];
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
{
|
||||
NSUInteger offsetInElement;
|
||||
NSUInteger elementIndex = [self convertIndex:anIndex
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
offset:&offsetInElement];
|
||||
id<MPExpressionElement> element = [self elementAtIndex:elementIndex];
|
||||
if ([element isFunction]) {
|
||||
return element;
|
||||
} else {
|
||||
return [((NSString *)element) substringWithRange:NSMakeRange(offsetInElement, 1)];
|
||||
}
|
||||
}
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
return self.tokens[anIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)anIndex
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
NSUInteger elementIndex = [self convertIndex:anIndex
|
||||
fromReferenceFrame:referenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame];
|
||||
return _elements[elementIndex];
|
||||
}
|
||||
|
||||
|
||||
#warning If multiple equal expressions exist errors may occur...
|
||||
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
|
||||
{
|
||||
return [_elements indexOfObject:element];
|
||||
}
|
||||
|
||||
|
||||
- (NSArray *)itemsInRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
MPExpression *subexpression = [self subexpressionWithRange:aRange
|
||||
referenceFrame:referenceFrame];
|
||||
return [subexpression allItemsInReferenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (NSArray *)allItemsInReferenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
switch (referenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
return _elements;
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
{
|
||||
NSMutableArray *symbols = [[NSMutableArray alloc] init];
|
||||
for (id<MPExpressionElement> element in _elements) {
|
||||
if ([element isString]) {
|
||||
for (NSUInteger i = 0; i < [element length]; i++) {
|
||||
NSString *ichar = [NSString stringWithFormat:@"%c", [((NSString *)element) characterAtIndex:i]];
|
||||
[symbols addObject:ichar];
|
||||
}
|
||||
} else {
|
||||
[symbols addObject:element];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
return self.tokens;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.length == 0) {
|
||||
return self;
|
||||
}
|
||||
id<MPExpressionElement> element = _elements[[indexPath indexAtPosition:0]];
|
||||
if (indexPath.length == 1) {
|
||||
return element;
|
||||
}
|
||||
if ([element isFunction]) {
|
||||
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
|
||||
}
|
||||
// TODO: Raise appropriate exeption.
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)convertIndex:(NSUInteger)anIndex
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
{
|
||||
return [self convertIndex:anIndex
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
offset:NULL];
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)convertIndex:(NSUInteger)anIndex
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
offset:(NSUInteger *)offset
|
||||
{
|
||||
if (fromReferenceFrame == toReferenceFrame || anIndex == 0) {
|
||||
if (offset) {
|
||||
*offset = 0;
|
||||
}
|
||||
return anIndex;
|
||||
}
|
||||
|
||||
NSUInteger symbolIndex __block = 0;
|
||||
switch (fromReferenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
[_elements enumerateObjectsUsingBlock:^(id<MPExpressionElement> obj, NSUInteger idx, BOOL *stop) {
|
||||
symbolIndex += obj.length;
|
||||
*stop = idx >= anIndex - 1;
|
||||
}];
|
||||
break;
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
symbolIndex = anIndex;
|
||||
break;
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
[self.tokens enumerateObjectsUsingBlock:^(id<MPToken> obj, NSUInteger idx, BOOL *stop) {
|
||||
symbolIndex += obj.range.length;
|
||||
*stop = idx >= anIndex - 1;
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
switch (toReferenceFrame) {
|
||||
case MPElementReferenceFrame:
|
||||
{
|
||||
NSUInteger totalLength = 0;
|
||||
NSUInteger elementIndex = 0;
|
||||
id<MPExpressionElement> element;
|
||||
while (totalLength < symbolIndex) {
|
||||
element = _elements[elementIndex++];
|
||||
totalLength += element.length;
|
||||
}
|
||||
--elementIndex;
|
||||
NSUInteger offsetInElement = element.length - totalLength + symbolIndex;
|
||||
if (offsetInElement == element.length) {
|
||||
offsetInElement = 0;
|
||||
elementIndex++;
|
||||
}
|
||||
if (offset) {
|
||||
*offset = offsetInElement;
|
||||
}
|
||||
return elementIndex;
|
||||
}
|
||||
|
||||
case MPSymbolReferenceFrame:
|
||||
if (offset) {
|
||||
*offset = 0;
|
||||
}
|
||||
return symbolIndex;
|
||||
|
||||
case MPTokenReferenceFrame:
|
||||
{
|
||||
NSUInteger totalLength = 0;
|
||||
NSUInteger tokenIndex = 0;
|
||||
id<MPToken> token;
|
||||
while (totalLength < symbolIndex) {
|
||||
token = self.tokens[tokenIndex++];
|
||||
totalLength += token.range.length;
|
||||
}
|
||||
--tokenIndex;
|
||||
NSUInteger offsetInToken = token.range.length - totalLength + symbolIndex;
|
||||
if (offsetInToken == token.range.length) {
|
||||
offsetInToken = 0;
|
||||
tokenIndex++;
|
||||
}
|
||||
if (offset) {
|
||||
*offset = offsetInToken;
|
||||
}
|
||||
return tokenIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (NSRange)convertRange:(NSRange)aRange
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
{
|
||||
return [self convertRange:aRange
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
leadingOffset:NULL
|
||||
trailingOffset:NULL];
|
||||
}
|
||||
|
||||
|
||||
- (NSRange)convertRange:(NSRange)aRange
|
||||
fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame
|
||||
toReferenceFrame:(MPReferenceFrame)toReferenceFrame
|
||||
leadingOffset:(NSUInteger *)leadingOffset
|
||||
trailingOffset:(NSUInteger *)trailingOffset
|
||||
{
|
||||
NSUInteger start = [self convertIndex:aRange.location
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
offset:leadingOffset];
|
||||
NSUInteger end = [self convertIndex:NSMaxRange(aRange)
|
||||
fromReferenceFrame:fromReferenceFrame
|
||||
toReferenceFrame:toReferenceFrame
|
||||
offset:trailingOffset];
|
||||
return NSMakeRange(start, end - start);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Mutating Expressions
|
||||
|
||||
|
||||
- (void)replaceItemsInRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
withElements:(NSArray *)elements
|
||||
{
|
||||
NSUInteger start = [self convertIndex:aRange.location
|
||||
fromReferenceFrame:referenceFrame
|
||||
toReferenceFrame:MPSymbolReferenceFrame];
|
||||
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
|
||||
{
|
||||
if (NSMaxRange(range) > [self countItemsInReferenceFrame:MPSymbolReferenceFrame]) {
|
||||
@throw [NSException exceptionWithName:NSRangeException
|
||||
reason:@"Range out of bounds of expression"
|
||||
userInfo:nil];
|
||||
}
|
||||
[self validateElements:elements];
|
||||
|
||||
// Locate the position, split the elements
|
||||
NSUInteger startIndex; // startIndex is inclusive
|
||||
BOOL didSplitStart = NO;
|
||||
if (_elements.count == 0) {
|
||||
startIndex = 0;
|
||||
} else {
|
||||
didSplitStart = [self _splitElementsAtLocation:range.location
|
||||
insertionIndex:&startIndex];
|
||||
}
|
||||
NSUInteger endIndex; // endIndex is exclusive
|
||||
BOOL didSplitEnd = [self _splitElementsAtLocation:NSMaxRange(range)
|
||||
insertionIndex:&endIndex];
|
||||
|
||||
// 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;
|
||||
|
||||
NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0);
|
||||
NSUInteger editLength = endIndex - startIndex;
|
||||
if (range.length == 0 && didSplitStart) {
|
||||
editLength = 1;
|
||||
}
|
||||
|
||||
_editedRange = NSMakeRange(editLocation, editLength);
|
||||
_didSplitStartOnEditing = didSplitStart;
|
||||
_didSplitEndOnEditing = didSplitEnd;
|
||||
_replacementLength = elements.count;
|
||||
if (didSplitStart) {
|
||||
_replacementLength++;
|
||||
if (range.length == 0) {
|
||||
_replacementLength++;
|
||||
}
|
||||
}
|
||||
if (didSplitEnd) {
|
||||
_replacementLength++;
|
||||
}
|
||||
|
||||
[self fixElements];
|
||||
[self changedElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange]
|
||||
replacementLength:_replacementLength];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)_splitElementsAtLocation:(NSUInteger)location
|
||||
insertionIndex:(NSUInteger *)insertionIndex
|
||||
{
|
||||
if (location == 0) {
|
||||
*insertionIndex = 0;
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSUInteger splitOffset;
|
||||
NSUInteger splitElementIndex = [self convertIndex:location
|
||||
fromReferenceFrame:MPSymbolReferenceFrame
|
||||
toReferenceFrame:MPElementReferenceFrame
|
||||
offset:&splitOffset];
|
||||
if (splitOffset != 0) {
|
||||
NSString *splitElement = (NSString *)_elements[splitElementIndex];
|
||||
NSString *leftPart = [splitElement substringToIndex:splitOffset];
|
||||
NSString *rightPart = [splitElement substringFromIndex:splitOffset];
|
||||
[_elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1)
|
||||
withObjectsFromArray:@[leftPart, rightPart]];
|
||||
++splitElementIndex;
|
||||
}
|
||||
*insertionIndex = splitElementIndex;
|
||||
return splitOffset != 0;
|
||||
}
|
||||
|
||||
|
||||
- (void)changedElementsInRangePath:(MPRangePath *)rangePath
|
||||
replacementLength:(NSUInteger)replacementLength
|
||||
{
|
||||
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
||||
MPRangePath *newPath = MPMakeRangePath([rangePath.location indexPathByPreceedingIndex:selfIndex], rangePath.length);
|
||||
[self.parent didChangeElementsInRangePath:newPath
|
||||
replacementLength:replacementLength];
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(from, [self countItemsInReferenceFrame:referenceFrame] - from)
|
||||
referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(0, to)
|
||||
referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (MPExpression *)subexpressionWithRange:(NSRange)aRange
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
MPExpression *subexpression = [self copy];
|
||||
NSRange preceedingRange = NSMakeRange(0, aRange.location);
|
||||
NSUInteger firstOut = NSMaxRange(aRange);
|
||||
NSRange exceedingRange = NSMakeRange(firstOut, [self countItemsInReferenceFrame:referenceFrame] - firstOut);
|
||||
[subexpression deleteElementsInRange:exceedingRange
|
||||
referenceFrame:referenceFrame];
|
||||
[subexpression deleteElementsInRange:preceedingRange
|
||||
referenceFrame:referenceFrame];
|
||||
return subexpression;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Evaluating Expressions
|
||||
|
||||
|
||||
- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
return [self parseExpectingVariable:NO
|
||||
errors:errors];
|
||||
}
|
||||
|
||||
|
||||
- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag
|
||||
errors:(NSArray *__autoreleasing *)errors
|
||||
{
|
||||
return [[[MPExpressionParser alloc] initWithExpression:self] parseExpectingVariableDefinition:flag
|
||||
errors:errors];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Basic NSObject Methods
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
#warning Bad Implementation
|
||||
NSMutableString *description = [[NSMutableString alloc] init];
|
||||
NSUInteger index = 0;
|
||||
for (id element in _elements) {
|
||||
if ([element isString]) {
|
||||
NSMutableString *correctedSymbol = [[element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
|
||||
// Prefix operator
|
||||
if (element != _elements[0]) {
|
||||
unichar prefix = [correctedSymbol characterAtIndex:0];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
|
||||
[correctedSymbol insertString:@"*"
|
||||
atIndex:0];
|
||||
}
|
||||
}
|
||||
// Suffix operator
|
||||
if (element != [_elements lastObject]) {
|
||||
unichar suffix = [correctedSymbol characterAtIndex:correctedSymbol.length-1];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:suffix]) {
|
||||
[correctedSymbol appendString:@"*"];
|
||||
}
|
||||
}
|
||||
[description appendString:correctedSymbol];
|
||||
} else if (index > 0 && [_elements[index-1] isKindOfClass:[MPFunction class]]) {
|
||||
[description appendFormat:@"*%@", [element description]];
|
||||
} else {
|
||||
[description appendString:[element description]];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [_elements hash];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:_elements];
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
// TODO: Test Coding
|
||||
return [self initWithElements:[aDecoder decodeObject]];
|
||||
}
|
||||
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_elements];
|
||||
}
|
||||
|
||||
@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)anIndex
|
||||
{
|
||||
return [self itemAtIndex:anIndex
|
||||
referenceFrame:MPElementReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (id<MPExpressionElement>)symbolAtIndex:(NSUInteger)anIndex
|
||||
{
|
||||
return [self itemAtIndex:anIndex
|
||||
referenceFrame:MPSymbolReferenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (id<MPToken>)tokenAtIndex:(NSUInteger)anIndex
|
||||
{
|
||||
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)
|
||||
referenceFrame:MPSymbolReferenceFrame
|
||||
withElements:elements];
|
||||
}
|
||||
|
||||
|
||||
- (void)insertElement:(id<MPExpressionElement>)anElement
|
||||
atIndex:(NSUInteger)index
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
[self insertElements:@[anElement]
|
||||
atIndex:index
|
||||
referenceFrame:referenceFrame];
|
||||
}
|
||||
|
||||
|
||||
- (void)insertElements:(NSArray *)elements
|
||||
atIndex:(NSUInteger)index
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
[self replaceItemsInRange:NSMakeRange(index, 0)
|
||||
referenceFrame:referenceFrame
|
||||
withElements:elements];
|
||||
}
|
||||
|
||||
|
||||
- (void)deleteElementsInRange:(NSRange)range
|
||||
referenceFrame:(MPReferenceFrame)referenceFrame
|
||||
{
|
||||
[self replaceItemsInRange:range
|
||||
referenceFrame:referenceFrame
|
||||
withElements:@[]];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user