diff --git a/MathPad/MPException.h b/MathPad/MPException.h new file mode 100644 index 0000000..eee7df0 --- /dev/null +++ b/MathPad/MPException.h @@ -0,0 +1,15 @@ +// +// MPException.h +// MathPad +// +// Created by Kim Wittenburg on 19.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#ifndef MathPad_MPException_h +#define MathPad_MPException_h + +extern NSString *MPIllegalSymbolException; +extern NSString *MPIllegalSymbolExceptionSymbolKey; + +#endif diff --git a/MathPad/MPException.m b/MathPad/MPException.m new file mode 100644 index 0000000..cf228b8 --- /dev/null +++ b/MathPad/MPException.m @@ -0,0 +1,14 @@ +// +// MPException.m +// MathPad +// +// Created by Kim Wittenburg on 19.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPException.h" +#import "MPException_Private.h" + +NSString *MPIllegalSymbolException = @"MPIllegalSymbolException"; +NSString *MPIllegalSymbolExceptionReason = @"Only NSString and MPFunction objects are valid symbols."; +NSString *MPIllegalSymbolExceptionSymbolKey = @"MPIllegalSymbolExceptionSymbolKey"; \ No newline at end of file diff --git a/MathPad/MPException_Private.h b/MathPad/MPException_Private.h new file mode 100644 index 0000000..fdee303 --- /dev/null +++ b/MathPad/MPException_Private.h @@ -0,0 +1,14 @@ +// +// MPException_Private.h +// MathPad +// +// Created by Kim Wittenburg on 19.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#ifndef MathPad_MPException_Private_h +#define MathPad_MPException_Private_h + +extern NSString *MPIllegalSymbolExceptionReason; + +#endif diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m new file mode 100644 index 0000000..a4befe4 --- /dev/null +++ b/MathPad/MPExpression.m @@ -0,0 +1,540 @@ +// +// 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 "MPException_Private.h" + +@interface MPExpression (MPExpressionPrivate) + +- (NSInteger)lengthOfSymbol:(id)symbol; +- (void)validateSymbols:(NSArray *)symbols; +- (NSArray *)repairedSymbols:(NSArray *)symbols; // Merges subsequent strings +- (void)repairSymbols:(NSMutableArray *)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 = [self repairedSymbols:symbols]; + _symbols = [[NSArray alloc] initWithArray:symbols + copyItems:YES]; + } + return self; +} + +#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 +{ + // TODO: Test Copying + 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 isKindOfClass:[NSString class]]) { + return [symbol length]; + } + return 1; +} + +// TODO: Deal with subsequent strings +- (void)validateSymbols:(NSArray *)symbols +{ + for (id symbol in symbols) { + if (!([symbol isKindOfClass:[NSString class]] + || [symbol isKindOfClass:[MPFunction class]])) { + @throw [NSException exceptionWithName:MPIllegalSymbolException + reason:MPIllegalSymbolExceptionReason + userInfo:@{MPIllegalSymbolExceptionSymbolKey: symbol}]; + } + } +} + +- (NSArray *)repairedSymbols:(NSArray *)symbols +{ + NSMutableArray *mutableSymbols = [symbols mutableCopy]; + [self repairSymbols:mutableSymbols]; + return [mutableSymbols copy]; +} + +- (void)repairSymbols:(NSMutableArray *)symbols +{ + for (NSInteger index = 1; index < symbols.count; index++) { + id last = symbols[index-1]; + id current = symbols[index]; + if ([last isKindOfClass:[NSString class]] && [current isKindOfClass:[NSString class]]) { + NSMutableString *new = [NSMutableString stringWithFormat:@"%@%@", last, current]; + [symbols replaceObjectAtIndex:index-1 withObject:new]; + [symbols removeObjectAtIndex:index]; + index--; + } + } +} + +- (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) + +#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 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 isKindOfClass:[NSString class]]) { + startSymbol = [startSymbol substringFromIndex:startOffset]; + } + NSUInteger endOffset; + NSUInteger endSymbolIndex; + [self getSplitOffset:&endOffset inSymbolAtIndex:&endSymbolIndex forSplitLocation:NSMaxRange(range)]; + id endSymbol = _symbols[endSymbolIndex]; + if ([endSymbol isKindOfClass:[NSString class]]) { + 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 isKindOfClass:[NSString class]]) { + 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]; +} + +// TODO: prefix and suffix operators + +- (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 +{ +#warning Unimplemented Method + /* + Join the symbols regarding pre- and suffix operators. If there is no operator a * should be inserted + */ + return [super description]; +} + +- (NSUInteger)hash +{ + return [_symbols hash]; +} + +@end + +@interface MPMutableExpression (MPMutableExpressionPrivate) + +- (NSArray *)makeSymbols:(NSArray *)symbols mutable:(BOOL)mutable copySymbols:(BOOL)copy; + +@end + +@implementation MPMutableExpression (MPMutableExpressionPrivate) + +- (NSArray *)makeSymbols:(NSArray *)symbols mutable:(BOOL)mutable copySymbols:(BOOL)copy +{ + NSMutableArray *newSymbols = [[NSMutableArray alloc] initWithCapacity:symbols.count]; + for (id symbol in symbols) { + id newSymbol; + if ([symbol isKindOfClass:[NSString class]]) { + if (mutable) { + newSymbol = [symbol mutableCopy]; + } else { + newSymbol = [symbol copy]; + } + } else { + if (copy) { + newSymbol = [symbol copy]; + } else { + newSymbol = symbol; + } + } + [newSymbols addObject:newSymbol]; + } + return newSymbols; +} + +@end + +// A mutable expression uses NSMutableString objects instead of NSString objects +@implementation MPMutableExpression + +- (instancetype)initWithSymbols:(NSArray *)symbols +{ + [self validateSymbols:symbols]; + self = [super initWithSymbols:nil]; + if (self) { + symbols = [self repairedSymbols:symbols]; + _symbols = [self makeSymbols:symbols + mutable:YES + copySymbols:YES]; + } + return self; +} + +- (id)symbolAtIndex:(NSUInteger)index +{ + id symbol = _symbols[index]; + if ([symbol isKindOfClass:[NSString class]]) { + return [symbol copy]; + } + return symbol; +} + +- (NSArray *)symbols +{ + return [self makeSymbols:_symbols mutable:NO copySymbols:NO]; +} + +- (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; + [self splitSymbolsAtLocation:range.location insertionIndex:&startIndex]; + + // Perform the deletion + if (range.length > 0) { + NSUInteger endIndex; + [self splitSymbolsAtLocation:NSMaxRange(range) insertionIndex:&endIndex]; + NSMutableIndexSet *indexes = [[NSMutableIndexSet alloc] init]; + for (NSUInteger index = startIndex; index < endIndex; index++) { + [indexes addIndex:index]; + } + [(NSMutableArray *)_symbols removeObjectsAtIndexes:indexes]; + } + + // Perform the insertion + if (symbols.count > 0) { + NSArray *newSymbols = [self makeSymbols:symbols mutable:YES copySymbols:YES]; + [(NSMutableArray *)_symbols replaceObjectsInRange:NSMakeRange(startIndex, 0) withObjectsFromArray:newSymbols]; + } + + // Revalidate structure and invalidate length + [self repairSymbols:(NSMutableArray *)_symbols]; + _length = 0; +} + +- (void)splitSymbolsAtLocation:(NSUInteger)loc insertionIndex:(out NSUInteger *)insertionIndex +{ + 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) { + NSMutableString *leftPart = [[splitSymbol substringToIndex:splitOffset] mutableCopy]; + NSMutableString *rightPart = [[splitSymbol substringFromIndex:splitOffset] mutableCopy]; + [(NSMutableArray *)_symbols replaceObjectAtIndex:splitSymbolIndex withObject:leftPart]; + splitSymbolIndex++; + [(NSMutableArray *)_symbols insertObject:rightPart atIndex:splitSymbolIndex]; + } + *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 \ No newline at end of file diff --git a/MathPad/MPFunction.m b/MathPad/MPFunction.m new file mode 100644 index 0000000..5424a0c --- /dev/null +++ b/MathPad/MPFunction.m @@ -0,0 +1,135 @@ +// +// MPFunction.m +// MathPad +// +// Created by Kim Wittenburg on 18.04.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunction.h" + +@implementation MPFunction + +#pragma mark Creation Methods + +- (instancetype)init +{ + self = [super init]; + if (self) { + } + return self; +} + +#pragma mark Children + +- (NSUInteger)numberOfChildren +{ + return 0; +} + +- (MPExpression *)childAtIndex:(NSUInteger)index +{ + return nil; +} + +- (void)setChild:(MPExpression *)child atIndex:(NSUInteger)index +{} + +#pragma mark Evaluating Functions + +- (double)doubleValue +{ + return 0; +} + +#pragma mark Working With Functions + +- (BOOL)isEqual:(id)object +{ + if (self == object) { + return YES; + } + if (object == nil) { + return NO; + } + if (![object isKindOfClass:[MPFunction class]]) { + return NO; + } + return [self isEqualToFunction:(MPFunction *)object]; +} + +- (BOOL)isEqualToFunction:(MPFunction *)aFunction +{ + return [aFunction isMemberOfClass:[MPFunction class]] && [self isMemberOfClass:[MPFunction class]]; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + return [[MPFunction allocWithZone:zone] init]; +} + +#pragma mark - NSCoding + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super init]; + if (self) { + + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{} + +@end + +@implementation MPFunction (MPFunctionExtensionMethods) + +#pragma mark Children + +- (NSArray *)children +{ + NSUInteger childCount = [self numberOfChildren]; + NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:childCount]; + for (NSInteger i = 0; i < childCount; i++) { + [children addObject:[self childAtIndex:i]]; + } + return [children copy]; +} + +#pragma mark Evaluating Functions + +- (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]; +} + +- (NSString *)description +{ + return @"[]"; +} + +- (NSUInteger)hash +{ + return 0; +} + +@end \ No newline at end of file