// // MPExpression.m // MathPad // // Created by Kim Wittenburg on 17.04.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPExpression.h" #import "MPFunction.h" #import "MPException.h" #import "NSObject+MPStringTest.h" NSString *MPAdditionOperator = @"+"; NSString *MPSubtractionOperator = @"-"; NSString *MPMultiplicationOperator = @"*"; NSString *MPDivisionOperator = @"/"; @interface MPExpression (MPExpressionPrivate) - (NSInteger)lengthOfSymbol:(id)symbol; - (void)validateSymbols:(NSArray *)symbols; - (NSArray *)fixedSymbols:(NSArray *)symbols; - (void)getSplitOffset:(out NSUInteger *)offset inSymbolAtIndex:(out NSUInteger *)symbolIndex forSplitLocation:(NSUInteger)loc; @end @implementation MPExpression { @package __strong NSArray *_symbols; NSInteger _length; } #pragma mark Creation Methods - (instancetype)initWithSymbols:(NSArray *)symbols { [self validateSymbols:symbols]; self = [super init]; if (self) { _symbols = [[NSArray alloc] initWithArray:symbols copyItems:YES]; [self fixSymbols]; } return self; } #pragma mark Working With the Expression Tree - (void)fixSymbols { _symbols = [self fixedSymbols:_symbols]; } #pragma mark Primitive Methods - (NSUInteger)numberOfSymbols { return [_symbols count]; } - (id)symbolAtIndex:(NSUInteger)index { return _symbols[index]; } - (double)doubleValue { #warning Unimplemented Method return 0; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { MPExpression *copy = [[MPExpression allocWithZone:zone] initWithSymbols:_symbols]; return copy; } #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}]; } } } - (NSArray *)fixedSymbols:(NSArray *)symbols { 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]; if ([current isString]) { if ([current length] == 0) { [mutableSymbols removeObjectAtIndex:index]; index--; } else if ([next isString]) { NSString *new = [NSString stringWithFormat:@"%@%@", current, next]; [mutableSymbols replaceObjectAtIndex:index withObject:new]; [mutableSymbols removeObjectAtIndex:index+1]; index--; } } else { [(MPFunction *)current setParent:self]; } } return [mutableSymbols copy]; } - (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 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]; } - (BOOL)isEqual:(id)object { if (self == object) { return YES; } if (object == nil) { return NO; } if (![object isKindOfClass:[MPExpression class]]) { return NO; } return [self isEqualToExpression:(MPExpression *)object]; } - (BOOL)isEqualToExpression:(MPExpression *)anExpression { // TODO: Use ->_symbols or .symbols return [_symbols isEqualToArray:anExpression->_symbols]; } - (MPExpression *)expressionByAppendingString:(NSString *)aString { return [self expressionByAppendingSymbols:@[aString]]; } - (MPExpression *)expressionByAppendingFunction:(MPFunction *)aFunction { return [self expressionByAppendingSymbols:@[aFunction]]; } - (MPExpression *)expressionByAppendingExpression:(MPExpression *)anExpression { return [self expressionByAppendingSymbols:anExpression.symbols]; } - (MPExpression *)expressionByAppendingSymbols:(NSArray *)symbols { return [[MPExpression alloc] initWithSymbols:[_symbols arrayByAddingObjectsFromArray:symbols]]; } #pragma mark Evaluating Expressions - (float)floatValue { return (float)[self doubleValue]; } - (int)intValue { return (int)[self doubleValue]; } - (NSInteger)integerValue { return (NSInteger)[self doubleValue]; } - (long long)longLongValue { 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 MPMutableExpression { @protected NSInteger _editCount; } - (instancetype)initWithSymbols:(NSArray *)symbols { [self validateSymbols:symbols]; self = [super initWithSymbols:nil]; if (self) { _symbols = [[NSMutableArray alloc] initWithArray:symbols copyItems:YES]; [self fixSymbols]; } return self; } - (void)fixSymbols { _symbols = [[self fixedSymbols:_symbols] mutableCopy]; } - (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; if ([self numberOfSymbols] == 0) { startIndex = 0; } else { [self splitSymbolsAtLocation:range.location insertionIndex:&startIndex]; } // Perform the deletion if (range.length > 0) { NSUInteger endIndex; [self splitSymbolsAtLocation:NSMaxRange(range) insertionIndex:&endIndex]; NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init]; for (NSUInteger index = startIndex; index < endIndex; index++) { [indexes addIndex:index]; } [(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]; } // Revalidate structure and invalidate length if (_editCount == 0) { [self fixSymbols]; } _length = 0; } - (void)splitSymbolsAtLocation:(NSUInteger)loc insertionIndex:(out NSUInteger *)insertionIndex { 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]; } *insertionIndex = splitSymbolIndex; } - (void)beginEditing { _editCount++; } - (void)endEditing { if (_editCount == 0) { return; } _editCount--; if (_editCount == 0) { [self fixSymbols]; } } @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