Internal Redesign:
- Combined MPExpression and MPMutableExpression - Abstracted children of MPExpression into MPExpressionElement protocol - Abstracted most of MPExpressionLayout and MPFunctionLayout into common superclass MPLayout
This commit is contained in:
@@ -2,88 +2,228 @@
|
||||
// MPExpression.m
|
||||
// MathPad
|
||||
//
|
||||
// Created by Kim Wittenburg on 17.04.14.
|
||||
// Created by Kim Wittenburg on 10.08.14.
|
||||
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MPExpression.h"
|
||||
#import "MPFunction.h"
|
||||
#import "MPException.h"
|
||||
#import "MPRangePath.h"
|
||||
|
||||
#import "NSObject+MPStringTest.h"
|
||||
#import "NSIndexPath+MPReverseIndexPath.h"
|
||||
#import "NSIndexPath+MPAdditions.h"
|
||||
#import "MPException.h"
|
||||
|
||||
NSString *MPAdditionOperator = @"+";
|
||||
NSString *MPSubtractionOperator = @"-";
|
||||
NSString *MPMultiplicationOperator = @"*";
|
||||
NSString *MPDivisionOperator = @"/";
|
||||
@interface MPExpression ()
|
||||
@property (readonly, nonatomic, strong) NSMutableArray *elements;
|
||||
@end
|
||||
|
||||
@interface MPExpression (MPExpressionPrivate)
|
||||
|
||||
- (NSInteger)lengthOfSymbol:(id)symbol;
|
||||
- (void)validateSymbols:(NSArray *)symbols;
|
||||
- (void)getSplitOffset:(out NSUInteger *)offset
|
||||
inSymbolAtIndex:(out NSUInteger *)symbolIndex
|
||||
forSplitLocation:(NSUInteger)loc;
|
||||
- (void)validateElements:(NSArray *)elements;
|
||||
- (BOOL)splitElementsAtLocation:(NSUInteger)location
|
||||
insertionIndex:(out NSUInteger *)insertionIndex;
|
||||
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
|
||||
inElementAtIndex:(out NSUInteger *)elementIndex;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpression (MPExpressionPrivate)
|
||||
|
||||
- (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}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)splitElementsAtLocation:(NSUInteger)location
|
||||
insertionIndex:(out NSUInteger *)insertionIndex
|
||||
{
|
||||
if (location == 0) {
|
||||
*insertionIndex = 0;
|
||||
return NO;
|
||||
}
|
||||
NSUInteger splitElementIndex;
|
||||
NSUInteger splitOffset = [self calculateSplitOffsetForSplitLocation:location
|
||||
inElementAtIndex:&splitElementIndex];
|
||||
id<MPExpressionElement> splitElement = self.elements[splitElementIndex];
|
||||
if (splitOffset == splitElement.length) {
|
||||
splitOffset = 0;
|
||||
splitElementIndex++;
|
||||
}
|
||||
if (splitOffset != 0) {
|
||||
NSString *stringElement = (NSString *)splitElement;
|
||||
NSString *leftPart = [stringElement substringToIndex:splitOffset];
|
||||
NSString *rightPart = [stringElement substringFromIndex:splitOffset];
|
||||
[self.elements replaceObjectsInRange:NSMakeRange(splitElementIndex, 1)
|
||||
withObjectsFromArray:@[leftPart, rightPart]];
|
||||
++splitElementIndex;
|
||||
}
|
||||
*insertionIndex = splitElementIndex;
|
||||
return splitOffset != 0;
|
||||
}
|
||||
|
||||
- (NSUInteger)calculateSplitOffsetForSplitLocation:(NSUInteger)location
|
||||
inElementAtIndex:(out NSUInteger *)elementIndex
|
||||
{
|
||||
NSUInteger length = 0;
|
||||
NSUInteger index = 0;
|
||||
NSUInteger elementLength = 0;
|
||||
for (id<MPExpressionElement> element in self.elements) {
|
||||
elementLength = element.length;
|
||||
length += elementLength;
|
||||
if (length >= location) {
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
*elementIndex = index;
|
||||
return elementLength - (length - location);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpression {
|
||||
@package
|
||||
__strong NSArray *_symbols;
|
||||
NSInteger _length;
|
||||
NSUInteger _cachedLength;
|
||||
NSRange editedRange;
|
||||
NSRange replacementRange;
|
||||
}
|
||||
|
||||
#pragma mark Creation Methods
|
||||
@synthesize elements = _elements;
|
||||
|
||||
- (instancetype)initWithSymbols:(NSArray *)symbols
|
||||
#pragma mark Creation Methods
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithElements:@[]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(id<MPExpressionElement>)element
|
||||
{
|
||||
return [self initWithElements:@[element]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithElements:(NSArray *)elements
|
||||
{
|
||||
[self validateSymbols:symbols];
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_symbols = [[NSArray alloc] initWithArray:symbols
|
||||
copyItems:YES];
|
||||
[self fixSymbols];
|
||||
_cachedLength = 0;
|
||||
_elements = [[NSMutableArray alloc] initWithCapacity:elements.count];
|
||||
_elements = [[NSMutableArray alloc] initWithArray:elements
|
||||
copyItems:YES];
|
||||
[self fixElements];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
|
||||
- (void)fixSymbols
|
||||
- (void)fixElements
|
||||
{
|
||||
NSMutableArray *mutableSymbols = [_symbols mutableCopy];
|
||||
for (NSInteger index = 0; index < mutableSymbols.count; index++) {
|
||||
id next = index+1 < mutableSymbols.count ? mutableSymbols[index+1] : nil;
|
||||
id current = mutableSymbols[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> current = self.elements[index];
|
||||
if ([current isString]) {
|
||||
if ([current length] == 0) {
|
||||
[mutableSymbols removeObjectAtIndex:index];
|
||||
index--;
|
||||
if (current.length == 0) {
|
||||
[self.elements removeObjectAtIndex:index];
|
||||
if (index < replacementRange.location) {
|
||||
replacementRange.location--;
|
||||
} else if (index < NSMaxRange(replacementRange)-1) {
|
||||
replacementRange.length--;
|
||||
} else if (index == NSMaxRange(replacementRange)) {
|
||||
editedRange.length++;
|
||||
}
|
||||
--index;
|
||||
} else if ([next isString]) {
|
||||
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
|
||||
[mutableSymbols replaceObjectAtIndex:index withObject:new];
|
||||
[mutableSymbols removeObjectAtIndex:index+1];
|
||||
index--;
|
||||
[self.elements replaceObjectsInRange:NSMakeRange(index, 2)
|
||||
withObjectsFromArray:@[new]];
|
||||
if (index < replacementRange.location) {
|
||||
replacementRange.location--;
|
||||
} else if (index < NSMaxRange(replacementRange)-1) {
|
||||
replacementRange.length--;
|
||||
} else if (index == NSMaxRange(replacementRange)-1) {
|
||||
editedRange.length++;
|
||||
}
|
||||
--index;
|
||||
}
|
||||
} else {
|
||||
[(MPFunction *)current setParent:self];
|
||||
}
|
||||
}
|
||||
_symbols = [mutableSymbols copy];
|
||||
}
|
||||
|
||||
#pragma mark Primitive Methods
|
||||
|
||||
- (NSUInteger)numberOfSymbols
|
||||
- (NSUInteger)length
|
||||
{
|
||||
return [_symbols count];
|
||||
if (_cachedLength == 0) {
|
||||
for (id<MPExpressionElement> element in self.elements) {
|
||||
_cachedLength += element.length;
|
||||
}
|
||||
}
|
||||
return _cachedLength;
|
||||
}
|
||||
|
||||
- (id)symbolAtIndex:(NSUInteger)index
|
||||
- (NSUInteger)numberOfElements
|
||||
{
|
||||
return _symbols[index];
|
||||
return self.elements.count;
|
||||
}
|
||||
|
||||
- (id<MPExpressionElement>)elementAtIndex:(NSUInteger)index
|
||||
{
|
||||
return self.elements[index];
|
||||
}
|
||||
|
||||
- (NSArray *)elementsInRange:(NSRange)range
|
||||
{
|
||||
return [self.elements subarrayWithRange:range];
|
||||
}
|
||||
|
||||
- (NSUInteger)indexOfElement:(id<MPExpressionElement>)element
|
||||
{
|
||||
return [self.elements indexOfObject:element];
|
||||
}
|
||||
|
||||
- (void)replaceElementsInRange:(NSRange)range
|
||||
withElements:(NSArray *)elements
|
||||
{
|
||||
if (NSMaxRange(range) > self.length) {
|
||||
@throw [NSException exceptionWithName:NSRangeException
|
||||
reason:@"Range out of bounds of expression"
|
||||
userInfo:nil];
|
||||
}
|
||||
[self validateElements:elements];
|
||||
|
||||
// Locate the position, split the elements
|
||||
NSUInteger startIndex;
|
||||
BOOL didSplitStart = NO;
|
||||
if ([self numberOfElements] == 0) {
|
||||
startIndex = 0;
|
||||
} else {
|
||||
didSplitStart = [self splitElementsAtLocation:range.location
|
||||
insertionIndex:&startIndex];
|
||||
}
|
||||
NSUInteger endIndex;
|
||||
BOOL didSplitEnd = [self splitElementsAtLocation:NSMaxRange(range)
|
||||
insertionIndex:&endIndex];
|
||||
|
||||
// Perform the replacement
|
||||
NSArray *newElements = [[NSArray alloc] initWithArray:elements
|
||||
copyItems:YES];
|
||||
[self.elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex)
|
||||
withObjectsFromArray:newElements];
|
||||
|
||||
|
||||
_cachedLength = 0;
|
||||
NSUInteger editingStart = startIndex - (didSplitStart?1:0);
|
||||
NSUInteger editingLength = endIndex - startIndex + (didSplitStart?1:0) + (didSplitEnd?1:0);
|
||||
editedRange = NSMakeRange(editingStart, editingLength);
|
||||
replacementRange = NSMakeRange(startIndex, elements.count);
|
||||
[self fixElements];
|
||||
MPRangePath *changePath = [[MPRangePath alloc] initWithRange:editedRange];
|
||||
[self didChangeElementsInRangePath:changePath replacementLength:replacementRange.length];
|
||||
}
|
||||
|
||||
- (double)doubleValue
|
||||
@@ -92,218 +232,18 @@ NSString *MPDivisionOperator = @"/";
|
||||
return 0;
|
||||
}
|
||||
|
||||
#pragma mark - NSCopying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
#pragma mark Notifications
|
||||
- (void)didChangeElementsInRangePath:(MPRangePath *)rangePath
|
||||
replacementLength:(NSUInteger)replacementLength
|
||||
{
|
||||
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithSymbols:_symbols];
|
||||
return copy;
|
||||
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
||||
MPRangePath *newPath = rangePath.copy;
|
||||
newPath.location = [newPath.location indexPathByPreceedingIndex:selfIndex];
|
||||
[self.parent didChangeElementsInRangePath:newPath
|
||||
replacementLength:replacementLength];
|
||||
}
|
||||
|
||||
#pragma mark - NSMutableCopying
|
||||
|
||||
- (id)mutableCopyWithZone:(NSZone *)zone
|
||||
{
|
||||
MPMutableExpression *mutableCopy = [[MPMutableExpression allocWithZone:zone] initWithSymbols:_symbols];
|
||||
return mutableCopy;
|
||||
}
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
// TODO: Test Coding
|
||||
return [self initWithSymbols:[aDecoder decodeObject]];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_symbols];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpression (MPExpressionPrivate)
|
||||
|
||||
- (NSInteger)lengthOfSymbol:(id)symbol
|
||||
{
|
||||
if ([symbol isString]) {
|
||||
return [symbol length];
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
- (void)validateSymbols:(NSArray *)symbols
|
||||
{
|
||||
for (id symbol in symbols) {
|
||||
if (!([symbol isString]
|
||||
|| [symbol isKindOfClass:[MPFunction class]])) {
|
||||
@throw [NSException exceptionWithName:MPIllegalSymbolException
|
||||
reason:@"Only NSString and MPFunction objects are valid symbols."
|
||||
userInfo:@{MPIllegalSymbolExceptionSymbolKey: symbol}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)getSplitOffset:(out NSUInteger *)offset
|
||||
inSymbolAtIndex:(out NSUInteger *)symbolIndex
|
||||
forSplitLocation:(NSUInteger)loc
|
||||
{
|
||||
NSUInteger length = 0;
|
||||
NSUInteger index = 0;
|
||||
NSUInteger symbolLength = 0;
|
||||
for (id symbol in _symbols) {
|
||||
symbolLength = [self lengthOfSymbol:symbol];
|
||||
length += symbolLength;
|
||||
if (length >= loc) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
*offset = symbolLength - (length - loc);
|
||||
*symbolIndex = index;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpression (MPExpressionExtensionMethods)
|
||||
|
||||
+ (NSArray *)operators
|
||||
{
|
||||
return @[MPAdditionOperator, MPSubtractionOperator, MPMultiplicationOperator, MPDivisionOperator];
|
||||
}
|
||||
|
||||
#pragma mark Creation Methods
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithSymbols:@[]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithString:(NSString *)aString
|
||||
{
|
||||
return [self initWithSymbols:@[aString]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFunction:(MPFunction *)aFunction
|
||||
{
|
||||
return [self initWithSymbols:@[aFunction]];
|
||||
}
|
||||
|
||||
+ (instancetype)expression
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
+ (instancetype)expressionWithString:(NSString *)aString
|
||||
{
|
||||
return [[self alloc] initWithString:aString];
|
||||
}
|
||||
|
||||
+ (instancetype)expressionWithFunction:(MPFunction *)aFunction
|
||||
{
|
||||
return [[self alloc] initWithFunction:aFunction];
|
||||
}
|
||||
|
||||
+ (instancetype)expressionWithSymbols:(NSArray *)symbols
|
||||
{
|
||||
return [[self alloc] initWithSymbols:symbols];
|
||||
}
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
|
||||
- (NSUInteger)indexOfSymbol:(id)symbol
|
||||
{
|
||||
return [_symbols indexOfObject:symbol];
|
||||
}
|
||||
|
||||
- (id)symbolAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.length == 0) {
|
||||
return self;
|
||||
}
|
||||
id symbol = [self symbolAtIndex:[indexPath indexAtPosition:0]];
|
||||
if (indexPath.length == 1) {
|
||||
return symbol;
|
||||
}
|
||||
if ([symbol isKindOfClass:[MPFunction class]]) {
|
||||
return [symbol symbolAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Working With Expressions
|
||||
|
||||
- (NSUInteger)length
|
||||
{
|
||||
if (_length == 0) {
|
||||
for (id symbol in _symbols) {
|
||||
_length += [self lengthOfSymbol:symbol];
|
||||
}
|
||||
}
|
||||
return _length;
|
||||
}
|
||||
|
||||
- (MPExpression *)subexpressionFromIndex:(NSUInteger)from
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(from, [self length] - from)];
|
||||
}
|
||||
|
||||
- (MPExpression *)subexpressionToIndex:(NSUInteger)to
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(0, to)];
|
||||
}
|
||||
|
||||
- (MPExpression *)subexpressionWithRange:(NSRange)range
|
||||
{
|
||||
if (NSMaxRange(range) > self.length) {
|
||||
@throw [NSException exceptionWithName:NSRangeException
|
||||
reason:@"Range outside bounds of expression."
|
||||
userInfo:nil];
|
||||
}
|
||||
if (range.location == self.length || NSMaxRange(range) == 0 || range.length == 0) {
|
||||
// Speed this up
|
||||
return [[MPExpression alloc] init];
|
||||
}
|
||||
NSUInteger startOffset;
|
||||
NSUInteger startSymbolIndex;
|
||||
[self getSplitOffset:&startOffset
|
||||
inSymbolAtIndex:&startSymbolIndex
|
||||
forSplitLocation:range.location];
|
||||
id startSymbol = _symbols[startSymbolIndex];
|
||||
if (startOffset == [self lengthOfSymbol:startSymbol]) {
|
||||
startOffset = 0;
|
||||
startSymbolIndex++;
|
||||
startSymbol = _symbols[startSymbolIndex];
|
||||
} else if ([startSymbol isString]) {
|
||||
startSymbol = [startSymbol substringFromIndex:startOffset];
|
||||
}
|
||||
NSUInteger endOffset;
|
||||
NSUInteger endSymbolIndex;
|
||||
[self getSplitOffset:&endOffset
|
||||
inSymbolAtIndex:&endSymbolIndex
|
||||
forSplitLocation:NSMaxRange(range)];
|
||||
id endSymbol = _symbols[endSymbolIndex];
|
||||
if ([endSymbol isString]) {
|
||||
endSymbol = [endSymbol substringToIndex:endOffset];
|
||||
|
||||
}
|
||||
|
||||
NSMutableArray *symbols = [[NSMutableArray alloc] initWithCapacity:endSymbolIndex-startSymbolIndex+1];
|
||||
[symbols addObject:startSymbol];
|
||||
if (endSymbolIndex > startSymbolIndex + 1) {
|
||||
NSInteger restLength = endSymbolIndex - startSymbolIndex - 1;
|
||||
[symbols addObjectsFromArray:[_symbols subarrayWithRange:NSMakeRange(startSymbolIndex+1, restLength)]];
|
||||
}
|
||||
if (endSymbolIndex > startSymbolIndex) {
|
||||
[symbols addObject:endSymbol];
|
||||
} else if (endSymbolIndex == startSymbolIndex && [startSymbol isString]) {
|
||||
NSString *result = [_symbols[startSymbolIndex] substringWithRange:NSMakeRange(startOffset, endOffset-startOffset)];
|
||||
[symbols replaceObjectAtIndex:0
|
||||
withObject:result];
|
||||
}
|
||||
return [[MPExpression alloc] initWithSymbols:symbols];
|
||||
}
|
||||
#pragma mark Basic NSObject Methods
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
@@ -321,32 +261,141 @@ NSString *MPDivisionOperator = @"/";
|
||||
|
||||
- (BOOL)isEqualToExpression:(MPExpression *)anExpression
|
||||
{
|
||||
// TODO: Use ->_symbols or .symbols
|
||||
return [_symbols isEqualToArray:anExpression->_symbols];
|
||||
return [self.elements isEqualToArray:anExpression.elements];
|
||||
}
|
||||
|
||||
- (MPExpression *)expressionByAppendingString:(NSString *)aString
|
||||
- (NSString *)description
|
||||
{
|
||||
return [self expressionByAppendingSymbols:@[aString]];
|
||||
NSMutableString *description = [[NSMutableString alloc] init];
|
||||
NSUInteger index = 0;
|
||||
for (id element in self.elements) {
|
||||
if ([element isString]) {
|
||||
NSMutableString *correctedSymbol = [[element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
|
||||
// Prefix operator
|
||||
if (element != self.elements[0]) {
|
||||
unichar prefix = [correctedSymbol characterAtIndex:0];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
|
||||
[correctedSymbol insertString:@"*"
|
||||
atIndex:0];
|
||||
}
|
||||
}
|
||||
// Suffix operator
|
||||
if (element != [self.elements lastObject]) {
|
||||
unichar suffix = [correctedSymbol characterAtIndex:correctedSymbol.length-1];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:suffix]) {
|
||||
[correctedSymbol appendString:@"*"];
|
||||
}
|
||||
}
|
||||
[description appendString:correctedSymbol];
|
||||
} else if (index > 0 && [self.elements[index-1] isKindOfClass:[MPFunction class]]) {
|
||||
[description appendFormat:@"*%@", [element description]];
|
||||
} else {
|
||||
[description appendString:[element description]];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
- (MPExpression *)expressionByAppendingFunction:(MPFunction *)aFunction
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [self expressionByAppendingSymbols:@[aFunction]];
|
||||
return [self.elements hash];
|
||||
}
|
||||
|
||||
- (MPExpression *)expressionByAppendingExpression:(MPExpression *)anExpression
|
||||
#pragma mark - NSCopying
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
return [self expressionByAppendingSymbols:anExpression.symbols];
|
||||
MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:self.elements];
|
||||
return copy;
|
||||
}
|
||||
|
||||
- (MPExpression *)expressionByAppendingSymbols:(NSArray *)symbols
|
||||
#pragma mark - NSCoding
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
return [[MPExpression alloc] initWithSymbols:[_symbols arrayByAddingObjectsFromArray:symbols]];
|
||||
// TODO: Test Coding
|
||||
return [self initWithElements:[aDecoder decodeObject]];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:self.elements];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpression (MPExpressionExtension)
|
||||
|
||||
#pragma mark Working With the Expression Tree
|
||||
- (id)elementAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if (indexPath.length == 0) {
|
||||
return self;
|
||||
}
|
||||
id<MPExpressionElement> element = [self elementAtIndex:[indexPath indexAtPosition:0]];
|
||||
if (indexPath.length == 1) {
|
||||
return element;
|
||||
}
|
||||
if ([element isFunction]) {
|
||||
return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingLastIndex]];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray *)elementsInRangePath:(MPRangePath *)rangePath
|
||||
{
|
||||
MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]];
|
||||
return [targetExpression elementsInRange:rangePath.rangeAtLastIndex];
|
||||
}
|
||||
|
||||
#pragma mark Working With Expressions
|
||||
- (MPExpression *)subexpressionFromLocation:(NSUInteger)from
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(from, self.length - from)];
|
||||
}
|
||||
|
||||
- (MPExpression *)subexpressionToLocation:(NSUInteger)to
|
||||
{
|
||||
return [self subexpressionWithRange:NSMakeRange(0, to)];
|
||||
}
|
||||
|
||||
- (MPExpression *)subexpressionWithRange:(NSRange)range
|
||||
{
|
||||
MPExpression *subexpression = [self copy];
|
||||
NSRange preceedingRange = NSMakeRange(0, range.location);
|
||||
NSUInteger firstOut = NSMaxRange(range);
|
||||
NSRange exceedingRange = NSMakeRange(firstOut, self.length-firstOut);
|
||||
[subexpression deleteElementsInRange:exceedingRange];
|
||||
[subexpression deleteElementsInRange:preceedingRange];
|
||||
return subexpression;
|
||||
}
|
||||
|
||||
#pragma mark Mutating Expressions
|
||||
- (void)appendElement:(id<MPExpressionElement>)anElement
|
||||
{
|
||||
[self appendElements:@[anElement]];
|
||||
}
|
||||
|
||||
- (void)appendElements:(NSArray *)elements
|
||||
{
|
||||
[self replaceElementsInRange:NSMakeRange(self.length, 0) withElements:elements];
|
||||
}
|
||||
|
||||
- (void)insertElement:(id<MPExpressionElement>)anElement atLocation:(NSUInteger)index
|
||||
{
|
||||
[self insertElements:@[anElement] atLocation:index];
|
||||
}
|
||||
|
||||
- (void)insertElements:(NSArray *)elements atLocation:(NSUInteger)index
|
||||
{
|
||||
[self replaceElementsInRange:NSMakeRange(index, 0) withElements:elements];
|
||||
}
|
||||
|
||||
- (void)deleteElementsInRange:(NSRange)range
|
||||
{
|
||||
[self replaceElementsInRange:range withElements:@[]];
|
||||
}
|
||||
|
||||
#pragma mark Evaluating Expressions
|
||||
|
||||
- (float)floatValue
|
||||
{
|
||||
return (float)[self doubleValue];
|
||||
@@ -367,290 +416,4 @@ NSString *MPDivisionOperator = @"/";
|
||||
return (long long)[self doubleValue];
|
||||
}
|
||||
|
||||
#pragma mark Querying an Expression's Contents
|
||||
|
||||
- (NSArray *)symbols
|
||||
{
|
||||
// _symbols is immutable so it is ok to just return it instead of making a copy
|
||||
return _symbols;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSMutableString *description = [[NSMutableString alloc] init];
|
||||
NSUInteger index = 0;
|
||||
for (id symbol in _symbols) {
|
||||
if ([symbol isString]) {
|
||||
NSMutableString *correctedSymbol = [[symbol stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
|
||||
// Prefix operator
|
||||
if (symbol != _symbols[0]) {
|
||||
unichar prefix = [correctedSymbol characterAtIndex:0];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
|
||||
[correctedSymbol insertString:@"*"
|
||||
atIndex:0];
|
||||
}
|
||||
}
|
||||
// Suffix operator
|
||||
if (symbol != [_symbols lastObject]) {
|
||||
unichar suffix = [correctedSymbol characterAtIndex:correctedSymbol.length-1];
|
||||
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:suffix]) {
|
||||
[correctedSymbol appendString:@"*"];
|
||||
}
|
||||
}
|
||||
[description appendString:correctedSymbol];
|
||||
} else if (index > 0 && [_symbols[index-1] isKindOfClass:[MPFunction class]]) {
|
||||
[description appendFormat:@"*%@", [symbol description]];
|
||||
} else {
|
||||
[description appendString:[symbol description]];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return [_symbols hash];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPExpression (MPChangeNotificationExtension)
|
||||
|
||||
- (void)symbolsChangedInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)length
|
||||
{
|
||||
if (!self.parent) {
|
||||
return;
|
||||
}
|
||||
NSUInteger selfIndex = [self.parent indexOfChild:self];
|
||||
NSIndexPath *newLocation = [rangePath.location indexPathByPrecedingIndex:selfIndex];
|
||||
MPRangePath *newPath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length];
|
||||
[self.parent symbolsChangedInRangePath:newPath replacementLength:length];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMutableExpression {
|
||||
NSRange editedRange;
|
||||
NSRange replacementRange;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSymbols:(NSArray *)symbols
|
||||
{
|
||||
[self validateSymbols:symbols];
|
||||
self = [super initWithSymbols:nil];
|
||||
if (self) {
|
||||
editedRange = NSMakeRange(0, 0);
|
||||
replacementRange = NSMakeRange(0, 0);
|
||||
_symbols = [[NSMutableArray alloc] initWithArray:symbols
|
||||
copyItems:YES];
|
||||
[self fixSymbols];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fixSymbols
|
||||
{
|
||||
NSMutableArray *mutableSymbols = (NSMutableArray *)_symbols;
|
||||
for (NSInteger index = 0; index < mutableSymbols.count; index++) {
|
||||
id next = index+1 < mutableSymbols.count ? mutableSymbols[index+1] : nil;
|
||||
id current = mutableSymbols[index];
|
||||
if ([current isString]) {
|
||||
if ([current length] == 0) {
|
||||
[mutableSymbols removeObjectAtIndex:index];
|
||||
if (index < replacementRange.location) {
|
||||
replacementRange.location--;
|
||||
} else if (index < NSMaxRange(replacementRange)-1) {
|
||||
replacementRange.length--;
|
||||
} else if (index == NSMaxRange(replacementRange)) {
|
||||
editedRange.length++;
|
||||
}
|
||||
index--;
|
||||
} else if ([next isString]) {
|
||||
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
|
||||
[mutableSymbols replaceObjectAtIndex:index withObject:new];
|
||||
[mutableSymbols removeObjectAtIndex:index+1];
|
||||
if (index < replacementRange.location) {
|
||||
replacementRange.location--;
|
||||
} else if (index < NSMaxRange(replacementRange)-1) {
|
||||
replacementRange.length--;
|
||||
} else if (index == NSMaxRange(replacementRange)-1) {
|
||||
editedRange.length++;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
} else {
|
||||
[(MPFunction *)current setParent:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)symbols
|
||||
{
|
||||
// Return an immutable array:
|
||||
return [_symbols copy];
|
||||
}
|
||||
|
||||
- (void)replaceSymbolsInRange:(NSRange)range
|
||||
withSymbols:(NSArray *)symbols
|
||||
{
|
||||
if (NSMaxRange(range) > self.length) {
|
||||
@throw [NSException exceptionWithName:NSRangeException
|
||||
reason:@"Range out of bounds of expression."
|
||||
userInfo:nil];
|
||||
}
|
||||
[self validateSymbols:symbols];
|
||||
|
||||
// Locate the position, split the symbols
|
||||
NSUInteger startIndex;
|
||||
BOOL didSplitStart = NO;
|
||||
if ([self numberOfSymbols] == 0) {
|
||||
startIndex = 0;
|
||||
} else {
|
||||
[self splitSymbolsAtLocation:range.location
|
||||
insertionIndex:&startIndex
|
||||
didSplit:&didSplitStart];
|
||||
}
|
||||
|
||||
// Perform the deletion
|
||||
NSUInteger endIndex;
|
||||
BOOL didSplitEnd = NO;
|
||||
[self splitSymbolsAtLocation:NSMaxRange(range)
|
||||
insertionIndex:&endIndex
|
||||
didSplit:&didSplitEnd];
|
||||
if (range.length > 0) {
|
||||
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
|
||||
for (NSUInteger index = startIndex; index < endIndex; index++) {
|
||||
[indexes addIndex:index];
|
||||
}
|
||||
// TODO: Replace with removeObjectsInRange:
|
||||
[(NSMutableArray *)_symbols removeObjectsAtIndexes:indexes];
|
||||
}
|
||||
|
||||
// Perform the insertion
|
||||
if (symbols.count > 0) {
|
||||
NSArray *newSymbols = [[NSArray alloc] initWithArray:symbols copyItems:YES];
|
||||
[(NSMutableArray *)_symbols replaceObjectsInRange:NSMakeRange(startIndex, 0)
|
||||
withObjectsFromArray:newSymbols];
|
||||
}
|
||||
|
||||
// Invalidate length and revalidate structure
|
||||
_length = 0;
|
||||
NSUInteger editingStart = startIndex - (didSplitStart?1:0);
|
||||
NSUInteger editingLength = endIndex - startIndex + (didSplitStart?1:0) + (didSplitEnd?1:0);
|
||||
editedRange = NSMakeRange(editingStart, editingLength);
|
||||
replacementRange = NSMakeRange(startIndex, symbols.count);
|
||||
[self fixSymbols];
|
||||
MPRangePath *changePath = [[MPRangePath alloc] initWithRange:editedRange];
|
||||
[self symbolsChangedInRangePath:changePath replacementLength:replacementRange.length];
|
||||
}
|
||||
|
||||
- (void)splitSymbolsAtLocation:(NSUInteger)loc
|
||||
insertionIndex:(out NSUInteger *)insertionIndex
|
||||
didSplit:(out BOOL *)flag;
|
||||
{
|
||||
NSUInteger splitSymbolIndex;
|
||||
NSUInteger splitOffset;
|
||||
[self getSplitOffset:&splitOffset
|
||||
inSymbolAtIndex:&splitSymbolIndex
|
||||
forSplitLocation:loc];
|
||||
id splitSymbol = _symbols[splitSymbolIndex];
|
||||
NSInteger splitSymbolLength = [self lengthOfSymbol:splitSymbol];
|
||||
if (splitOffset == splitSymbolLength) {
|
||||
splitOffset = 0;
|
||||
splitSymbolIndex++;
|
||||
}
|
||||
if (splitOffset != 0) {
|
||||
NSString *leftPart = [splitSymbol substringToIndex:splitOffset];
|
||||
NSString *rightPart = [splitSymbol substringFromIndex:splitOffset];
|
||||
[(NSMutableArray *)_symbols replaceObjectAtIndex:splitSymbolIndex
|
||||
withObject:leftPart];
|
||||
splitSymbolIndex++;
|
||||
[(NSMutableArray *)_symbols insertObject:rightPart
|
||||
atIndex:splitSymbolIndex];
|
||||
}
|
||||
*flag = splitOffset != 0;
|
||||
*insertionIndex = splitSymbolIndex;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MPMutableExpression (MPMutableExpressionExtensionMethods)
|
||||
|
||||
- (void)insertString:(NSString *)aString
|
||||
atIndex:(NSUInteger)loc
|
||||
{
|
||||
[self insertSymbols:@[aString]
|
||||
atIndex:loc];
|
||||
}
|
||||
|
||||
- (void)insertFunction:(MPFunction *)aFunction
|
||||
atIndex:(NSUInteger)loc
|
||||
{
|
||||
[self insertSymbols:@[aFunction]
|
||||
atIndex:loc];
|
||||
}
|
||||
|
||||
- (void)insertExpression:(MPExpression *)anExpression
|
||||
atIndex:(NSUInteger)loc
|
||||
{
|
||||
[self insertSymbols:anExpression.symbols
|
||||
atIndex:loc];
|
||||
}
|
||||
|
||||
- (void)insertSymbols:(NSArray *)symbols
|
||||
atIndex:(NSUInteger)loc
|
||||
{
|
||||
[self replaceSymbolsInRange:NSMakeRange(loc, 0)
|
||||
withSymbols:symbols];
|
||||
}
|
||||
|
||||
- (void)deleteSymbolsInRange:(NSRange)range
|
||||
{
|
||||
[self replaceSymbolsInRange:range
|
||||
withSymbols:@[]];
|
||||
}
|
||||
|
||||
- (void)appendString:(NSString *)aString
|
||||
{
|
||||
[self appendSymbols:@[aString]];
|
||||
}
|
||||
|
||||
- (void)appendFunction:(MPFunction *)aFunction
|
||||
{
|
||||
[self appendSymbols:@[aFunction]];
|
||||
}
|
||||
|
||||
- (void)appendExpression:(MPExpression *)anExpression
|
||||
{
|
||||
[self appendSymbols:anExpression.symbols];
|
||||
}
|
||||
|
||||
- (void)appendSymbols:(NSArray *)symbols
|
||||
{
|
||||
[self replaceSymbolsInRange:NSMakeRange(self.length, 0)
|
||||
withSymbols:symbols];
|
||||
}
|
||||
|
||||
- (void)setString:(NSString *)aString
|
||||
{
|
||||
[self setSymbols:@[aString]];
|
||||
}
|
||||
|
||||
- (void)setFunction:(MPFunction *)aFunction
|
||||
{
|
||||
[self setSymbols:@[aFunction]];
|
||||
}
|
||||
|
||||
- (void)setExpression:(MPExpression *)anExpression
|
||||
{
|
||||
[self setSymbols:anExpression.symbols];
|
||||
}
|
||||
|
||||
- (void)setSymbols:(NSArray *)symbols
|
||||
{
|
||||
[self replaceSymbolsInRange:NSMakeRange(0, self.length)
|
||||
withSymbols:symbols];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user