diff --git a/MathPad/MPEvaluationContext.h b/MathPad/MPEvaluationContext.h index 1452d77..f5acfb2 100644 --- a/MathPad/MPEvaluationContext.h +++ b/MathPad/MPEvaluationContext.h @@ -15,9 +15,10 @@ - (void)push; - (void)pop; -- (void)bindValue:(NSDecimalNumber *)value toName:(NSString *)variableName; -- (void)unbindVariableName:(NSString *)variableName; +- (void)defineVariable:(NSString *)variable withValue:(id)value; +- (void)undefineVariable:(NSString *)variable; +- (BOOL)isVariableDefined:(NSString *)variable; -- (NSDecimalNumber *)valueForVariableName:(NSString *)variableName; +- (NSDecimalNumber *)valueForVariable:(NSString *)variable; @end diff --git a/MathPad/MPEvaluationContext.m b/MathPad/MPEvaluationContext.m index 6e6d0e1..38b9a31 100644 --- a/MathPad/MPEvaluationContext.m +++ b/MathPad/MPEvaluationContext.m @@ -45,22 +45,27 @@ static MPEvaluationContext *sharedContext; [self.stack removeLastObject]; } -- (void)bindValue:(NSDecimalNumber *)value toName:(NSString *)variableName +- (void)defineVariable:(NSString *)variable withValue:(id)value { NSMutableDictionary *currentBindings = self.stack.lastObject; - currentBindings[variableName] = value; + currentBindings[variable] = value; } -- (void)unbindVariableName:(NSString *)variableName +- (void)undefineVariable:(NSString *)variable { NSMutableDictionary *currentBindings = self.stack.lastObject; - [currentBindings removeObjectForKey:variableName]; + [currentBindings removeObjectForKey:variable]; } -- (NSDecimalNumber *)valueForVariableName:(NSString *)variableName +- (NSDecimalNumber *)valueForVariable:(NSString *)variable { NSMutableDictionary *currentBindings = self.stack.lastObject; - return currentBindings[variableName]; + return currentBindings[variable]; +} + +- (BOOL)isVariableDefined:(NSString *)variable +{ + return [self valueForVariable:variable] != nil; } @end diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m index 3ab7318..9a8ff35 100644 --- a/MathPad/MPExpression.m +++ b/MathPad/MPExpression.m @@ -258,7 +258,9 @@ - (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error { - return [self.evaluator evaluateWithError:error]; + MPTerm *term = [self.evaluator parseExpectingVariable:NO + error:error]; + return [term evaluate]; } @synthesize evaluator = _evaluator; diff --git a/MathPad/MPExpressionEvaluator.h b/MathPad/MPExpressionEvaluator.h index f901c1c..185999b 100644 --- a/MathPad/MPExpressionEvaluator.h +++ b/MathPad/MPExpressionEvaluator.h @@ -8,7 +8,8 @@ #import #import "MPExpression.h" -#import "MPElementParser.h" +#import "MPExpressionTokenizer.h" +#import "MPTerm.h" @class MPExpressionEvaluator, MPExpression, MPParsedElementOld; @@ -18,10 +19,14 @@ - (instancetype)initWithExpression:(MPExpression *)expression; @property (readonly, nonatomic, weak) MPExpression *expression; -#pragma mark Evaluating Expressions -@property (readonly, nonatomic, strong) NSString *definedVariable; +@property (nonatomic, strong) MPExpressionTokenizer *lexer; -- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error; -- (NSDecimalNumber *)evaluateVariableDefinition:(BOOL)flag error:(MPParseError *__autoreleasing *)error; +- (void)expressionDidChangeInRange:(NSRange)range + replacementLength:(NSUInteger)replacementLength; + +#pragma mark Evaluating Expressions +@property (readonly, nonatomic, copy) NSString *definedVariable; + +- (MPTerm *)parseExpectingVariable:(BOOL)flag error:(MPParseError *__autoreleasing *)error; @end diff --git a/MathPad/MPExpressionEvaluator.m b/MathPad/MPExpressionEvaluator.m index 7d07ced..410921e 100644 --- a/MathPad/MPExpressionEvaluator.m +++ b/MathPad/MPExpressionEvaluator.m @@ -9,105 +9,236 @@ #import "MPExpressionEvaluator.h" #import "MPExpression.h" #import "MPFunction.h" -#import "MPParsedFactor.h" -#import "MPFunction+MPParsedFactor.h" #import "MPMathRules.h" +#import "MPToken.h" +#import "MPFunction+MPToken.h" +#import "MPTokenStream.h" + +#import "MPEvaluationContext.h" + + @interface MPExpressionEvaluator () -@property (readwrite, nonatomic, strong) NSString *definedVariable; +@property (readwrite, nonatomic, copy) NSString *definedVariable; +@property (nonatomic, strong) NSArray *tokens; + +- (void)setError:(MPParseError *)error; @end @implementation MPExpressionEvaluator { - MPElementParser *parser; + MPParseError *__autoreleasing *_error; + MPTokenStream *tokenStream; } + - (id)initWithExpression:(MPExpression *)expression { self = [super init]; if (self) { _expression = expression; - parser = [[MPElementParser alloc] init]; } return self; } -#pragma mark Evaluating Expressions -- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error +@synthesize lexer = _lexer; + +- (void)setLexer:(MPExpressionTokenizer *)lexer { - return [self evaluateVariableDefinition:NO error:error]; + _lexer = lexer; + self.tokens = nil; } -- (NSDecimalNumber *)evaluateVariableDefinition:(BOOL)flag error:(MPParseError *__autoreleasing *)error +- (MPExpressionTokenizer *)lexer { - // Empty Expression - if (self.expression.numberOfElements == 0) { - *error = MPParseError(NSMakeRange(0, 0), @"Expected Expression"); + if (!_lexer) { + _lexer = [[MPExpressionTokenizer alloc] init]; + } + return _lexer; +} + +- (NSArray *)tokens +{ + if (!_tokens) { + _tokens = [self.lexer tokenizeExpression:self.expression]; + } + return _tokens; +} + +- (void)expressionDidChangeInRange:(NSRange)range + replacementLength:(NSUInteger)replacementLength +{ + self.tokens = nil; +} + +#pragma mark Evaluating Expressions +- (void)setError:(MPParseError *)error +{ + if (_error) { + error.pathToExpression = self.expression.indexPath; + *_error = error; + } +} + +- (MPTerm *)parseExpectingVariable:(BOOL)flag + error:(MPParseError *__autoreleasing *)error +{ + _error = error; + tokenStream = [[MPTokenStream alloc] initWithTokens:self.tokens]; + if (!tokenStream.hasMoreTokens) { + self.error = MPParseError(NSMakeRange(0, 0), @"Empty Expression"); return nil; } - - NSMutableArray *products = [[NSMutableArray alloc] init]; - MPParsedProduct *currentProduct = nil; - for (NSUInteger elementIndex = 0; elementIndex < self.expression.numberOfElements; elementIndex++) { + // Variable Definition + if (flag) { + MPToken *variableToken = [tokenStream nextToken]; + MPToken *equalsToken = [tokenStream nextTokenOfType:MPEqualsToken]; - id element = [self.expression elementAtIndex:elementIndex]; - - if ([element isString]) { - id nextFactor = nil; - if (elementIndex < self.expression.numberOfElements - 1) { - MPFunction *nextFunction = (MPFunction *)[self.expression elementAtIndex:elementIndex+1]; - nextFactor = nextFunction; - } - - NSArray *newProducts; - if (elementIndex == 0 && flag) { - NSString *definedVariable; - newProducts = [parser parseElement:(NSString *)element - previousProduct:currentProduct - nextFactor:nextFactor - definesVariable:YES - definedVariable:&definedVariable - error:error]; - self.definedVariable = definedVariable; - } else { - newProducts = [parser parseElement:(NSString *)element - previousProduct:currentProduct - nextFactor:nextFactor - error:error]; - } - if (!newProducts) { - return nil; - } - - for (NSUInteger productIndex = 0; productIndex < newProducts.count-1; productIndex++) { - [products addObject:newProducts[productIndex]]; - } - currentProduct = newProducts.lastObject; - - elementIndex++; - } else { - if (!currentProduct) { - currentProduct = [[MPParsedProduct alloc] init]; - } else if ([MPMathRules sharedRules].allowsImplicitMultiplication) { - [currentProduct addFactor:(MPFunction *)element]; - } else { - *error = MPParseError(NSMakeRange(((MPFunction *)element).range.location, 0), @"Implicit Multiplication not allowed"); - return nil; - } - } - } - - [products addObject:currentProduct]; - - NSDecimalNumber *value = [NSDecimalNumber zero]; - for (MPParsedProduct *product in products) { - NSDecimalNumber *productValue = [product evaluateWithError:error]; - if (!productValue) { + if (variableToken.tokenType != MPVariableToken) { + self.error = MPParseError(variableToken.range, @"Expected Variable Definition."); + return nil; + } + if (equalsToken.tokenType != MPEqualsToken) { + self.error = MPParseError(equalsToken.range, @"Expected \"=\"."); + return nil; + } + self.definedVariable = variableToken.stringValue; + } + + BOOL isAtBeginning = YES; + NSMutableArray *summands = [[NSMutableArray alloc] init]; + NSMutableArray *currentSummand = [[NSMutableArray alloc] init]; + + while (tokenStream.hasMoreTokens) { + MPToken *multiplicationToken = [tokenStream nextTokenOfType:MPMultiplicationSymbolToken]; + MPToken *operatorToken = [tokenStream nextTokenOfType:MPOperatorListToken]; + NSRange valueRange; + MPTerm *value = [self nextValue:&valueRange]; + + if (multiplicationToken) { + if (isAtBeginning) { + self.error = MPParseError(multiplicationToken.range, @"Unexpected Symbol. Expected Number."); + return nil; + } + if (operatorToken) { + if (operatorToken.numberOfOperators > [[MPMathRules sharedRules] maximumOperatorChainLengthInMultiplication]) { + self.error = MPParseError(operatorToken.range, @"Too many operators in multiplication"); + return nil; + } + [currentSummand addObject:[[MPTerm alloc] initWithNumber:operatorToken.operatorValue]]; + } + if (value) { + [currentSummand addObject:value]; + } else { + return nil; + } + } else { + if (operatorToken) { + if (operatorToken.numberOfOperators > [[MPMathRules sharedRules] maximumOperatorChainLength]) { + self.error = MPParseError(operatorToken.range, @"Too many operators."); + return nil; + } + [summands addObject:[[MPTerm alloc] initWithFactors:currentSummand.copy]]; + currentSummand = [[NSMutableArray alloc] init]; + [currentSummand addObject:[[MPTerm alloc] initWithNumber:operatorToken.operatorValue]]; + if (value) { + [currentSummand addObject:value]; + } else { + return nil; + } + } else if (!value) { + return nil; + } else if (isAtBeginning || [[MPMathRules sharedRules] allowsImplicitMultiplication]) { + [currentSummand addObject:value]; + } else { + self.error = MPParseError(NSMakeRange(valueRange.location, 0), @"Implicit Multiplication not allowed here"); + return nil; + } + } + + isAtBeginning = NO; + } + [summands addObject:[[MPTerm alloc] initWithFactors:currentSummand.copy]]; + return [[MPTerm alloc] initWithSummands:summands.copy]; +} + +- (MPTerm *)nextValue:(NSRangePointer)range +{ + MPToken *token = [tokenStream nextToken]; + if (range) { + *range = token.range; + } + switch (token.tokenType) { + case MPNumberToken: + { + return [[MPTerm alloc] initWithNumber:token.number]; + } + + case MPVariableToken: + { + if(![[MPEvaluationContext sharedContext] isVariableDefined:token.variable]) { + self.error = MPParseError(token.range, @"Undefined Variable"); + return nil; + } + return [[MPTerm alloc] initWithVariable:token.variable]; + } + + case MPGenericFunctionToken: + { + return [((MPFunction *)token) parseWithError:_error]; + } + + case MPSinToken: + { + NSRange sinTermRange; + MPTerm *sinTerm = [self nextValue:&sinTermRange]; + if (!sinTerm) { + if (range) { + *range = NSUnionRange(token.range, sinTermRange); + } + return nil; + } + return [[MPTerm alloc] initWithSinOfTerm:sinTerm]; + } + + case MPCosToken: + { + NSRange cosTermRange; + MPTerm *cosTerm = [self nextValue:&cosTermRange]; + if (!cosTerm) { + if (range) { + *range = NSUnionRange(token.range, cosTermRange); + } + return nil; + } + return [[MPTerm alloc] initWithSinOfTerm:cosTerm]; + } + + case MPTanToken: + { + NSRange tanTermRange; + MPTerm *tanTerm = [self nextValue:&tanTermRange]; + if (!tanTerm) { + if (range) { + *range = NSUnionRange(token.range, tanTermRange); + } + return nil; + } + return [[MPTerm alloc] initWithTanOfTerm:tanTerm]; + } + + case MPEOFToken: + { + self.error = MPParseError(token.range, @"Unexpected End. Expected Number."); + return nil; + } + + default: + { + self.error = MPParseError(token.range, @"Unexpected Symbol. Expected Number."); return nil; } - value = [value decimalNumberByAdding:productValue]; } - return value; } @end diff --git a/MathPad/MPExpressionTokenizer.h b/MathPad/MPExpressionTokenizer.h new file mode 100644 index 0000000..8b8b012 --- /dev/null +++ b/MathPad/MPExpressionTokenizer.h @@ -0,0 +1,16 @@ +// +// MPExpressionLexer.h +// MathPad +// +// Created by Kim Wittenburg on 19.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import +#import "MPExpression.h" + +@interface MPExpressionTokenizer : NSObject + +- (NSArray *)tokenizeExpression:(MPExpression *)expression; // Returns MPToken's + +@end diff --git a/MathPad/MPExpressionTokenizer.m b/MathPad/MPExpressionTokenizer.m new file mode 100644 index 0000000..126f3e0 --- /dev/null +++ b/MathPad/MPExpressionTokenizer.m @@ -0,0 +1,115 @@ +// +// MPExpressionLexer.m +// MathPad +// +// Created by Kim Wittenburg on 19.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPExpressionTokenizer.h" +#import "MPToken.h" +#import "MPFunction+MPToken.h" + +#import "NSRegularExpression+MPParsingAdditions.h" + + +#define MPRangeExists(range) (range.location != NSNotFound) + + +@implementation MPExpressionTokenizer + +- (NSArray *)tokenizeExpression:(MPExpression *)expression +{ + NSMutableArray *tokens = [[NSMutableArray alloc] init]; + for (NSUInteger index = 0; index < expression.numberOfElements; index++) { + id element = [expression elementAtIndex:index]; + if ([element isFunction]) { + [tokens addObject:element]; + } else { + [tokens addObjectsFromArray:[self tokenizeElement:(NSString *)element]]; + } + } + return tokens; +} + +- (NSArray *)tokenizeElement:(NSString *)element +{ + NSUInteger lexLocation = 0; + + NSString *decimalSeparator = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]]; + NSString *regexStringFormat = @"\\A(?:" + @"(\\*)|" + @"([+-](?:\\s*[+-])*)|" + @"((?:\\d+(?:%@\\d+)?)|(?:\\s%@\\d+))|" + @"(sin)|" + @"(cos)|" + @"(tan)|" + @"([A-Za-z])|" + @"(=)|" + @"(\\s)" + @")"; + NSString *regexString = [NSString stringWithFormat:regexStringFormat, decimalSeparator, decimalSeparator]; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString + options:0 + error:NULL]; + NSMutableArray *tokens = [[NSMutableArray alloc] init]; + while (lexLocation < element.length) { + NSTextCheckingResult *match = [regex firstMatchInString:element fromIndex:lexLocation]; + + NSRange range = NSMakeRange(lexLocation, 1); + MPTokenType tokenType = MPUnidentifiedToken; + if (match) { + NSRange multiplicationSymbolRange = [match rangeAtIndex:1]; + NSRange operatorRange = [match rangeAtIndex:2]; + NSRange numberRange = [match rangeAtIndex:3]; + NSRange sinRange = [match rangeAtIndex:4]; + NSRange cosRange = [match rangeAtIndex:5]; + NSRange tanRange = [match rangeAtIndex:6]; + NSRange variableRange = [match rangeAtIndex:7]; + NSRange equalsRange = [match rangeAtIndex:8]; + NSRange whitespaceRange = [match rangeAtIndex:9]; + + if (MPRangeExists(multiplicationSymbolRange)) { + range = multiplicationSymbolRange; + tokenType = MPMultiplicationSymbolToken; + } else if (MPRangeExists(operatorRange)) { + range = operatorRange; + tokenType = MPOperatorListToken; + } else if (MPRangeExists(numberRange)) { + range = numberRange; + tokenType = MPNumberToken; + } else if (MPRangeExists(sinRange)) { + range = sinRange; + tokenType = MPSinToken; + } else if (MPRangeExists(cosRange)) { + range = cosRange; + tokenType = MPCosToken; + } else if (MPRangeExists(tanRange)) { + range = tanRange; + tokenType = MPTanToken; + } else if (MPRangeExists(variableRange)) { + range = variableRange; + tokenType = MPVariableToken; + } else if (MPRangeExists(equalsRange)) { + range = equalsRange; + tokenType = MPEqualsToken; + } else if (MPRangeExists(whitespaceRange)) { + range = whitespaceRange; + tokenType = MPWhitespaceToken; + } else { + // Should not get here + range = NSMakeRange(lexLocation, 1); + tokenType = MPUnidentifiedToken; + } + } + + lexLocation = NSMaxRange(range); + [tokens addObject:[[MPToken alloc] initWithTokenType:tokenType + range:range + inString:element]]; + } + + return tokens; +} + +@end diff --git a/MathPad/MPFunction+MPToken.h b/MathPad/MPFunction+MPToken.h new file mode 100644 index 0000000..44e45fc --- /dev/null +++ b/MathPad/MPFunction+MPToken.h @@ -0,0 +1,14 @@ +// +// MPFunction+MPToken.h +// MathPad +// +// Created by Kim Wittenburg on 19.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import +#import "MPToken.h" + +@interface MPFunction (MPToken) + +@end diff --git a/MathPad/MPFunction+MPToken.m b/MathPad/MPFunction+MPToken.m new file mode 100644 index 0000000..94a939a --- /dev/null +++ b/MathPad/MPFunction+MPToken.m @@ -0,0 +1,34 @@ +// +// MPFunction+MPToken.m +// MathPad +// +// Created by Kim Wittenburg on 19.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunction+MPToken.h" + +@implementation MPFunction (MPToken) + +- (MPTokenType)tokenType +{ + return MPGenericFunctionToken; +} + +- (NSRange)range +{ + NSUInteger selfIndex = [self.parent indexOfElement:self]; + return NSMakeRange([self.parent locationOfElementAtIndex:selfIndex], 1); +} + +- (BOOL)exists +{ + return YES; +} + +- (NSString *)stringValue +{ + return [self description]; +} + +@end diff --git a/MathPad/MPFunction.h b/MathPad/MPFunction.h index 1cbbc2b..eccb0b4 100644 --- a/MathPad/MPFunction.h +++ b/MathPad/MPFunction.h @@ -9,11 +9,14 @@ @import Foundation; #import "MPExpressionElement.h" #import "MPParseError.h" +#import "MPTerm.h" @class MPFunction, MPExpression, MPRangePath; @interface MPFunction : NSObject ++ (NSString *)localizedFunctionName; // Override + #pragma mark Creation Methods - (instancetype)init; @@ -38,7 +41,8 @@ - (id)elementAtIndexPath:(NSIndexPath *)indexPath; #pragma mark Evaluating Functions -- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error; + +- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error; // Override #pragma mark Messages - (void)didChangeElementsInRangePath:(MPRangePath *)rangePath @@ -52,4 +56,6 @@ - (NSString *)description; // Should be overridden - (NSUInteger)hash;// Override +- (id)copyWithZone:(NSZone *)zone; // Override + @end diff --git a/MathPad/MPFunction.m b/MathPad/MPFunction.m index 141c56b..7e28658 100644 --- a/MathPad/MPFunction.m +++ b/MathPad/MPFunction.m @@ -14,6 +14,11 @@ @implementation MPFunction ++ (NSString *)localizedFunctionName +{ + return NSLocalizedString(@"Function", @"Name of Generic Function."); +} + #pragma mark Creation Methods - (instancetype)init { @@ -82,9 +87,9 @@ } #pragma mark Evaluating Functions -- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error +- (MPTerm *)parseWithError:(MPParseError *__autoreleasing *)error { - return [NSDecimalNumber zero]; + return nil; } #pragma mark Notifications diff --git a/MathPad/MPParseError.h b/MathPad/MPParseError.h index 4c60175..f01a328 100644 --- a/MathPad/MPParseError.h +++ b/MathPad/MPParseError.h @@ -6,8 +6,12 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // +// TODO: Rename to MPSyntaxError + #import +@class MPRangePath; + #define MPParseError(range,key) [[MPParseError alloc] initWithErrorRange:range errorMessageKey:key] @interface MPParseError : NSObject @@ -23,4 +27,6 @@ @property (nonatomic) NSRange errorRange; @property (nonatomic, copy) NSString *localizedErrorMessage; +- (MPRangePath *)rangePath; + @end diff --git a/MathPad/MPParseError.m b/MathPad/MPParseError.m index 1ca6f72..c6a9db3 100644 --- a/MathPad/MPParseError.m +++ b/MathPad/MPParseError.m @@ -8,6 +8,9 @@ #import "MPParseError.h" +#import "MPRangePath.h" +#import "NSIndexPath+MPAdditions.h" + @implementation MPParseError - (instancetype)initWithErrorRange:(NSRange)errorRange errorMessageKey:(NSString *)key @@ -30,6 +33,14 @@ return self; } +- (MPRangePath *)rangePath +{ + NSIndexPath *location = self.pathToExpression; + location = [location indexPathByPreceedingIndex:self.errorRange.location]; + MPRangePath *rangePath = [MPRangePath rangePathWithLocation:location length:self.errorRange.length]; + return rangePath; +} + - (NSString *)description { return [NSString stringWithFormat:@"MPParseError", self.errorRange.location, self.errorRange.length, self.localizedErrorMessage]; diff --git a/MathPad/MPTerm.h b/MathPad/MPTerm.h new file mode 100644 index 0000000..6b69acd --- /dev/null +++ b/MathPad/MPTerm.h @@ -0,0 +1,26 @@ +// +// MPTerm.h +// MathPad +// +// Created by Kim Wittenburg on 27.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +@import Foundation; + +@interface MPTerm : NSObject + +- (instancetype)initWithBlock:(NSDecimalNumber *(^)())block; // Designated Initializer + +- (instancetype)initWithNumber:(NSDecimalNumber *)number; +- (instancetype)initWithSummands:(NSArray *)summands; // array of MPTerms +- (instancetype)initWithFactors:(NSArray *)factors; // array of MPTerms +- (instancetype)initWithVariable:(NSString *)variable; + +- (instancetype)initWithSinOfTerm:(MPTerm *)term; +- (instancetype)initWithCosOfTerm:(MPTerm *)term; +- (instancetype)initWithTanOfTerm:(MPTerm *)term; + +- (NSDecimalNumber *)evaluate; + +@end diff --git a/MathPad/MPTerm.m b/MathPad/MPTerm.m new file mode 100644 index 0000000..20e9d9e --- /dev/null +++ b/MathPad/MPTerm.m @@ -0,0 +1,94 @@ +// +// MPTerm.m +// MathPad +// +// Created by Kim Wittenburg on 27.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" +#import "MPEvaluationContext.h" + +@interface MPTerm () + +@property (nonatomic, copy) NSDecimalNumber *(^block)(); + +@end + +@implementation MPTerm + +- (instancetype)initWithBlock:(NSDecimalNumber *(^)())block +{ + self = [super init]; + if (self) { + self.block = block; + } + return self; +} + +- (instancetype)initWithNumber:(NSDecimalNumber *)number +{ + return [self initWithBlock:^NSDecimalNumber *{ + return number; + }]; +} + +- (instancetype)initWithSummands:(NSArray *)summands +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *value = [NSDecimalNumber zero]; + for (MPTerm *term in summands) { + value = [value decimalNumberByAdding:[term evaluate]]; + } + return value; + }]; +} + +- (instancetype)initWithFactors:(NSArray *)factors +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *value = [NSDecimalNumber one]; + for (MPTerm *term in factors) { + value = [value decimalNumberByMultiplyingBy:[term evaluate]]; + } + return value; + }]; +} + +- (instancetype)initWithVariable:(NSString *)variable +{ + return [self initWithBlock:^NSDecimalNumber *{ + return [[MPEvaluationContext sharedContext] valueForVariable:variable]; + }]; +} + +- (instancetype)initWithSinOfTerm:(MPTerm *)term +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *sinValue = [term evaluate]; + return [[NSDecimalNumber alloc] initWithDouble:sin(sinValue.doubleValue)]; + }]; +} + +- (instancetype)initWithCosOfTerm:(MPTerm *)term +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *cosValue = [term evaluate]; + return [[NSDecimalNumber alloc] initWithDouble:cos(cosValue.doubleValue)]; + }]; +} + +- (instancetype)initWithTanOfTerm:(MPTerm *)term +{ + return [self initWithBlock:^NSDecimalNumber *{ + NSDecimalNumber *tanValue = [term evaluate]; + return [[NSDecimalNumber alloc] initWithDouble:tan(tanValue.doubleValue)]; + }]; +} + +- (NSDecimalNumber *)evaluate +{ + return self.block(); +} + +@end diff --git a/MathPad/MPToken.h b/MathPad/MPToken.h new file mode 100644 index 0000000..fbeb113 --- /dev/null +++ b/MathPad/MPToken.h @@ -0,0 +1,60 @@ +// +// MPToken.h +// MathPad +// +// Created by Kim Wittenburg on 19.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import + +typedef NS_ENUM(NSUInteger, MPTokenType) { + MPEOFToken = 0, + MPMultiplicationSymbolToken, + MPOperatorListToken, + MPSinToken, + MPCosToken, + MPTanToken, + MPNumberToken, + MPVariableToken, + MPEqualsToken, + MPGenericFunctionToken, + + MPWhitespaceToken, + MPUnidentifiedToken, + + MPCompoundToken +}; + + +@protocol MPToken + +- (MPTokenType)tokenType; + +- (NSRange)range; +- (NSString *)stringValue; + +@end + + +@interface MPToken : NSObject + +- (instancetype)initEOFTokenAtLocation:(NSUInteger)eofLocation; +- (instancetype)initWithRange:(NSRange)range inString:(NSString *)input; +- (instancetype)initWithTokenType:(MPTokenType)tokenType + range:(NSRange)range + inString:(NSString *)input; + +@end + +@interface MPToken (MPTokenExtension) +// Methods are only available for respective token type + +- (NSUInteger)numberOfOperators; +- (NSDecimalNumber *)operatorValue; + +- (NSDecimalNumber *)number; + +- (NSString *)variable; + +@end \ No newline at end of file diff --git a/MathPad/MPToken.m b/MathPad/MPToken.m new file mode 100644 index 0000000..512684f --- /dev/null +++ b/MathPad/MPToken.m @@ -0,0 +1,104 @@ +// +// MPToken.m +// MathPad +// +// Created by Kim Wittenburg on 19.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPToken.h" + +@implementation MPToken { + NSRange _range; + MPTokenType _tokenType; + NSString *_stringValue; +} + +- (instancetype)initEOFTokenAtLocation:(NSUInteger)eofLocation +{ + self = [super init]; + if (self) { + _range = NSMakeRange(eofLocation, 0); + } + return self; +} + +- (instancetype)initWithRange:(NSRange)range inString:(NSString *)input +{ + self = [super init]; + if (self) { + _range = range; + _stringValue = [input substringWithRange:range].copy; + } + return self; +} + +- (instancetype)initWithTokenType:(MPTokenType)tokenType + range:(NSRange)range + inString:(NSString *)input +{ + self = [self initWithRange:range + inString:input]; + if (self) { + _tokenType = tokenType; + } + return self; +} + +- (NSRange)range +{ + return _range; +} + +- (MPTokenType)tokenType +{ + return _tokenType; +} + +- (NSString *)stringValue +{ + return _stringValue; +} + +- (NSString *)description +{ + return self.stringValue; +} + +@end + + +@implementation MPToken (MPTokenExtension) + +- (NSUInteger)numberOfOperators +{ + NSString *operatorString = [[self.stringValue componentsSeparatedByString:@" "] componentsJoinedByString:@""]; + return operatorString.length; +} + +- (NSDecimalNumber *)operatorValue +{ + NSString *operatorString = [[self.stringValue componentsSeparatedByString:@" "] componentsJoinedByString:@""]; + NSDecimalNumber *value = [NSDecimalNumber one]; + for (NSUInteger charIndex = 0; charIndex < operatorString.length; charIndex++) { + NSString *operator = [operatorString substringWithRange:NSMakeRange(charIndex, 1)]; + if ([operator isEqualToString:@"-"]) { + value = [value decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]]; + } + } + return value; +} + +- (NSDecimalNumber *)number +{ + return [NSDecimalNumber decimalNumberWithString:self.stringValue + locale:[NSLocale currentLocale]]; +} + +- (NSString *)variable +{ + return self.stringValue; +} + + +@end diff --git a/MathPad/MPTokenStream.h b/MathPad/MPTokenStream.h new file mode 100644 index 0000000..05fc9f6 --- /dev/null +++ b/MathPad/MPTokenStream.h @@ -0,0 +1,27 @@ +// +// MPTokenStream.h +// MathPad +// +// Created by Kim Wittenburg on 20.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import +#import "MPToken.h" + +@interface MPTokenStream : NSObject + +- (instancetype)initWithTokens:(NSArray *)tokens; + +@property (nonatomic, copy) NSArray *tokens; +@property (nonatomic, getter=isIgnoringWhitespaceTokens) BOOL ignoringWhitespaceTokens; // Default: YES + +@property (readonly, nonatomic) NSUInteger currentLocation; + +- (void)reset; +- (BOOL)hasMoreTokens; + +- (MPToken *)nextToken; +- (MPToken *)nextTokenOfType:(MPTokenType)type; + +@end diff --git a/MathPad/MPTokenStream.m b/MathPad/MPTokenStream.m new file mode 100644 index 0000000..37f0ebe --- /dev/null +++ b/MathPad/MPTokenStream.m @@ -0,0 +1,90 @@ +// +// MPTokenStream.m +// MathPad +// +// Created by Kim Wittenburg on 20.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTokenStream.h" + +@interface MPTokenStream () +@property (readwrite, nonatomic) NSUInteger currentLocation; +@end + +@implementation MPTokenStream { + NSUInteger currentTokenIndex; + NSUInteger eofLocation; +} + +- (instancetype)initWithTokens:(NSArray *)tokens +{ + self = [super init]; + if (self) { + self.tokens = tokens; + self.ignoringWhitespaceTokens = YES; + if (tokens.count > 0) { + eofLocation = NSMaxRange([tokens.lastObject range]); + } + [self reset]; + } + return self; +} + +- (void)skipWhitespaces +{ + if (!self.isIgnoringWhitespaceTokens) { + return; + } + while (currentTokenIndex < self.tokens.count) { + MPToken *token = self.tokens[currentTokenIndex]; + if (token.tokenType != MPWhitespaceToken) { + return; + } + self.currentLocation = NSMaxRange(token.range); + ++currentTokenIndex; + } +} + +- (void)reset +{ + currentTokenIndex = 0; + self.currentLocation = 0; + [self skipWhitespaces]; +} + +- (BOOL)hasMoreTokens +{ + [self skipWhitespaces]; + return currentTokenIndex < self.tokens.count; +} + +- (MPToken *)nextToken +{ + [self skipWhitespaces]; + if (currentTokenIndex >= self.tokens.count) { + return [[MPToken alloc] initEOFTokenAtLocation:eofLocation]; + } else { + MPToken *token = self.tokens[currentTokenIndex++]; + self.currentLocation = NSMaxRange(token.range); + return token; + } +} + +- (MPToken *)nextTokenOfType:(MPTokenType)type +{ + [self skipWhitespaces]; + if (currentTokenIndex >= self.tokens.count) { + return nil; + } else { + MPToken *token = self.tokens[currentTokenIndex]; + if (token.tokenType != type) { + return nil; + } + ++currentTokenIndex; + self.currentLocation = NSMaxRange(token.range); + return token; + } +} + +@end