// // MPExpression.m // MathPad // // Created by Kim Wittenburg on 10.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPExpression.h" #import "MPExpressionElement.h" #import "MPFunction.h" #import "MPRangePath.h" #import "MPExpressionTokenizer.h" #import "MPToken.h" #import "MPExpressionParser.h" #import "MPParsedExpression.h" #import "NSIndexPath+MPAdditions.h" NSString *const MPIllegalElementException = @"MPIllegalElementException"; NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey"; @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)aRange referenceFrame:(MPReferenceFrame)referenceFrame { MPExpression *subexpression = [self subexpressionWithRange:aRange 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)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame { return [self convertIndex:anIndex fromReferenceFrame:fromReferenceFrame toReferenceFrame:toReferenceFrame offset:NULL]; } - (NSUInteger)convertIndex:(NSUInteger)anIndex fromReferenceFrame:(MPReferenceFrame)fromReferenceFrame toReferenceFrame:(MPReferenceFrame)toReferenceFrame offset:(out NSUInteger *)offset { if (fromReferenceFrame == toReferenceFrame || anIndex == 0) { if (offset) { *offset = 0; } return anIndex; } NSUInteger symbolIndex __block = 0; switch (fromReferenceFrame) { case MPElementReferenceFrame: [_elements enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { symbolIndex += obj.length; *stop = idx >= anIndex - 1; }]; break; case MPSymbolReferenceFrame: symbolIndex = anIndex; break; case MPTokenReferenceFrame: [self.tokens enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { symbolIndex += obj.range.length; *stop = idx >= anIndex - 1; }]; // symbolIndex = [self.tokens[anIndex] range].location; 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 tokenIndex = 0; while (true) { id token = self.tokens[tokenIndex++]; if (NSMaxRange(token.range) < symbolIndex || token.range.location > symbolIndex) { continue; } NSUInteger offsetInToken = anIndex - token.range.location; if (offsetInToken == token.range.length) { offsetInToken = 0; tokenIndex++; } if (offset) { *offset = offsetInToken; } return tokenIndex; } // Should never get here return 0; } } } - (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)aRange referenceFrame:(MPReferenceFrame)referenceFrame withElements:(NSArray *)elements { NSUInteger start = [self convertIndex:aRange.location fromReferenceFrame:referenceFrame toReferenceFrame:MPSymbolReferenceFrame]; NSUInteger end = [self convertIndex:NSMaxRange(aRange) 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]; NSArray *removedElements = [_elements subarrayWithRange:NSMakeRange(startIndex, endIndex-startIndex)]; [_elements replaceObjectsInRange:NSMakeRange(startIndex, endIndex-startIndex) withObjectsFromArray:newElements]; for (id element in removedElements) { if ([element isFunction]) { ((MPFunction *)element).parent = nil; } } _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)aRange referenceFrame:(MPReferenceFrame)referenceFrame { MPExpression *subexpression = [self copy]; NSRange preceedingRange = NSMakeRange(0, aRange.location); NSUInteger firstOut = NSMaxRange(aRange); 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 *)evaluateWithErrors:(NSArray *__autoreleasing *)errors { NSArray *parsingErrors; MPParsedExpression *parsedExpression = [self parse:&parsingErrors]; NSError *evaluationError; NSDecimalNumber *result = parsedExpression ? [parsedExpression evaluate:&evaluationError] : nil; if (errors) { NSMutableArray *localErrors = [[NSMutableArray alloc] initWithCapacity:parsingErrors.count+1]; if (parsingErrors) { [localErrors addObjectsFromArray:parsingErrors]; } if (evaluationError) { [localErrors addObject:evaluationError]; } *errors = localErrors; } return result; } - (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors { return [self parseExpectingVariable:NO errors:errors]; } - (MPParsedExpression *)parseExpectingVariable:(BOOL)flag errors:(NSArray *__autoreleasing *)errors { return [[[MPExpressionParser alloc] initWithExpression:self] parseExpectingVariableDefinition:flag errors:errors]; } #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 - (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)anIndex { return [self itemAtIndex:anIndex referenceFrame:MPElementReferenceFrame]; } - (id)symbolAtIndex:(NSUInteger)anIndex { return [self itemAtIndex:anIndex referenceFrame:MPSymbolReferenceFrame]; } - (id)tokenAtIndex:(NSUInteger)anIndex { return [self itemAtIndex:anIndex 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