// // MPExpression.m // MathPad // // Created by Kim Wittenburg on 10.08.14. // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // #import "MPExpression.h" #import "MPExpression.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 = @"Illegal Element Exception"; NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey"; @interface MPExpression () { NSMutableArray * _elements; } /*! @method tokens @abstract Private method. Returns an array containing all tokens from the receiver. @return All items in the MPTokenReferenceFrame. */ - (NSArray *)tokens; /*! @method validateElements: @abstract Private method. Checks whether all objects in the specified array are valid expression elements. @discussion If an object is not valid a MPIllegalElementException is raised. @param elements The array of objects to be validated. */ - (void)validateElements:(NSArray *)elements; /*! @method fixElements @abstract Private method. Restores consistency in the receiver after a change was made. */ - (void)fixElements; /*! @method _replaceSymbolsInRange:withElements: @abstract Private method. Replaces the symbols in the specified range with the elements from the elements array. @discussion This is the most primitive mutation method of the @c MPExpression class. @param range The range of symbols to be replaced. The range is specified in the symbol reference frame. If the range exceeds the receiver's bounds a NSRangeException is raised. @param elements The elements that should replace the symbols in the specified range. */ - (void)_replaceSymbolsInRange:(NSRange)range withElements:(NSArray *)elements; /*! @method _splitElementsAtLocation:insertionIndex: @abstract Splits the receiver's elements at the specified location. @discussion The split location can be inside a string element. In that case the string is replaced with two other smaller strings. Splitting the elements destroys the receiver's integrity. The receiver of this message must also receive a -fixElements message soon afterwards. @param location The index where to split the elements. Specified in the symbol reference frame. @param insertionIndex Splitting elements may shift the indexes of the following elements. This parameter is set to the index (specified in the element reference frame) that should be used to insert a new element at the location where the elements were previously splitted. @return @c YES if a string element was split into two smaller strings, NO if the split location corresponds to a location between two elements. */ - (BOOL)_splitElementsAtLocation:(NSUInteger)location insertionIndex:(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 offsetInElement; NSUInteger elementIndex = [self convertIndex:anIndex fromReferenceFrame:MPSymbolReferenceFrame toReferenceFrame:MPElementReferenceFrame offset:&offsetInElement]; id element = [self elementAtIndex:elementIndex]; if ([element isFunction]) { return element; } else { return [((NSString *)element) substringWithRange:NSMakeRange(offsetInElement, 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:(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; }]; 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 += token.range.length; } --tokenIndex; 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)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 changedElementsInRangePath:[[MPRangePath alloc] initWithRange:_editedRange] replacementLength:_replacementLength]; } - (BOOL)_splitElementsAtLocation:(NSUInteger)location insertionIndex:(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; } - (void)changedElementsInRangePath:(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]; } - (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 - (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 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