// // MPExpression.m // MathPad // // Created by Kim Wittenburg on 10.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPExpression.h" #import "MPFunction.h" #import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" #import "MPException.h" #import "MPExpressionTokenizer.h" #import "MPExpressionEvaluator.h" #import "MPToken.h" @interface MPExpression () { NSMutableArray * _elements; } - (NSArray *)tokens; - (void)validateElements:(NSArray *)elements; - (void)fixElements; - (void)_replaceSymbolsInRange:(NSRange)range withElements:(NSArray *)elements; - (BOOL)_splitElementsAtLocation:(NSUInteger)location insertionIndex:(out 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)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 next = index+1 < _elements.count ? _elements[index+1] : nil; id 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 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 location = 0; NSUInteger elementIndex = 0; id element = nil; while (location < anIndex) { element = _elements[elementIndex++]; location += element.length; } if (location == anIndex && element.isFunction) { return element; } NSUInteger indexInString = location - element.length + anIndex; return [((NSString *)element) substringWithRange:NSMakeRange(indexInString, 1)]; } case MPTokenReferenceFrame: return self.tokens[anIndex]; } } - (id)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)element { return [_elements indexOfObject:element]; } - (NSArray *)itemsInRange:(NSRange)range referenceFrame:(MPReferenceFrame)referenceFrame { MPExpression *subexpression = [self subexpressionWithRange:range 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 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 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)index fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame { return [self convertIndex:index fromReferenceFrame:fromReferenceFrame toReferenceFrame:toReferenceFrame offset:NULL]; } - (NSUInteger)convertIndex:(NSUInteger)index fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame offset:(NSUInteger *)offset { if (fromReferenceFrame == toReferenceFrame || index == 0) { if (offset) { *offset = 0; } return index; } NSUInteger symbolIndex __block = 0; switch (fromReferenceFrame) { case MPElementReferenceFrame: [_elements enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { symbolIndex += obj.length; *stop = idx >= index - 1; }]; break; case MPSymbolReferenceFrame: symbolIndex = index; break; case MPTokenReferenceFrame: [_elements enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { symbolIndex = NSMaxRange(obj.range); *stop = idx >= index - 1; }]; break; } switch (toReferenceFrame) { case MPElementReferenceFrame: { NSUInteger totalLength = 0; NSUInteger elementIndex = 0; id 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 token; while (totalLength < symbolIndex) { token = self.tokens[tokenIndex++]; totalLength = NSMaxRange(token.range); } 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)range referenceFrame:(MPReferenceFrame)referenceFrame withElements:(NSArray *)elements { NSUInteger start = [self convertIndex:range.location fromReferenceFrame:referenceFrame toReferenceFrame:MPSymbolReferenceFrame]; NSUInteger end = [self convertIndex:NSMaxRange(range) 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]; [_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex) withObjectsFromArray:newElements]; _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 didChangeElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange] replacementLength:_replacementLength]; } - (BOOL)_splitElementsAtLocation:(NSUInteger)location insertionIndex:(out 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; } - (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)range referenceFrame:(MPReferenceFrame)referenceFrame { MPExpression *subexpression = [self copy]; NSRange preceedingRange = NSMakeRange(0, range.location); NSUInteger firstOut = NSMaxRange(range); NSRange exceedingRange = NSMakeRange(firstOut, [self countItemsInReferenceFrame:referenceFrame] - firstOut); [subexpression deleteElementsInRange:exceedingRange referenceFrame:referenceFrame]; [subexpression deleteElementsInRange:preceedingRange referenceFrame:referenceFrame]; return subexpression; } #pragma mark Evaluating Expressions - (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error { MPExpressionEvaluator *evaluator = [[MPExpressionEvaluator alloc] initWithExpression:self]; MPTerm *term = [evaluator parseExpectingVariable:NO error:error]; return [term evaluate]; } #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 _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)elementAtIndex:(NSUInteger)index { return [self itemAtIndex:index referenceFrame:MPElementReferenceFrame]; } - (id)symbolAtIndex:(NSUInteger)index { return [self itemAtIndex:index referenceFrame:MPSymbolReferenceFrame]; } - (id)tokenAtIndex:(NSUInteger)index { return [self itemAtIndex:index referenceFrame:MPTokenReferenceFrame]; } #pragma mark Mutating Expressions - (void)appendElement:(id)anElement { [self appendElements:@[anElement]]; } - (void)appendElements:(NSArray *)elements { [self replaceItemsInRange:NSMakeRange([self countItemsInReferenceFrame:MPSymbolReferenceFrame], 0) referenceFrame:MPSymbolReferenceFrame withElements:elements]; } - (void)insertElement:(id)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