Archived
1
This repository has been archived on 2022-08-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
mathpad/MathPad/MPExpression.m
Kim Wittenburg 982b70d7fb Added Change Notifications to MPExpression, MPMutableExpression and MPFunction
Removed -beginEditing and -endEditing
Optimized -fixSymbols
Updated Project File
2014-05-18 01:06:49 +02:00

656 lines
18 KiB
Objective-C

//
// 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 "MPRangePath.h"
#import "NSObject+MPStringTest.h"
#import "NSIndexPath+MPReverseIndexPath.h"
NSString *MPAdditionOperator = @"+";
NSString *MPSubtractionOperator = @"-";
NSString *MPMultiplicationOperator = @"*";
NSString *MPDivisionOperator = @"/";
@interface MPExpression (MPExpressionPrivate)
- (NSInteger)lengthOfSymbol:(id)symbol;
- (void)validateSymbols:(NSArray *)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 = [[NSArray alloc] initWithArray:symbols
copyItems:YES];
[self fixSymbols];
}
return self;
}
#pragma mark Working With the Expression Tree
- (void)fixSymbols
{
NSMutableArray *mutableSymbols = [_symbols mutableCopy];
for (NSInteger index = 0; index < mutableSymbols.count; index++) {
id next = index+1 < mutableSymbols.count ? mutableSymbols[index+1] : nil;
id current = mutableSymbols[index];
if ([current isString]) {
if ([current length] == 0) {
[mutableSymbols removeObjectAtIndex:index];
index--;
} else if ([next isString]) {
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
[mutableSymbols replaceObjectAtIndex:index withObject:new];
[mutableSymbols removeObjectAtIndex:index+1];
index--;
}
} else {
[(MPFunction *)current setParent:self];
}
}
_symbols = [mutableSymbols copy];
}
#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
{
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 isString]) {
return [symbol length];
}
return 1;
}
- (void)validateSymbols:(NSArray *)symbols
{
for (id symbol in symbols) {
if (!([symbol isString]
|| [symbol isKindOfClass:[MPFunction class]])) {
@throw [NSException exceptionWithName:MPIllegalSymbolException
reason:@"Only NSString and MPFunction objects are valid symbols."
userInfo:@{MPIllegalSymbolExceptionSymbolKey: symbol}];
}
}
}
- (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)
+ (NSArray *)operators
{
return @[MPAdditionOperator, MPSubtractionOperator, MPMultiplicationOperator, MPDivisionOperator];
}
#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 the Expression Tree
- (NSUInteger)indexOfSymbol:(id)symbol
{
return [_symbols indexOfObject:symbol];
}
- (id)symbolAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.length == 0) {
return self;
}
id symbol = [self symbolAtIndex:[indexPath indexAtPosition:0]];
if (indexPath.length == 1) {
return symbol;
}
if ([symbol isKindOfClass:[MPFunction class]]) {
return [symbol symbolAtIndexPath:[indexPath indexPathByRemovingFirstIndex]];
}
return nil;
}
#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 isString]) {
startSymbol = [startSymbol substringFromIndex:startOffset];
}
NSUInteger endOffset;
NSUInteger endSymbolIndex;
[self getSplitOffset:&endOffset
inSymbolAtIndex:&endSymbolIndex
forSplitLocation:NSMaxRange(range)];
id endSymbol = _symbols[endSymbolIndex];
if ([endSymbol isString]) {
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 isString]) {
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];
}
- (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
{
NSMutableString *description = [[NSMutableString alloc] init];
NSUInteger index = 0;
for (id symbol in _symbols) {
if ([symbol isString]) {
NSMutableString *correctedSymbol = [[symbol stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
// Prefix operator
if (symbol != _symbols[0]) {
unichar prefix = [correctedSymbol characterAtIndex:0];
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:prefix]) {
[correctedSymbol insertString:@"*"
atIndex:0];
}
}
// Suffix operator
if (symbol != [_symbols lastObject]) {
unichar suffix = [correctedSymbol characterAtIndex:correctedSymbol.length-1];
if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:suffix]) {
[correctedSymbol appendString:@"*"];
}
}
[description appendString:correctedSymbol];
} else if (index > 0 && [_symbols[index-1] isKindOfClass:[MPFunction class]]) {
[description appendFormat:@"*%@", [symbol description]];
} else {
[description appendString:[symbol description]];
}
index++;
}
return description;
}
- (NSUInteger)hash
{
return [_symbols hash];
}
@end
@implementation MPExpression (MPChangeNotificationExtension)
- (void)symbolsChangedInRangePath:(MPRangePath *)rangePath replacementLength:(NSUInteger)length
{
if (!self.parent) {
return;
}
NSUInteger selfIndex = [self.parent indexOfChild:self];
NSIndexPath *newLocation = [rangePath.location indexPathByPrecedingIndex:selfIndex];
MPRangePath *newPath = [[MPRangePath alloc] initWithLocation:newLocation length:rangePath.length];
[self.parent symbolsChangedInRangePath:newPath replacementLength:length];
}
@end
@implementation MPMutableExpression {
NSRange editedRange;
NSRange replacementRange;
}
- (instancetype)initWithSymbols:(NSArray *)symbols
{
[self validateSymbols:symbols];
self = [super initWithSymbols:nil];
if (self) {
editedRange = NSMakeRange(0, 0);
replacementRange = NSMakeRange(0, 0);
_symbols = [[NSMutableArray alloc] initWithArray:symbols
copyItems:YES];
[self fixSymbols];
}
return self;
}
- (void)fixSymbols
{
NSMutableArray *mutableSymbols = (NSMutableArray *)_symbols;
for (NSInteger index = 0; index < mutableSymbols.count; index++) {
id next = index+1 < mutableSymbols.count ? mutableSymbols[index+1] : nil;
id current = mutableSymbols[index];
if ([current isString]) {
if ([current length] == 0) {
[mutableSymbols removeObjectAtIndex:index];
if (index < replacementRange.location) {
replacementRange.location--;
} else if (index < NSMaxRange(replacementRange)-1) {
replacementRange.length--;
} else if (index == NSMaxRange(replacementRange)) {
editedRange.length++;
}
index--;
} else if ([next isString]) {
NSString *new = [NSString stringWithFormat:@"%@%@", current, next];
[mutableSymbols replaceObjectAtIndex:index withObject:new];
[mutableSymbols removeObjectAtIndex:index+1];
if (index < replacementRange.location) {
replacementRange.location--;
} else if (index < NSMaxRange(replacementRange)-1) {
replacementRange.length--;
} else if (index == NSMaxRange(replacementRange)-1) {
editedRange.length++;
}
index--;
}
} else {
[(MPFunction *)current setParent:self];
}
}
}
- (NSArray *)symbols
{
// Return an immutable array:
return [_symbols copy];
}
- (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;
BOOL didSplitStart = NO;
if ([self numberOfSymbols] == 0) {
startIndex = 0;
} else {
[self splitSymbolsAtLocation:range.location
insertionIndex:&startIndex
didSplit:&didSplitStart];
}
// Perform the deletion
NSUInteger endIndex;
BOOL didSplitEnd = NO;
[self splitSymbolsAtLocation:NSMaxRange(range)
insertionIndex:&endIndex
didSplit:&didSplitEnd];
if (range.length > 0) {
NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init];
for (NSUInteger index = startIndex; index < endIndex; index++) {
[indexes addIndex:index];
}
// TODO: Replace with removeObjectsInRange:
[(NSMutableArray *)_symbols removeObjectsAtIndexes:indexes];
}
// Perform the insertion
if (symbols.count > 0) {
NSArray *newSymbols = [[NSArray alloc] initWithArray:symbols copyItems:YES];
[(NSMutableArray *)_symbols replaceObjectsInRange:NSMakeRange(startIndex, 0)
withObjectsFromArray:newSymbols];
}
// Invalidate length and revalidate structure
_length = 0;
NSUInteger editingStart = startIndex - (didSplitStart?1:0);
NSUInteger editingLength = endIndex - startIndex + (didSplitStart?1:0) + (didSplitEnd?1:0);
editedRange = NSMakeRange(editingStart, editingLength);
replacementRange = NSMakeRange(startIndex, symbols.count);
[self fixSymbols];
MPRangePath *changePath = [[MPRangePath alloc] initWithRange:editedRange];
[self symbolsChangedInRangePath:changePath replacementLength:replacementRange.length];
}
- (void)splitSymbolsAtLocation:(NSUInteger)loc
insertionIndex:(out NSUInteger *)insertionIndex
didSplit:(out BOOL *)flag;
{
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) {
NSString *leftPart = [splitSymbol substringToIndex:splitOffset];
NSString *rightPart = [splitSymbol substringFromIndex:splitOffset];
[(NSMutableArray *)_symbols replaceObjectAtIndex:splitSymbolIndex
withObject:leftPart];
splitSymbolIndex++;
[(NSMutableArray *)_symbols insertObject:rightPart
atIndex:splitSymbolIndex];
}
*flag = splitOffset != 0;
*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