// // 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 "MPException_Private.h" @interface MPExpression (MPExpressionPrivate) - (NSInteger)lengthOfSymbol:(id)symbol; - (void)validateSymbols:(NSArray *)symbols; - (NSArray *)repairedSymbols:(NSArray *)symbols; // Merges subsequent strings - (void)repairSymbols:(NSMutableArray *)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 = [self repairedSymbols:symbols]; _symbols = [[NSArray alloc] initWithArray:symbols copyItems:YES]; } return self; } #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 { // TODO: Test Copying 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 isKindOfClass:[NSString class]]) { return [symbol length]; } return 1; } // TODO: Deal with subsequent strings - (void)validateSymbols:(NSArray *)symbols { for (id symbol in symbols) { if (!([symbol isKindOfClass:[NSString class]] || [symbol isKindOfClass:[MPFunction class]])) { @throw [NSException exceptionWithName:MPIllegalSymbolException reason:MPIllegalSymbolExceptionReason userInfo:@{MPIllegalSymbolExceptionSymbolKey: symbol}]; } } } - (NSArray *)repairedSymbols:(NSArray *)symbols { NSMutableArray *mutableSymbols = [symbols mutableCopy]; [self repairSymbols:mutableSymbols]; return [mutableSymbols copy]; } - (void)repairSymbols:(NSMutableArray *)symbols { for (NSInteger index = 1; index < symbols.count; index++) { id last = symbols[index-1]; id current = symbols[index]; if ([last isKindOfClass:[NSString class]] && [current isKindOfClass:[NSString class]]) { NSMutableString *new = [NSMutableString stringWithFormat:@"%@%@", last, current]; [symbols replaceObjectAtIndex:index-1 withObject:new]; [symbols removeObjectAtIndex:index]; index--; } } } - (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) #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 isKindOfClass:[NSString class]]) { startSymbol = [startSymbol substringFromIndex:startOffset]; } NSUInteger endOffset; NSUInteger endSymbolIndex; [self getSplitOffset:&endOffset inSymbolAtIndex:&endSymbolIndex forSplitLocation:NSMaxRange(range)]; id endSymbol = _symbols[endSymbolIndex]; if ([endSymbol isKindOfClass:[NSString class]]) { 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 isKindOfClass:[NSString class]]) { 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]; } // TODO: prefix and suffix operators - (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 { #warning Unimplemented Method /* Join the symbols regarding pre- and suffix operators. If there is no operator a * should be inserted */ return [super description]; } - (NSUInteger)hash { return [_symbols hash]; } @end @interface MPMutableExpression (MPMutableExpressionPrivate) - (NSArray *)makeSymbols:(NSArray *)symbols mutable:(BOOL)mutable copySymbols:(BOOL)copy; @end @implementation MPMutableExpression (MPMutableExpressionPrivate) - (NSArray *)makeSymbols:(NSArray *)symbols mutable:(BOOL)mutable copySymbols:(BOOL)copy { NSMutableArray *newSymbols = [[NSMutableArray alloc] initWithCapacity:symbols.count]; for (id symbol in symbols) { id newSymbol; if ([symbol isKindOfClass:[NSString class]]) { if (mutable) { newSymbol = [symbol mutableCopy]; } else { newSymbol = [symbol copy]; } } else { if (copy) { newSymbol = [symbol copy]; } else { newSymbol = symbol; } } [newSymbols addObject:newSymbol]; } return newSymbols; } @end // A mutable expression uses NSMutableString objects instead of NSString objects @implementation MPMutableExpression - (instancetype)initWithSymbols:(NSArray *)symbols { [self validateSymbols:symbols]; self = [super initWithSymbols:nil]; if (self) { symbols = [self repairedSymbols:symbols]; _symbols = [self makeSymbols:symbols mutable:YES copySymbols:YES]; } return self; } - (id)symbolAtIndex:(NSUInteger)index { id symbol = _symbols[index]; if ([symbol isKindOfClass:[NSString class]]) { return [symbol copy]; } return symbol; } - (NSArray *)symbols { return [self makeSymbols:_symbols mutable:NO copySymbols:NO]; } - (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; [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 = [self makeSymbols:symbols mutable:YES copySymbols:YES]; [(NSMutableArray *)_symbols replaceObjectsInRange:NSMakeRange(startIndex, 0) withObjectsFromArray:newSymbols]; } // Revalidate structure and invalidate length [self repairSymbols:(NSMutableArray *)_symbols]; _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) { NSMutableString *leftPart = [[splitSymbol substringToIndex:splitOffset] mutableCopy]; NSMutableString *rightPart = [[splitSymbol substringFromIndex:splitOffset] mutableCopy]; [(NSMutableArray *)_symbols replaceObjectAtIndex:splitSymbolIndex withObject:leftPart]; splitSymbolIndex++; [(NSMutableArray *)_symbols insertObject:rightPart atIndex:splitSymbolIndex]; } *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