// // MPExpression.m // MathPad // // Created by Kim Wittenburg on 10.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // // TODO: Reorganise header/implementation order. Drop the primitive/not primitive Approach #import "MPExpression.h" #import "MPFunction.h" #import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" #import "MPException.h" #import "MPExpressionEvaluator.h" @interface MPExpression () { NSMutableArray *__strong _elements; } @end @interface MPExpression (MPExpressionPrivate) - (NSUInteger)lengthOfElements:(NSArray *)elements; - (void)validateElements:(NSArray *)elements; - (BOOL)splitElementsAtLocation:(NSUInteger)location insertionIndex:(out NSUInteger *)insertionIndex; @end @implementation MPExpression (MPExpressionPrivate) - (NSUInteger)lengthOfElements:(NSArray *)elements { NSUInteger length = 0; for (id element in elements) { length += element.length; } return length; } - (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 splitOffset; NSUInteger splitElementIndex = [self indexOfElementAtSymbolLocation:location offset:&splitOffset]; if (splitOffset != 0) { NSString *splitElement = (NSString *)self.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; } @end @implementation MPExpression { NSUInteger _cachedLength; NSRange _editedRange; BOOL _didSplitEndOnEditing; NSUInteger _replacementLength; } #pragma mark Creation Methods - (instancetype)init { return [self initWithElements:@[]]; } - (instancetype)initWithElement:(id)element { return [self initWithElements:@[element]]; } - (instancetype)initWithElements:(NSArray *)elements { self = [super init]; if (self) { _cachedLength = 0; _elements = [[NSMutableArray alloc] initWithArray:elements copyItems:YES]; [self fixElements]; } return self; } #pragma mark Working With the Expression Tree - (void)fixElements { for (NSUInteger index = 0; index < self.elements.count; index++) { id next = index+1 < self.elements.count ? self.elements[index+1] : nil; id current = self.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) { --_editedRange.location; ++_editedRange.length; } else if (index >= _editedRange.location && index < maxReplacementIndex - 1) { --_replacementLength; } else if (index == maxReplacementIndex - 1) { if (!_didSplitEndOnEditing) { ++_editedRange.length; } } --index; } } else { [(MPFunction *)current setParent:self]; } } } #pragma mark Primitive Methods - (NSUInteger)length { if (_cachedLength == 0) { _cachedLength = [self lengthOfElements:self.elements]; } return _cachedLength; } - (NSUInteger)numberOfElements { return self.elements.count; } - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx { [self replaceSymbolsInRange:NSMakeRange(idx, 1) withElements:@[obj]]; } - (id)objectAtIndexedSubscript:(NSUInteger)idx { return [self elementAtIndex:idx]; } - (id)elementAtIndex:(NSUInteger)anIndex { return self.elements[anIndex]; } - (NSArray *)elementsInRange:(NSRange)range { return [self.elements subarrayWithRange:range]; } #warning If multiple equal expressions exist errors may occur... - (NSUInteger)indexOfElement:(id)element { return [self.elements indexOfObject:element]; } - (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location offset:(out NSUInteger *)offset { if (location == 0) { if (offset != NULL) { *offset = 0; } return 0; } // Calculating elementIndex and splitOffset NSUInteger totalLength = 0; NSUInteger elementIndex = 0; NSUInteger elementLength = 0; for (id element in self.elements) { elementLength = element.length; totalLength += elementLength; if (totalLength >= location) { break; } ++elementIndex; } NSUInteger splitOffset = elementLength - (totalLength - location); id element = self.elements[elementIndex]; if (splitOffset == element.length) { splitOffset = 0; elementIndex++; } if (offset != NULL) { *offset = splitOffset; } return elementIndex; } - (void)replaceSymbolsInRange:(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; // startIndex is inclusive BOOL didSplitStart = NO; if ([self numberOfElements] == 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]; [_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex) withObjectsFromArray:newElements]; _cachedLength = 0; NSUInteger editLocation = startIndex - (didSplitStart ? 1 : 0); NSUInteger editLength = range.length > 0 ? (endIndex - startIndex) : 0; _editedRange = NSMakeRange(editLocation, editLength); _didSplitEndOnEditing = didSplitEnd; _replacementLength = elements.count + (didSplitStart ? 1 : 0) + (didSplitEnd ? 1 : 0); [self fixElements]; [self didChangeElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange] replacementLength:_replacementLength]; } - (NSArray *)elements { return _elements; } - (double)evaluateExpression:(NSError *__autoreleasing *)error { return [self.evaluator evaluateWithError:error]; } @synthesize evaluator = _evaluator; - (MPExpressionEvaluator *)evaluator { if (!_evaluator) { _evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self]; } return _evaluator; } #pragma mark Notifications - (void)didChangeElementsInRangePath:(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]; } #pragma mark Basic NSObject Methods /* - (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 { return [self.elements isEqualToArray:anExpression.elements]; } */ - (NSString *)description { #warning Bad Implementation 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; } - (NSUInteger)hash { return [self.elements hash]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { MPExpression *copy = [[MPExpression allocWithZone:zone] initWithElements:self.elements]; return copy; } #pragma mark - NSCoding - (id)initWithCoder:(NSCoder *)aDecoder { // 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 - (MPExpression *)rootExpression { if (self.parent == nil) { return self; } return [self.parent rootExpression]; } - (id)elementAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.length == 0) { return self; } id element = [self elementAtIndex:[indexPath indexAtPosition:0]]; if (indexPath.length == 1) { return element; } if ([element isFunction]) { return [(MPFunction *)element elementAtIndexPath:[indexPath indexPathByRemovingFirstIndex]]; } return nil; } - (NSArray *)elementsInRangePath:(MPRangePath *)rangePath { MPExpression *targetExpression = [self elementAtIndexPath:[rangePath.location indexPathByRemovingLastIndex]]; 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 - (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)anElement { [self appendElements:@[anElement]]; } - (void)appendElements:(NSArray *)elements { [self replaceSymbolsInRange:NSMakeRange(self.length, 0) withElements:elements]; } - (void)insertElement:(id)anElement atLocation:(NSUInteger)location { [self insertElements:@[anElement] atLocation:location]; } - (void)insertElements:(NSArray *)elements atLocation:(NSUInteger)location { [self replaceSymbolsInRange:NSMakeRange(location, 0) withElements:elements]; } - (void)deleteElementsInRange:(NSRange)range { [self replaceSymbolsInRange:range withElements:@[]]; } @end