From 7a32e3b0b6d208cdc1567d9ac1a7c0642d840ee3 Mon Sep 17 00:00:00 2001 From: Kim Wittenburg Date: Mon, 24 Nov 2014 22:42:44 +0100 Subject: [PATCH] Fundamental Redesign of Evaluation --- MathPad/MPElementaryFunctionTerm.h | 20 ++ MathPad/MPElementaryFunctionTerm.m | 109 ++++++++ MathPad/MPEvaluationContext.h | 6 +- MathPad/MPEvaluationContext.m | 31 ++- MathPad/MPExpression.h | 62 +---- MathPad/MPExpression.m | 47 +++- MathPad/MPExpressionParser.h | 22 ++ MathPad/MPExpressionParser.m | 416 +++++++++++++++++++++++++++++ MathPad/MPExpressionTokenizer.m | 43 +-- MathPad/MPFactorialTerm.h | 17 ++ MathPad/MPFactorialTerm.m | 32 +++ MathPad/MPFractionFunction.m | 11 +- MathPad/MPFractionTerm.h | 13 + MathPad/MPFractionTerm.m | 27 ++ MathPad/MPFunction+MPToken.m | 12 +- MathPad/MPFunction.h | 48 +--- MathPad/MPFunction.m | 11 + MathPad/MPFunctionTerm.h | 23 ++ MathPad/MPFunctionTerm.m | 72 +++++ MathPad/MPNegatedTerm.h | 17 ++ MathPad/MPNegatedTerm.m | 32 +++ MathPad/MPNumber.h | 6 +- MathPad/MPNumber.m | 32 +-- MathPad/MPParenthesisFunction.m | 11 +- MathPad/MPParenthesisTerm.h | 13 + MathPad/MPParenthesisTerm.m | 18 ++ MathPad/MPParsedExpression.h | 34 +++ MathPad/MPParsedExpression.m | 27 ++ MathPad/MPPowerFunction.h | 11 +- MathPad/MPPowerFunction.m | 27 -- MathPad/MPPowerTerm.h | 15 ++ MathPad/MPPowerTerm.m | 36 +++ MathPad/MPProductTerm.h | 17 ++ MathPad/MPProductTerm.m | 50 ++++ MathPad/MPSumFunction.m | 48 +--- MathPad/MPSumFunctionTerm.h | 13 + MathPad/MPSumFunctionTerm.m | 38 +++ MathPad/MPSumTerm.h | 18 ++ MathPad/MPSumTerm.m | 43 +++ MathPad/MPTerm.h | 28 ++ MathPad/MPTerm.m | 85 ++++++ MathPad/MPToken.h | 34 +-- MathPad/MPTokenStream.m | 16 +- MathPad/MPVariable.h | 6 +- MathPad/MPVariable.m | 51 +--- 45 files changed, 1398 insertions(+), 350 deletions(-) create mode 100644 MathPad/MPElementaryFunctionTerm.h create mode 100644 MathPad/MPElementaryFunctionTerm.m create mode 100644 MathPad/MPExpressionParser.h create mode 100644 MathPad/MPExpressionParser.m create mode 100644 MathPad/MPFactorialTerm.h create mode 100644 MathPad/MPFactorialTerm.m create mode 100644 MathPad/MPFractionTerm.h create mode 100644 MathPad/MPFractionTerm.m create mode 100644 MathPad/MPFunctionTerm.h create mode 100644 MathPad/MPFunctionTerm.m create mode 100644 MathPad/MPNegatedTerm.h create mode 100644 MathPad/MPNegatedTerm.m create mode 100644 MathPad/MPParenthesisTerm.h create mode 100644 MathPad/MPParenthesisTerm.m create mode 100644 MathPad/MPParsedExpression.h create mode 100644 MathPad/MPParsedExpression.m create mode 100644 MathPad/MPPowerTerm.h create mode 100644 MathPad/MPPowerTerm.m create mode 100644 MathPad/MPProductTerm.h create mode 100644 MathPad/MPProductTerm.m create mode 100644 MathPad/MPSumFunctionTerm.h create mode 100644 MathPad/MPSumFunctionTerm.m create mode 100644 MathPad/MPSumTerm.h create mode 100644 MathPad/MPSumTerm.m create mode 100644 MathPad/MPTerm.h create mode 100644 MathPad/MPTerm.m diff --git a/MathPad/MPElementaryFunctionTerm.h b/MathPad/MPElementaryFunctionTerm.h new file mode 100644 index 0000000..31c986a --- /dev/null +++ b/MathPad/MPElementaryFunctionTerm.h @@ -0,0 +1,20 @@ +// +// MPFunctionValue.h +// MathPad +// +// Created by Kim Wittenburg on 11.10.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" + +@class MPElementaryFunctionTerm, MPValueGroup; + +@interface MPElementaryFunctionTerm : MPTerm + +- (instancetype)initWithFunctionIdentifier:(NSString *)function + term:(MPTerm *)term; /* designated initializer */ + +@property (readonly, nonatomic, strong) MPTerm *term; + +@end \ No newline at end of file diff --git a/MathPad/MPElementaryFunctionTerm.m b/MathPad/MPElementaryFunctionTerm.m new file mode 100644 index 0000000..3f337ed --- /dev/null +++ b/MathPad/MPElementaryFunctionTerm.m @@ -0,0 +1,109 @@ +// +// MPFunctionValue.m +// MathPad +// +// Created by Kim Wittenburg on 11.10.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPElementaryFunctionTerm.h" + +#import "MPParsedExpression.h" +#import "MPPowerFunction.h" +#import "MPProductTerm.h" +#import "MPTokenStream.h" +#import "MPToken.h" + +#import "MPExpression.h" +#import "MPMathRules.h" + +@implementation MPElementaryFunctionTerm { + NSDecimalNumber *(^_function)(NSDecimalNumber *); +} + +- (instancetype)initWithFunctionIdentifier:(NSString *)function + term:(MPTerm *)term +{ + self = [super init]; + if (self) { + NSAssert(function != nil, @"function must not be nil."); + NSAssert(term != nil, @"term must not be nil."); + double (*func)(double); + BOOL takesArc = NO; + BOOL returnsArc = NO; + if ([function isEqualToString:@"sin"]) { + func = &sin; + takesArc = YES; + } else if ([function isEqualToString:@"cos"]) { + func = &cos; + takesArc = YES; + } else if ([function isEqualToString:@"tan"]) { + func = &tan; + takesArc = YES; + } else if ([function isEqualToString:@"asin"]) { + func = &asin; + returnsArc = YES; + } else if ([function isEqualToString:@"acos"]) { + func = &acos; + returnsArc = YES; + } else if ([function isEqualToString:@"atan"]) { + func = &atan; + returnsArc = YES; + } else { + NSAssert(true, @"function must be one of (sin, cos, tan, asin, acos, atan)."); + } + [self setFunction:func + takesArcValue:takesArc + returnsArcValue:returnsArc]; + _term = term; + } + return self; +} + +- (void)setFunction:(double (*)(double))function + takesArcValue:(BOOL)takesArc + returnsArcValue:(BOOL)returnsArc +{ + id __weak weakSelf = self; + _function = ^(NSDecimalNumber *value){ + if (takesArc) { + value = [weakSelf convertToRadiantsIfNecessary:value]; + } + NSDecimalNumber *returnValue = [[NSDecimalNumber alloc] initWithDouble:function(value.doubleValue)]; + if (returnsArc) { + returnValue = [weakSelf convertToDegreesIfNecessary:returnValue]; + } + return returnValue; + }; +} + +- (NSDecimalNumber *)convertToRadiantsIfNecessary:(NSDecimalNumber *)degrees +{ + if ([MPMathRules sharedRules].isUsingDegrees) { + // * M_PI / 180 + return [[degrees decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithInteger:180]]; + } else { + return degrees; + } +} + +- (NSDecimalNumber *)convertToDegreesIfNecessary:(NSDecimalNumber *)radiants +{ + if ([MPMathRules sharedRules].isUsingDegrees) { + // * 180 / M_PI + return [[radiants decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:180]] decimalNumberByDividingBy:[[NSDecimalNumber alloc] initWithDouble:M_PI]]; + } else { + return radiants; + } +} + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *value = [self.term evaluate:error]; + if (!value) { + return nil; + } + return _function(value); +} + +@end diff --git a/MathPad/MPEvaluationContext.h b/MathPad/MPEvaluationContext.h index 82119b3..d75c672 100644 --- a/MathPad/MPEvaluationContext.h +++ b/MathPad/MPEvaluationContext.h @@ -15,9 +15,11 @@ - (void)push; - (void)pop; -- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value; +- (BOOL)defineVariable:(NSString *)variable + value:(NSDecimalNumber *)value; +- (BOOL)redefineVariable:(NSString *)variable + value:(NSDecimalNumber *)value; - (void)undefineVariable:(NSString *)variable; -- (BOOL)isVariableDefined:(NSString *)variable; - (NSDecimalNumber *)valueForVariable:(NSString *)variable; diff --git a/MathPad/MPEvaluationContext.m b/MathPad/MPEvaluationContext.m index 73ce3aa..0cf1e10 100644 --- a/MathPad/MPEvaluationContext.m +++ b/MathPad/MPEvaluationContext.m @@ -32,8 +32,10 @@ static MPEvaluationContext *sharedContext; if (self) { _stack = [[NSMutableArray alloc] init]; [self push]; - [self defineVariable:@"e" withValue:[[NSDecimalNumber alloc] initWithDouble:M_E]]; - [self defineVariable:@"π" withValue:[[NSDecimalNumber alloc] initWithDouble:M_PI]]; + [self defineVariable:@"e" + value:[[NSDecimalNumber alloc] initWithDouble:M_E]]; + [self defineVariable:@"π" + value:[[NSDecimalNumber alloc] initWithDouble:M_PI]]; } return self; } @@ -48,10 +50,23 @@ static MPEvaluationContext *sharedContext; [self.stack removeLastObject]; } -- (void)defineVariable:(NSString *)variable withValue:(NSDecimalNumber *)value +- (BOOL)defineVariable:(NSString *)variable + value:(NSDecimalNumber *)value { + if ([self isVariableDefined:variable]) { + return NO; + } NSMutableDictionary *currentBindings = self.stack.lastObject; currentBindings[variable] = value; + return YES; +} + +- (BOOL)redefineVariable:(NSString *)variable + value:(NSDecimalNumber *)value +{ + [self undefineVariable:variable]; + return [self defineVariable:variable + value:value]; } - (void)undefineVariable:(NSString *)variable @@ -60,6 +75,11 @@ static MPEvaluationContext *sharedContext; [currentBindings removeObjectForKey:variable]; } +- (BOOL)isVariableDefined:(NSString *)variable +{ + return [self valueForVariable:variable] != nil; +} + - (NSDecimalNumber *)valueForVariable:(NSString *)variable { NSUInteger currentIndex = self.stack.count; @@ -72,9 +92,4 @@ static MPEvaluationContext *sharedContext; return value; } -- (BOOL)isVariableDefined:(NSString *)variable -{ - return [self valueForVariable:variable] != nil; -} - @end diff --git a/MathPad/MPExpression.h b/MathPad/MPExpression.h index 1ac3ee1..a5cbac4 100644 --- a/MathPad/MPExpression.h +++ b/MathPad/MPExpression.h @@ -31,57 +31,6 @@ FOUNDATION_EXPORT NSString *const MPIllegalElementException; FOUNDATION_EXPORT NSString *const MPIllegalElementExceptionElementKey; -/*! - @const MPMathKitErrorDomain - @brief Predefined error domain for errors from the MathKit framework. - - @discussion Errors in MathKit can occur during parsing of expressions or - during evaluation of expressions. These two can be distinguished - by the error code. Parsing errors have lower error codes. - Evaluation errors (math errors) have error codes from @c 100 upwards. - */ -FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain; - - -// TODO: Deal with this: -// FOUNDATION_EXPORT NSString *const MPPathToExpressionKey; -// FOUNDATION_EXPORT NSString *const MPRangeKey; - - -/*! - @function MPParseError - @brief Creates an @c NSError object in the @c MPMathKitErrorDomain. - - @discussion The created error is filled with the passed data if present. - - @param errorCode - The error code of the created error. - - @param errorDescription - The localized description of the created error. May be @c nil. - - @param underlyingError - The underlying error of the created error. May be @c nil. - - @return An @c NSError object initialized with the given data. - */ -NS_INLINE NSError * MPParseError(NSInteger errorCode, - NSString *errorDescription, - NSError *underlyingError) { - NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init]; - if (errorDescription) { - userInfo[NSLocalizedDescriptionKey] = errorDescription; - } - if (underlyingError) { - userInfo[NSUnderlyingErrorKey] = underlyingError; - } - NSError *error = [NSError errorWithDomain:MPMathKitErrorDomain - code:errorCode - userInfo:userInfo.copy]; - return error; -} - - /*! @typedef MPReferenceFrame @brief A reference frame describes what an @em item is. @@ -116,7 +65,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { -@class MPExpression, MPFunction, MPRangePath, MPExpressionTree, MPExpressionEvaluator; +@class MPExpression, MPFunction, MPRangePath, MPParsedExpression; @protocol MPExpressionElement, MPToken; /*! @@ -630,7 +579,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { want more control over the evaluation process use @c -parse instead. - @param error + @param errors If the receiver (or any of its elements) contains a syntax error or can not be evaluated this parameter is set to an appropriate value. Pass @c NULL if you are not interested in any errors that @@ -642,7 +591,7 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { math error. In most cases the error parameter contains an appropriate description of the problem. */ -- (NSDecimalNumber *)evaluateWithError:(NSError *__autoreleasing *)error; +- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors; /*! @@ -657,7 +606,10 @@ typedef NS_ENUM(NSUInteger, MPReferenceFrame) { @return A @c MPExpressionTree object that can evaluate the receiver. */ -- (MPExpressionTree *)parse; +- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors; + +- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag + errors:(NSArray *__autoreleasing *)errors; #pragma mark Notifications diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m index 1f36d39..6c2d7e8 100644 --- a/MathPad/MPExpression.m +++ b/MathPad/MPExpression.m @@ -14,9 +14,9 @@ #import "MPRangePath.h" #import "MPExpressionTokenizer.h" -#import "MPTokenStream.h" #import "MPToken.h" -#import "MPExpressionTree.h" +#import "MPExpressionParser.h" +#import "MPParsedExpression.h" #import "NSIndexPath+MPAdditions.h" @@ -25,10 +25,6 @@ NSString *const MPIllegalElementException = @"MPIllegalElementException"; NSString *const MPIllegalElementExceptionElementKey = @"MPIllegalElementExceptionElementKey"; -NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain"; -NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; - - @interface MPExpression () { NSMutableArray * _elements; @@ -328,7 +324,11 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; break; case MPTokenReferenceFrame: - symbolIndex = [self.tokens[anIndex] range].location; + [self.tokens enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + symbolIndex += obj.range.length; + *stop = idx >= anIndex - 1; + }]; +// symbolIndex = [self.tokens[anIndex] range].location; break; } @@ -556,17 +556,38 @@ NSString *const MPExpressionPathErrorKey = @"MPExpressionPathErrorKey"; #pragma mark Evaluating Expressions -- (NSDecimalNumber *)evaluateWithError:(NSError *__autoreleasing *)error +- (NSDecimalNumber *)evaluateWithErrors:(NSArray *__autoreleasing *)errors { - MPExpressionTree *tree = [self parse]; - return [tree validate:error] ? [tree evaluate] : nil; + 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; } -- (MPExpressionTree *)parse +- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors { - MPTokenStream *tokenStream = [[MPTokenStream alloc] initWithTokens:self.tokens]; - return [[MPExpressionTree alloc] initWithTokenStream:tokenStream]; + return [self parseExpectingVariable:NO + errors:errors]; +} + + +- (MPParsedExpression *)parseExpectingVariable:(BOOL)flag + errors:(NSArray *__autoreleasing *)errors +{ + return [[[MPExpressionParser alloc] initWithExpression:self] parseExpectingVariableDefinition:flag + errors:errors]; } diff --git a/MathPad/MPExpressionParser.h b/MathPad/MPExpressionParser.h new file mode 100644 index 0000000..dc39db0 --- /dev/null +++ b/MathPad/MPExpressionParser.h @@ -0,0 +1,22 @@ +// +// MPExpressionParser.h +// MathPad +// +// Created by Kim Wittenburg on 16.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPExpression.h" +#import "MPParsedExpression.h" + +@interface MPExpressionParser : NSObject + +@property (readonly, nonatomic, strong) MPExpression *expression; + +- (instancetype)initWithExpression:(MPExpression *)expression; + +- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors; +- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag + errors:(NSArray *__autoreleasing *)errors; + +@end diff --git a/MathPad/MPExpressionParser.m b/MathPad/MPExpressionParser.m new file mode 100644 index 0000000..462e87a --- /dev/null +++ b/MathPad/MPExpressionParser.m @@ -0,0 +1,416 @@ +// +// MPExpressionParser.m +// MathPad +// +// Created by Kim Wittenburg on 16.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPExpressionParser.h" + +#import "MPExpression.h" +#import "MPTokenStream.h" +#import "MPTerm.h" +#import "MPToken.h" +#import "MPSumTerm.h" +#import "MPProductTerm.h" +#import "MPFactorialTerm.h" +#import "MPElementaryFunctionTerm.h" +#import "MPFunctionTerm.h" +#import "MPPowerTerm.h" +#import "MPNegatedTerm.h" + +#import "MPNumber.h" +#import "MPVariable.h" +#import "MPFactorialTerm.h" + +#define success() state = 0 +#define fail() state = -1 + +@interface MPExpressionParser () + +@property (nonatomic, strong) NSArray *tokens; +@property (nonatomic) NSUInteger currentTokenIndex; + +@property (nonatomic, strong) NSMutableArray *errors; + +@property (nonatomic, strong) NSMutableArray *summands; +@property (nonatomic) BOOL summandNegative; +@property (nonatomic, strong) NSMutableArray *factors; +@property (nonatomic) BOOL factorNegative; +@property (nonatomic, strong) NSMutableArray *functionStack; +@property (nonatomic, strong) NSMutableArray *valueGroup; +@property (nonatomic, strong) MPTerm *value; + +@property (nonatomic) NSUInteger errorTokenIndex; + +@end + +@implementation MPExpressionParser + +- (instancetype)initWithExpression:(MPExpression *)expression +{ + self = [super init]; + if (self) { + _tokens = [expression allItemsInReferenceFrame:MPTokenReferenceFrame]; + _expression = expression; + } + return self; +} + +- (MPParsedExpression *)parse:(NSArray *__autoreleasing *)errors +{ + return [self parseExpectingVariableDefinition:NO + errors:errors]; +} + +- (MPParsedExpression *)parseExpectingVariableDefinition:(BOOL)flag + errors:(NSArray *__autoreleasing *)errors +{ + MPParsedExpression *result = [[MPParsedExpression alloc] init]; + self.currentTokenIndex = 0; + + BOOL hasVariableDefinition = NO; + NSString *variableName = nil; + [self skipWhitespaces]; + if (self.currentToken.tokenType == MPVariableToken) { + variableName = self.currentToken.stringValue; + [self nextToken]; + [self skipWhitespaces]; + if (self.currentToken.tokenType == MPEqualsToken) { + [self nextToken]; + hasVariableDefinition = YES; + } + } + if (flag && hasVariableDefinition) { + result.definedVariable = variableName; + } else if (flag) { + [self addErrorWithCode:3 localizedDescription:NSLocalizedString(@"Expected Variable Definition.", nil)]; + if (errors) { + *errors = self.errors; + } + return nil; + } else if (hasVariableDefinition) { + [self addErrorWithCode:4 localizedDescription:NSLocalizedString(@"Unexpected Variable Definition.", nil)]; + if (errors) { + *errors = self.errors; + } + return nil; + } else { + self.currentTokenIndex = 0; + } + + [self skipWhitespaces]; + if (self.currentTokenIndex >= self.tokens.count) { + [self addErrorWithCode:5 localizedDescription:NSLocalizedString(@"Empty Expression.", nil)]; + if (errors) { + *errors = self.errors; + } + return nil; + } + + [self runParserMachine]; + if ([self errorOccured]) { + if (errors) { + *errors = self.errors; + } + return nil; + } + result.term = [[MPSumTerm alloc] initWithSummands:self.summands]; + return result; +} + +- (void)runParserMachine +{ + NSInteger state = 1; + NSInteger backtraceState = 0; + NSUInteger backtraceTokenIndex = self.currentTokenIndex; + BOOL alternateRoute = NO; + while (state != 0) { + switch (state) { + case -1: + state = backtraceState; + self.currentTokenIndex = backtraceTokenIndex; + alternateRoute = YES; + break; + + case 1: + [self skipWhitespaces]; + if (self.currentToken.tokenType == MPOperatorListToken) { + self.factorNegative = [self parseOperatorList:self.currentToken]; + [self nextToken]; + state = 2; + } else { + self.factorNegative = NO; + state = 2; + } + break; + + case 2: + [self skipWhitespaces]; + if (self.currentToken.tokenType == MPElementaryFunctionToken) { + [self.functionStack addObject:self.currentToken.stringValue]; + [self nextToken]; + state = 2; + } else { + state = 3; + } + break; + + case 3: + [self skipWhitespaces]; + if (self.currentToken.tokenType == MPGenericFunctionToken) { + NSArray *errors; + self.value = [[MPFunctionTerm alloc] initWithFunction:(MPFunction *)self.currentToken + errors:&errors]; + if (!self.value) { + [self.errors addObjectsFromArray:errors]; + } + [self nextToken]; + [self runSuffixMachine]; + if (self.value) { + [self.valueGroup addObject:self.value]; + } + state = 6; + } else { + state = 4; + } + break; + + case 4: + if (self.currentToken.tokenType == MPNumberToken) { + self.value = [[MPNumber alloc] initWithNumber:[self parseNumber:self.currentToken]]; + [self nextToken]; + [self runSuffixMachine]; + [self.valueGroup addObject:self.value]; + state = 5; + } else if (self.currentToken.tokenType == MPVariableToken) { + self.value = [[MPVariable alloc] initWithVariableName:[self parseVariable:self.currentToken]]; + [self nextToken]; + [self runSuffixMachine]; + [self.valueGroup addObject:self.value]; + state = 5; + } else { + self.errorTokenIndex = self.currentTokenIndex; + fail(); + } + break; + + case 5: + if (alternateRoute) { + alternateRoute = NO; + state = 6; + } else { + backtraceState = 5; + backtraceTokenIndex = self.currentTokenIndex; + state = 4; + } + break; + + case 6: + [self collapseFactor]; + [self skipWhitespaces]; + backtraceState = 6; + backtraceTokenIndex = self.currentTokenIndex; + if (!alternateRoute && self.currentToken.tokenType == MPOperatorListToken) { + [self collapseSummand]; + self.summandNegative = [self parseOperatorList:self.currentToken]; + [self nextToken]; + state = 1; + } else { + state = 7; + } + break; + + case 7: + if (alternateRoute) { + [self collapseSummand]; + alternateRoute = NO; + success(); + } else { + backtraceState = 7; + backtraceTokenIndex = self.currentTokenIndex; + state = 8; + } + break; + + case 8: + [self skipWhitespaces]; + if (self.currentToken.tokenType == MPMultiplicationSymbolToken) { + [self nextToken]; + state = 1; + } else { + state = 1; + } + break; + + default: + // Should never get here + break; + } + } +} + +- (void)skipWhitespaces +{ + while ([self currentToken] != nil && [self currentToken].tokenType == MPWhitespaceToken) { + [self nextToken]; + } +} + +- (id)currentToken +{ + if (self.currentTokenIndex >= self.tokens.count) { + return nil; + } + return self.tokens[self.currentTokenIndex]; +} + +- (void)nextToken +{ + self.currentTokenIndex++; +} + +- (NSMutableArray *)errors +{ + if (!_errors) { + _errors = [[NSMutableArray alloc] init]; + } + return _errors; +} + +- (NSMutableArray *)summands +{ + if (!_summands) { + _summands = [[NSMutableArray alloc] init]; + } + return _summands; +} + +- (NSMutableArray *)factors +{ + if (!_factors) { + _factors = [[NSMutableArray alloc] init]; + } + return _factors; +} + +- (NSMutableArray *)functionStack +{ + if (!_functionStack) { + _functionStack = [[NSMutableArray alloc] init]; + } + return _functionStack; +} + +- (NSMutableArray *)valueGroup +{ + if (!_valueGroup) { + _valueGroup = [[NSMutableArray alloc] init]; + } + return _valueGroup; +} + +- (BOOL)parseOperatorList:(id)token // Returns YES if list is overall negative +{ + NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""] + stringByReplacingOccurrencesOfString:@"+" withString:@""]; + return (operatorString.length & 1) == 1; +} + +- (NSDecimalNumber *)parseNumber:(id)token +{ + return [NSDecimalNumber decimalNumberWithString:token.stringValue + locale:[NSLocale currentLocale]]; +} + +- (NSString *)parseVariable:(id)token +{ + return token.stringValue; +} + +- (void)collapseSummand +{ + if (self.factors.count > 0) { + MPTerm *summand = [[MPProductTerm alloc] initWithFactors:self.factors]; + if (self.summandNegative) { + summand = [[MPNegatedTerm alloc] initWithTerm:summand]; + } + [self.summands addObject:summand]; + } + self.factors = nil; + self.summandNegative = NO; +} + +- (void)collapseFactor +{ + if (self.valueGroup.count > 0) { + MPTerm *factor = [[MPProductTerm alloc] initWithFactors:self.valueGroup]; + for (NSInteger index = self.functionStack.count - 1; index >= 0; index--) { + factor = [[MPElementaryFunctionTerm alloc] initWithFunctionIdentifier:self.functionStack[index] + term:factor]; + } + if (self.factorNegative) { + factor = [[MPNegatedTerm alloc] initWithTerm:factor]; + } + [self.factors addObject:factor]; + } + self.valueGroup = nil; + self.functionStack = nil; + self.factorNegative = NO; +} + +- (void)runSuffixMachine +{ + BOOL checkMore = YES; + while (checkMore) { + if (self.currentToken.tokenType == MPFactorialToken) { + [self nextToken]; + if (self.value) { + MPFactorialTerm *term = [[MPFactorialTerm alloc] initWithTerm:self.value]; + self.value = term; + } + } else if (self.currentToken.tokenType == MPPowerToken) { + [self nextToken]; + if (self.value) { + NSArray *errors; + MPPowerTerm *term = [[MPPowerTerm alloc] initWithFunction:(MPFunction *)self.currentToken errors:&errors]; + if (!term) { + [self.errors addObjectsFromArray:errors]; + } + term.baseTerm = self.value; + self.value = term; + } + } else { + checkMore = NO; + } + } +} + +- (void)addErrorWithCode:(NSInteger)code + localizedDescription:(NSString *)description +{ + NSInteger errorIndex = [self.expression convertIndex:self.errorTokenIndex + fromReferenceFrame:MPTokenReferenceFrame + toReferenceFrame:MPSymbolReferenceFrame]; + [self.errors addObject:[NSError errorWithDomain:MPMathKitErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey: description, + MPPathToExpressionKey: self.expression.indexPath, + MPErrorIndexKey: @(errorIndex)}]]; +} + +- (BOOL)errorOccured +{ + [self skipWhitespaces]; + if (self.currentTokenIndex < self.tokens.count) { + if (self.errorTokenIndex >= self.tokens.count) { + [self addErrorWithCode:0 localizedDescription:NSLocalizedString(@"Unexpected end. Expected Value.", nil)]; + } else { + [self addErrorWithCode:1 localizedDescription:NSLocalizedString(@"Unexpected Symbol. Expected Value", nil)]; + } + } + return self.errors.count > 0; +} + +@end diff --git a/MathPad/MPExpressionTokenizer.m b/MathPad/MPExpressionTokenizer.m index f28db3f..a66c411 100644 --- a/MathPad/MPExpressionTokenizer.m +++ b/MathPad/MPExpressionTokenizer.m @@ -51,12 +51,7 @@ @"([\\*∙⋅])|" @"([+-](?:\\s*[+-])*)|" @"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|" - @"(sin)|" - @"(cos)|" - @"(tan)|" - @"(asin)|" - @"(acos)|" - @"(atan)|" + @"(sin|cos|tan|asin|acos|atan)|" @"([A-Za-z])|" @"(!)|" @"(=)|" @@ -76,16 +71,11 @@ 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 asinRange = [match rangeAtIndex:7]; - NSRange acosRange = [match rangeAtIndex:8]; - NSRange atanRange = [match rangeAtIndex:9]; - NSRange variableRange = [match rangeAtIndex:10]; - NSRange factorialRange = [match rangeAtIndex:11]; - NSRange equalsRange = [match rangeAtIndex:12]; - NSRange whitespaceRange = [match rangeAtIndex:13]; + NSRange elementaryFunctionRange = [match rangeAtIndex:4]; + NSRange variableRange = [match rangeAtIndex:5]; + NSRange factorialRange = [match rangeAtIndex:6]; + NSRange equalsRange = [match rangeAtIndex:7]; + NSRange whitespaceRange = [match rangeAtIndex:8]; if (MPRangeExists(multiplicationSymbolRange)) { range = multiplicationSymbolRange; @@ -96,24 +86,9 @@ } 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(asinRange)) { - range = asinRange; - tokenType = MPASinToken; - } else if (MPRangeExists(acosRange)) { - range = acosRange; - tokenType = MPACosToken; - } else if (MPRangeExists(atanRange)) { - range = atanRange; - tokenType = MPATanToken; + } else if (MPRangeExists(elementaryFunctionRange)) { + range = elementaryFunctionRange; + tokenType = MPElementaryFunctionToken; } else if (MPRangeExists(variableRange)) { range = variableRange; tokenType = MPVariableToken; diff --git a/MathPad/MPFactorialTerm.h b/MathPad/MPFactorialTerm.h new file mode 100644 index 0000000..e9cd26c --- /dev/null +++ b/MathPad/MPFactorialTerm.h @@ -0,0 +1,17 @@ +// +// MPFactorialTerm.h +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" + +@interface MPFactorialTerm : MPTerm + +- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */ + +@property (readonly, nonatomic, strong) MPTerm *term; + +@end diff --git a/MathPad/MPFactorialTerm.m b/MathPad/MPFactorialTerm.m new file mode 100644 index 0000000..33c4bc8 --- /dev/null +++ b/MathPad/MPFactorialTerm.m @@ -0,0 +1,32 @@ +// +// MPFactorialTerm.m +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFactorialTerm.h" + +@implementation MPFactorialTerm + +- (instancetype)initWithTerm:(MPTerm *)term +{ + self = [super init]; + if (self) { + NSAssert(term != nil, @"term must not be nil."); + _term = term; + } + return self; +} + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *value = [self.value evaluate:error]; + if (!value) { + return nil; + } + return [[NSDecimalNumber alloc] initWithDouble:tgamma(value.doubleValue + 1)]; +} + +@end diff --git a/MathPad/MPFractionFunction.m b/MathPad/MPFractionFunction.m index 73d53e1..76312e6 100644 --- a/MathPad/MPFractionFunction.m +++ b/MathPad/MPFractionFunction.m @@ -8,8 +8,8 @@ #import "MPFractionFunction.h" +#import "MPFractionTerm.h" #import "MPExpression.h" -#import "MPExpressionTree.h" @implementation MPFractionFunction @@ -21,14 +21,9 @@ MPFunctionAccessorImplementation(DenominatorExpression, _denominatorExpression) return @[@"nominatorExpression", @"denominatorExpression"]; } -- (BOOL)validate:(NSError *__autoreleasing *)error +- (Class)functionTermClass { - return [[self.nominatorExpression parse] validate:error] && [[self.denominatorExpression parse] validate:error]; -} - -- (NSDecimalNumber *)evaluate -{ - return [[[self.nominatorExpression parse] evaluate] decimalNumberByDividingBy:[[self.denominatorExpression parse] evaluate]]; + return [MPFractionTerm class]; } - (NSString *)description diff --git a/MathPad/MPFractionTerm.h b/MathPad/MPFractionTerm.h new file mode 100644 index 0000000..0bcbb18 --- /dev/null +++ b/MathPad/MPFractionTerm.h @@ -0,0 +1,13 @@ +// +// MPFractionTerm.h +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunctionTerm.h" + +@interface MPFractionTerm : MPFunctionTerm + +@end diff --git a/MathPad/MPFractionTerm.m b/MathPad/MPFractionTerm.m new file mode 100644 index 0000000..6b5928d --- /dev/null +++ b/MathPad/MPFractionTerm.m @@ -0,0 +1,27 @@ +// +// MPFractionTerm.m +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFractionTerm.h" + +@implementation MPFractionTerm + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *nominator = [[self expressionAtIndex:0] evaluate:error]; + if (!nominator) { + return nil; + } + NSDecimalNumber *denominator = [[self expressionAtIndex:1] evaluate:error]; + if (!denominator) { + return nil; + } +#warning Division by zero ahead + return [nominator decimalNumberByDividingBy:denominator]; +} + +@end diff --git a/MathPad/MPFunction+MPToken.m b/MathPad/MPFunction+MPToken.m index 2a6b430..5b83b43 100644 --- a/MathPad/MPFunction+MPToken.m +++ b/MathPad/MPFunction+MPToken.m @@ -9,31 +9,25 @@ #import "MPFunction+MPToken.h" #import "MPExpression.h" +#import "MPPowerFunction.h" @implementation MPFunction (MPToken) - (MPTokenType)tokenType { - return MPGenericFunctionToken; + return [self isMemberOfClass:[MPPowerFunction class]] ? MPPowerToken : MPGenericFunctionToken; } - (NSRange)range { - NSUInteger selfIndex = [self.parent indexOfElement:self]; - return NSMakeRange([self.parent convertIndex:selfIndex + return NSMakeRange([self.parent convertIndex:[self.parent indexOfElement:self] fromReferenceFrame:MPElementReferenceFrame toReferenceFrame:MPSymbolReferenceFrame], 1); } -- (BOOL)exists -{ - return YES; -} - - - (NSString *)stringValue { return [self description]; diff --git a/MathPad/MPFunction.h b/MathPad/MPFunction.h index c445974..0b1c341 100644 --- a/MathPad/MPFunction.h +++ b/MathPad/MPFunction.h @@ -26,7 +26,7 @@ -@class MPFunction, MPExpression, MPRangePath; +@class MPFunction, MPExpression, MPRangePath, MPFunctionTerm; @protocol MPExpressionElement; @@ -241,6 +241,12 @@ */ - (void)didChangeChildAtIndex:(NSUInteger)index; + +#pragma mark Evaluating Functions + + +- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index; + @end @@ -256,6 +262,10 @@ */ @interface MPFunction (MPSubclassOverride) +/* In Addition to the methods listed below MPFunction subclasses should also + * override the NSObject methods -description and -hash. + */ + #pragma mark Working With the Expression Tree /*! @methodgroup Working With the Expression Tree @@ -281,40 +291,6 @@ @methodgroup Evaluating Functions */ - -/*! - @method validate: - @brief Validates the function and all its children for syntax errors. - - @param error - If the validation fails this parameter should be set to an - appropriate value. - - @return @c YES if the validation was successful, @c NO otherwise. - */ -- (BOOL)validate:(NSError *__autoreleasing *)error; - - -/*! - @method evaluate: - @brief Evaluates the function. - - @discussion Function evaluation should actually perform math. Also the - implementation of this method assumes that the function and all - children are valid (as determined by the @c -validate: method). - - @param error - If a mathematical error occurs it is reflected in this parameter. - Depending on the error this method then either returns @c nil or - @c NaN. - - @return The result of the evaluation or @c nil or @c NaN if an evaluation - error occured (such as a division by @c 0). - */ -- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error; - -/* In Addition to the above methods MPFunction subclasses should also override - * the NSObject methods -description and -hash. - */ +- (Class)functionTermClass; @end diff --git a/MathPad/MPFunction.m b/MathPad/MPFunction.m index 875e86b..8b8a47a 100644 --- a/MathPad/MPFunction.m +++ b/MathPad/MPFunction.m @@ -12,6 +12,8 @@ #import "MPRangePath.h" #import "NSIndexPath+MPAdditions.h" +#import "MPFunctionTerm.h" + @implementation MPFunction @@ -131,6 +133,15 @@ } +#pragma mark Evaluating Functions + + +- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index +{ + return NO; +} + + #pragma mark Working With Functions diff --git a/MathPad/MPFunctionTerm.h b/MathPad/MPFunctionTerm.h new file mode 100644 index 0000000..1725501 --- /dev/null +++ b/MathPad/MPFunctionTerm.h @@ -0,0 +1,23 @@ +// +// MPFunctionTerm.h +// MathPad +// +// Created by Kim Wittenburg on 12.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" + +#import "MPParsedExpression.h" +#import "MPFunction.h" + + + +@interface MPFunctionTerm : MPTerm + +- (instancetype)initWithFunction:(MPFunction *)function + errors:(NSArray *__autoreleasing *)errors; /* designated initializer */ + +- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex; + +@end diff --git a/MathPad/MPFunctionTerm.m b/MathPad/MPFunctionTerm.m new file mode 100644 index 0000000..5fea915 --- /dev/null +++ b/MathPad/MPFunctionTerm.m @@ -0,0 +1,72 @@ +// +// MPFunctionTerm.m +// MathPad +// +// Created by Kim Wittenburg on 12.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunctionTerm.h" + +#import "MPParsedExpression.h" + +#import "MPToken.h" +#import "MPTokenStream.h" +#import "MPExpression.h" + + + +@interface MPFunctionTerm () + +@property (nonatomic, strong) NSArray *parsedExpressions; + +@end + + + +@implementation MPFunctionTerm + +- (instancetype)initWithFunction:(MPFunction *)function + errors:(NSArray *__autoreleasing *)errors +{ + NSAssert(function != nil, @"function must not be nil."); + if ([self isMemberOfClass:[MPFunctionTerm class]]) { + return [[function.functionTermClass alloc] initWithFunction:function + errors:errors]; + } + self = [super init]; + if (self) { + NSMutableArray *parsedExpressions = [[NSMutableArray alloc] initWithCapacity:function.childrenAccessors.count]; + NSUInteger childIndex = 0; + BOOL errorOccured = NO; + NSMutableArray *mutableErrors = [[NSMutableArray alloc] init]; + for (NSString *accessor in function.childrenAccessors) { + MPExpression *expression = [function valueForKey:accessor]; + NSArray *localErrors; + MPParsedExpression *parsedExpression = [expression parseExpectingVariable:[function expectsVariableDefinitionInChildAtIndex:childIndex] + errors:&localErrors]; + if (parsedExpression) { + [parsedExpressions addObject:parsedExpression]; + } else { + [mutableErrors addObjectsFromArray:localErrors]; + errorOccured = YES; + } + childIndex++; + } + if (errorOccured) { + if (errors) { + *errors = mutableErrors; + } + return nil; + } + self.parsedExpressions = parsedExpressions; + } + return self; +} + +- (MPParsedExpression *)expressionAtIndex:(NSUInteger)anIndex +{ + return [self.parsedExpressions objectAtIndex:anIndex]; +} + +@end diff --git a/MathPad/MPNegatedTerm.h b/MathPad/MPNegatedTerm.h new file mode 100644 index 0000000..3b4c180 --- /dev/null +++ b/MathPad/MPNegatedTerm.h @@ -0,0 +1,17 @@ +// +// MPNegatedTerm.h +// MathPad +// +// Created by Kim Wittenburg on 22.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" + +@interface MPNegatedTerm : MPTerm + +- (instancetype)initWithTerm:(MPTerm *)term; /* designated initializer */ + +@property (readonly, nonatomic, strong) MPTerm *term; + +@end diff --git a/MathPad/MPNegatedTerm.m b/MathPad/MPNegatedTerm.m new file mode 100644 index 0000000..d7b1d27 --- /dev/null +++ b/MathPad/MPNegatedTerm.m @@ -0,0 +1,32 @@ +// +// MPNegatedTerm.m +// MathPad +// +// Created by Kim Wittenburg on 22.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPNegatedTerm.h" + +@implementation MPNegatedTerm + +- (instancetype)initWithTerm:(MPTerm *)term +{ + self = [super init]; + if (self) { + NSAssert(term != nil, @"term must not be nil."); + _term = term; + } + return self; +} + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *value = [self.term evaluate:error]; + if (value) { + value = [value decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]]; + } + return value; +} + +@end diff --git a/MathPad/MPNumber.h b/MathPad/MPNumber.h index cfb1805..5a9b062 100644 --- a/MathPad/MPNumber.h +++ b/MathPad/MPNumber.h @@ -6,11 +6,13 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -#import "MPValueGroup.h" +#import "MPTerm.h" @class MPNumber; -@interface MPNumber : NSObject +@interface MPNumber : MPTerm + +- (instancetype)initWithNumber:(NSDecimalNumber *)number; /* designated initializer */ @property (readonly, nonatomic, strong) NSDecimalNumber *number; diff --git a/MathPad/MPNumber.m b/MathPad/MPNumber.m index d9c787b..2596593 100644 --- a/MathPad/MPNumber.m +++ b/MathPad/MPNumber.m @@ -8,44 +8,24 @@ #import "MPNumber.h" +#import "MPParsedExpression.h" + #import "MPTokenStream.h" #import "MPToken.h" @implementation MPNumber -- (instancetype)init +- (instancetype)initWithNumber:(NSDecimalNumber *)number { self = [super init]; if (self) { + NSAssert(number != nil, @"number must not be nil."); + _number = number; } return self; } -- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream -{ - self = [self init]; - if (self) { - [tokenStream beginIgnoringWhitespaceTokens]; - MPToken *token = tokenStream.currentToken; - if (token.tokenType == MPNumberToken) { - _number = [NSDecimalNumber decimalNumberWithString:token.stringValue locale:[NSLocale currentLocale]]; - } else { - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:@"Expected Number" - userInfo:nil]; - } - [tokenStream currentTokenConsumed]; - [tokenStream endIgnoringOrAcceptingWhitespaceTokens]; - } - return self; -} - -- (BOOL)validate:(NSError *__autoreleasing *)error -{ - return YES; -} - -- (NSDecimalNumber *)evaluate +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { return self.number; } diff --git a/MathPad/MPParenthesisFunction.m b/MathPad/MPParenthesisFunction.m index 4d138ce..78d0eaf 100644 --- a/MathPad/MPParenthesisFunction.m +++ b/MathPad/MPParenthesisFunction.m @@ -10,7 +10,7 @@ #import "MPParenthesisFunction.h" #import "MPExpression.h" -#import "MPExpressionTree.h" +#import "MPParenthesisTerm.h" @implementation MPParenthesisFunction @@ -21,14 +21,9 @@ MPFunctionAccessorImplementation(Expression, _expression) return @[@"expression"]; } -- (BOOL)validate:(NSError *__autoreleasing *)error +- (Class)functionTermClass { - return [[self.expression parse] validate:error]; -} - -- (NSDecimalNumber *)evaluate -{ - return [[self.expression parse] evaluate]; + return [MPParenthesisTerm class]; } - (NSString *)description diff --git a/MathPad/MPParenthesisTerm.h b/MathPad/MPParenthesisTerm.h new file mode 100644 index 0000000..2aace2f --- /dev/null +++ b/MathPad/MPParenthesisTerm.h @@ -0,0 +1,13 @@ +// +// MPParenthesisTerm.h +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunctionTerm.h" + +@interface MPParenthesisTerm : MPFunctionTerm + +@end diff --git a/MathPad/MPParenthesisTerm.m b/MathPad/MPParenthesisTerm.m new file mode 100644 index 0000000..474e234 --- /dev/null +++ b/MathPad/MPParenthesisTerm.m @@ -0,0 +1,18 @@ +// +// MPParenthesisTerm.m +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPParenthesisTerm.h" + +@implementation MPParenthesisTerm + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + return [[self expressionAtIndex:0] evaluate:error]; +} + +@end diff --git a/MathPad/MPParsedExpression.h b/MathPad/MPParsedExpression.h new file mode 100644 index 0000000..deabfc1 --- /dev/null +++ b/MathPad/MPParsedExpression.h @@ -0,0 +1,34 @@ +// +// MPParsedExpression.h +// MathPad +// +// Created by Kim Wittenburg on 14.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +@class MPParsedExpression, MPTokenStream, MPTerm; +@protocol MPToken; + +/*! + @const MPMathKitErrorDomain + @brief Predefined error domain for errors from the MathKit framework. + + @discussion Errors in MathKit can occur during parsing of expressions or + during evaluation of expressions. These two can be distinguished + by the error code. Parsing errors have lower error codes. + Evaluation errors (math errors) have error codes from @c 100 upwards. + */ +FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain; + +FOUNDATION_EXPORT NSString *const MPPathToExpressionKey; +FOUNDATION_EXPORT NSString *const MPErrorIndexKey; + + +@interface MPParsedExpression : NSObject + +@property (nonatomic, strong) NSString *definedVariable; +@property (nonatomic, strong) MPTerm *term; + +- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error; + +@end diff --git a/MathPad/MPParsedExpression.m b/MathPad/MPParsedExpression.m new file mode 100644 index 0000000..dcc305b --- /dev/null +++ b/MathPad/MPParsedExpression.m @@ -0,0 +1,27 @@ +// +// MPParsedExpression.m +// MathPad +// +// Created by Kim Wittenburg on 14.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPParsedExpression.h" + +#import "MPTokenStream.h" +#import "MPToken.h" + +#import "MPTerm.h" + +NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain"; +NSString *const MPPathToExpressionKey = @"MPPathToExpressionKey"; +NSString *const MPErrorIndexKey = @"MPErrorIndexKey"; + +@implementation MPParsedExpression + +- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error +{ + return [self.term evaluate:error]; +} + +@end diff --git a/MathPad/MPPowerFunction.h b/MathPad/MPPowerFunction.h index e69aee3..61e4c06 100644 --- a/MathPad/MPPowerFunction.h +++ b/MathPad/MPPowerFunction.h @@ -6,12 +6,19 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -#import "MPSuffixFunction.h" +#import "MPFunction.h" @class MPPowerFunction, MPExpression; -@interface MPPowerFunction : MPSuffixFunction +@interface MPPowerFunction : MPFunction +/*! + @property baseValue + @brief The receiver's base value. + + @discussion The base value is the thing a suffix function applies to (e.g. + a power's base). + */ @property (nonatomic, strong) MPExpression *exponentExpression; @end diff --git a/MathPad/MPPowerFunction.m b/MathPad/MPPowerFunction.m index 742049a..5953196 100644 --- a/MathPad/MPPowerFunction.m +++ b/MathPad/MPPowerFunction.m @@ -9,9 +9,6 @@ #import "MPPowerFunction.h" #import "MPExpression.h" -#import "MPExpressionTree.h" - -#import "MPValueGroup.h" @implementation MPPowerFunction @@ -22,30 +19,6 @@ MPFunctionAccessorImplementation(ExponentExpression, _exponentExpression) return @[@"exponentExpression"]; } -- (BOOL)validate:(NSError *__autoreleasing *)error -{ - if (!self.baseValue) { - if (error) { - *error = MPParseError(11, - NSLocalizedString(@"No Base For Power.", @"Error message. This is displayed when a power does not have a base value."), - nil); - } - return NO; - } - return [self.baseValue validate:error] && [[self.exponentExpression parse] validate:error]; -} - -- (NSDecimalNumber *)evaluate -{ - NSDecimalNumber *base = [self.baseValue evaluate]; - NSDecimalNumber *exponent = [[self.exponentExpression parse] evaluate]; - if ([base isEqualToNumber:@(0)] && [exponent isEqualToNumber:@(0)]) { - // The C pow function returns 1 for pow(0, 0). Mathematically this should be undefined. - return [NSDecimalNumber notANumber]; - } - return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)]; -} - - (NSString *)description { return [NSString stringWithFormat:@"^%@", self.exponentExpression.description]; diff --git a/MathPad/MPPowerTerm.h b/MathPad/MPPowerTerm.h new file mode 100644 index 0000000..af169bc --- /dev/null +++ b/MathPad/MPPowerTerm.h @@ -0,0 +1,15 @@ +// +// MPPowerTerm.h +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunctionTerm.h" + +@interface MPPowerTerm : MPFunctionTerm + +@property (nonatomic, strong) MPTerm *baseTerm; + +@end diff --git a/MathPad/MPPowerTerm.m b/MathPad/MPPowerTerm.m new file mode 100644 index 0000000..a6e44bc --- /dev/null +++ b/MathPad/MPPowerTerm.m @@ -0,0 +1,36 @@ +// +// MPPowerTerm.m +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPPowerTerm.h" +#import "MPParsedExpression.h" + +@implementation MPPowerTerm + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *exponent = [[self expressionAtIndex:0] evaluate:error]; + if (!exponent) { + return nil; + } + NSDecimalNumber *base = [self.baseTerm evaluate:error]; + if (!base) { + return nil; + } + if ([base isEqualToNumber:@(0)] && [exponent isEqualToNumber:@(0)]) { + // The C pow function returns 1 for pow(0, 0). Mathematically this should be undefined. + if (error) { + *error = [NSError errorWithDomain:MPMathKitErrorDomain + code:101 + userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"0 to the power of 0 is undefined.", nil)}]; + } + return nil; + } + return [[NSDecimalNumber alloc] initWithDouble:pow(base.doubleValue, exponent.doubleValue)]; +} + +@end diff --git a/MathPad/MPProductTerm.h b/MathPad/MPProductTerm.h new file mode 100644 index 0000000..b830563 --- /dev/null +++ b/MathPad/MPProductTerm.h @@ -0,0 +1,17 @@ +// +// MPProductTerm.h +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" + +@interface MPProductTerm : MPTerm + +- (instancetype)initWithFactors:(NSArray *)factors; /* designated initializer */ + +@property (readonly, nonatomic, strong) NSArray *factors; + +@end diff --git a/MathPad/MPProductTerm.m b/MathPad/MPProductTerm.m new file mode 100644 index 0000000..3dfc636 --- /dev/null +++ b/MathPad/MPProductTerm.m @@ -0,0 +1,50 @@ +// +// MPProductTerm.m +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPProductTerm.h" + +#import "MPParsedExpression.h" +#import "MPPowerFunction.h" + +#import "MPFunctionTerm.h" +#import "MPElementaryFunctionTerm.h" +#import "MPNumber.h" +#import "MPFactorialTerm.h" +#import "MPPowerTerm.h" +#import "MPVariable.h" + +#import "MPTokenStream.h" +#import "MPToken.h" + +@implementation MPProductTerm + +- (instancetype)initWithFactors:(NSArray *)factors +{ + self = [super init]; + if (self) { + NSAssert(factors != nil, @"factors must not be nil."); + NSAssert(factors.count > 0, @"factors must not be empty."); + _factors = factors; + } + return self; +} + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *value = [NSDecimalNumber one]; + for (MPTerm *factor in self.factors) { + NSDecimalNumber *currentValue = [factor evaluate:error]; + if (!currentValue) { + return nil; + } + value = [value decimalNumberByMultiplyingBy:currentValue]; + } + return value; +} + +@end diff --git a/MathPad/MPSumFunction.m b/MathPad/MPSumFunction.m index c6f7661..184d996 100644 --- a/MathPad/MPSumFunction.m +++ b/MathPad/MPSumFunction.m @@ -8,8 +8,8 @@ #import "MPSumFunction.h" +#import "MPSumFunctionTerm.h" #import "MPExpression.h" -#import "MPExpressionTree.h" #import "MPEvaluationContext.h" @@ -27,54 +27,16 @@ MPFunctionAccessorImplementation(SumExpression, _sumExpression) return @[@"startExpression", @"targetExpression", @"sumExpression"]; } - -#pragma mark Evaluating Functions - - -- (BOOL)validate:(NSError *__autoreleasing *)error +- (BOOL)expectsVariableDefinitionInChildAtIndex:(NSUInteger)index { - MPExpressionTree *startTree = [self.startExpression parse]; - if (![startTree validateExpectingVariableDefinition:YES error:error]) { - return NO; - } - - MPExpressionTree *targetTree = [self.targetExpression parse]; - if (![targetTree validate:error]) { - return NO; - } - - [[MPEvaluationContext sharedContext] push]; - [[MPEvaluationContext sharedContext] defineVariable:startTree.definedVariable withValue:[NSDecimalNumber notANumber]]; - MPExpressionTree *sumTree = [self.sumExpression parse]; - if (![sumTree validate:error]) { - return NO; - } - [[MPEvaluationContext sharedContext] pop]; - return YES; + return index == 0; } - -- (NSDecimalNumber *)evaluate +- (Class)functionTermClass { - MPExpressionTree *startTree = [self.startExpression parse]; - NSDecimalNumber *start = [startTree evaluate]; - NSDecimalNumber *target = [[self.targetExpression parse] evaluate]; - MPExpressionTree *sumTree = [self.sumExpression parse]; - - NSDecimalNumber *value = [NSDecimalNumber zero]; - - [[MPEvaluationContext sharedContext] push]; - for (NSDecimalNumber *current = start; - [current compare:target] <= 0; - current = [current decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) { - [[MPEvaluationContext sharedContext] defineVariable:startTree.definedVariable withValue:current]; - value = [value decimalNumberByAdding:[sumTree evaluate]]; - } - [[MPEvaluationContext sharedContext] pop]; - return value; + return [MPSumFunctionTerm class]; } - #pragma mark Working With Functions diff --git a/MathPad/MPSumFunctionTerm.h b/MathPad/MPSumFunctionTerm.h new file mode 100644 index 0000000..34f31f8 --- /dev/null +++ b/MathPad/MPSumFunctionTerm.h @@ -0,0 +1,13 @@ +// +// MPSumFunctionTerm.h +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPFunctionTerm.h" + +@interface MPSumFunctionTerm : MPFunctionTerm + +@end diff --git a/MathPad/MPSumFunctionTerm.m b/MathPad/MPSumFunctionTerm.m new file mode 100644 index 0000000..95866ed --- /dev/null +++ b/MathPad/MPSumFunctionTerm.m @@ -0,0 +1,38 @@ +// +// MPSumFunctionTerm.m +// MathPad +// +// Created by Kim Wittenburg on 15.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPSumFunctionTerm.h" + +@implementation MPSumFunctionTerm + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + MPParsedExpression *startExpression = [self expressionAtIndex:0]; + MPParsedExpression *targetExpression = [self expressionAtIndex:1]; + MPParsedExpression *sumExpression = [self expressionAtIndex:2]; + + NSDecimalNumber *start = [startExpression evaluate:error]; + NSDecimalNumber *target = [targetExpression evaluate:error]; + NSDecimalNumber *value = [NSDecimalNumber zero]; + + for (NSDecimalNumber *current = start; + [current compare:target] <= 0; + current = [current decimalNumberByAdding:[[NSDecimalNumber alloc] initWithInteger:1]]) { + if (![self redefineVariable:startExpression.definedVariable value:current error:error]) { + return nil; + } + NSDecimalNumber *currentValue = [sumExpression evaluate:error]; + if (!currentValue) { + return nil; + } + value = [value decimalNumberByAdding:currentValue]; + } + return value; +} + +@end diff --git a/MathPad/MPSumTerm.h b/MathPad/MPSumTerm.h new file mode 100644 index 0000000..25834fe --- /dev/null +++ b/MathPad/MPSumTerm.h @@ -0,0 +1,18 @@ +// +// MPSumTerm.h +// MathPad +// +// Created by Kim Wittenburg on 14.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" + +@interface MPSumTerm : MPTerm + +- (instancetype)initWithSummands:(NSArray *)summands; /* designated initializer */ + +@property (readonly, nonatomic, strong) NSArray *summands; + +@end + diff --git a/MathPad/MPSumTerm.m b/MathPad/MPSumTerm.m new file mode 100644 index 0000000..14e9322 --- /dev/null +++ b/MathPad/MPSumTerm.m @@ -0,0 +1,43 @@ +// +// MPSumTerm.m +// MathPad +// +// Created by Kim Wittenburg on 14.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPSumTerm.h" + +#import "MPParsedExpression.h" +#import "MPProductTerm.h" + +#import "MPTokenStream.h" +#import "MPToken.h" + +@implementation MPSumTerm + +- (instancetype)initWithSummands:(NSArray *)summands +{ + self = [super init]; + if (self) { + NSAssert(summands != nil, @"summands must not be nil."); + NSAssert(summands.count > 0, @"summands must not be empty."); + _summands = summands; + } + return self; +} + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *value = [NSDecimalNumber zero]; + for (MPTerm *summand in self.summands) { + NSDecimalNumber *currentValue = [summand evaluate:error]; + if (!currentValue) { + return nil; + } + value = [value decimalNumberByAdding:currentValue]; + } + return value; +} + +@end diff --git a/MathPad/MPTerm.h b/MathPad/MPTerm.h new file mode 100644 index 0000000..9aa9e08 --- /dev/null +++ b/MathPad/MPTerm.h @@ -0,0 +1,28 @@ +// +// MPTerm.h +// MathPad +// +// Created by Kim Wittenburg on 11.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + + +@class MPTerm, MPTokenStream; +@protocol MPToken; + +@interface MPTerm : NSObject + +- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error; +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error; + +- (BOOL)defineVariable:(NSString *)variableName + value:(NSDecimalNumber *)value + error:(NSError *__autoreleasing *)error; +- (BOOL)redefineVariable:(NSString *)variableName + value:(NSDecimalNumber *)value + error:(NSError *__autoreleasing *)error; +- (NSDecimalNumber *)valueForVariable:(NSString *)variableName + error:(NSError *__autoreleasing *)error; +- (void)undefineVariable:(NSString *)variableName; // Should rarely be needed, defined variables are automatically undefined at the end of evaluation. + +@end \ No newline at end of file diff --git a/MathPad/MPTerm.m b/MathPad/MPTerm.m new file mode 100644 index 0000000..d20f2e1 --- /dev/null +++ b/MathPad/MPTerm.m @@ -0,0 +1,85 @@ +// +// MPTerm.m +// MathPad +// +// Created by Kim Wittenburg on 11.11.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPTerm.h" + +#import "MPParsedExpression.h" +#import "MPSumTerm.h" +#import "MPEvaluationContext.h" + +@implementation MPTerm + +- (NSDecimalNumber *)evaluate:(NSError *__autoreleasing *)error +{ + [[MPEvaluationContext sharedContext] push]; + NSDecimalNumber *result = [self doEvaluation:error]; + [[MPEvaluationContext sharedContext] pop]; + return result; +} + +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error +{ + NSLog(@"%@", self.class); + return nil; +} + +- (BOOL)defineVariable:(NSString *)variableName + value:(NSDecimalNumber *)value + error:(NSError *__autoreleasing *)error +{ + BOOL couldDefineVariable = [[MPEvaluationContext sharedContext] defineVariable:variableName + value:value]; + if (!couldDefineVariable) { + if (error) { + NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Redifinition of variable \"%@\".", nil), variableName]; + *error = [NSError errorWithDomain:MPMathKitErrorDomain + code:100 + userInfo:@{NSLocalizedDescriptionKey: localizedDescription}]; + } + } + return couldDefineVariable; +} + +- (BOOL)redefineVariable:(NSString *)variableName + value:(NSDecimalNumber *)value + error:(NSError *__autoreleasing *)error +{ + BOOL couldDefineVariable = [[MPEvaluationContext sharedContext] redefineVariable:variableName + value:value]; + if (!couldDefineVariable) { + if (error) { + NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Redifinition of variable \"%@\".", nil), variableName]; + *error = [NSError errorWithDomain:MPMathKitErrorDomain + code:100 + userInfo:@{NSLocalizedDescriptionKey: localizedDescription}]; + } + } + return couldDefineVariable; +} + +- (NSDecimalNumber *)valueForVariable:(NSString *)variableName + error:(NSError *__autoreleasing *)error +{ + NSDecimalNumber *value = [[MPEvaluationContext sharedContext] valueForVariable:variableName]; + if (!value) { + if (error) { + NSString *localizedDescription = [NSString stringWithFormat:NSLocalizedString(@"Undefined variable \"%@\".", nil), variableName]; + *error = [NSError errorWithDomain:MPMathKitErrorDomain + code:101 + userInfo:@{NSLocalizedDescriptionKey: localizedDescription}]; + } + } + return value; +} + +- (void)undefineVariable:(NSString *)variableName +{ + [[MPEvaluationContext sharedContext] undefineVariable:variableName]; +} + +@end diff --git a/MathPad/MPToken.h b/MathPad/MPToken.h index 49f8ac5..d117d8b 100644 --- a/MathPad/MPToken.h +++ b/MathPad/MPToken.h @@ -23,24 +23,11 @@ @constant MPOperatorListToken A list of operators (+ and - symbols). The token may be longer than the number of operators if there are spaces between them. - - @constant MPSinToken - The sin function. - - @constant MPCosToken - The cos function. - - @constant MPTanToken - The tan function. - - @constant MPASinToken - The asin function. - - @constant MPACosToken - The acos function. - - @constant MPATanToken - The atan function. + + @constant MPElementaryFunction + @em Most elementary functions are represented by this token type. + Elementary functions not categorized as such are those that can + not be represented as text (e.g. roots and powers). @constant MPNumberToken A number. This may be an integer or a floating point number. @@ -66,18 +53,15 @@ Any symbol that does not match any other token. */ typedef NS_ENUM(NSUInteger, MPTokenType) { + MPEOFToken = 0, MPMultiplicationSymbolToken, MPOperatorListToken, - MPSinToken, - MPCosToken, - MPTanToken, - MPASinToken, - MPACosToken, - MPATanToken, + MPElementaryFunctionToken, MPNumberToken, MPVariableToken, - MPFactorialToken, MPEqualsToken, + MPFactorialToken, + MPPowerToken, MPGenericFunctionToken, MPWhitespaceToken, diff --git a/MathPad/MPTokenStream.m b/MathPad/MPTokenStream.m index 42ea0d1..1d85393 100644 --- a/MathPad/MPTokenStream.m +++ b/MathPad/MPTokenStream.m @@ -18,7 +18,7 @@ @property (nonatomic) NSUInteger currentTokenIndex; @property (nonatomic) NSUInteger eofLocation; -@property (nonatomic) BOOL *whitespaceIgnores; +@property (nonatomic) BOOL *whitespaceStates; @property (nonatomic) NSUInteger maxWhitespaceIgnores; @property (nonatomic) NSUInteger currentWhitespaceIgnoreState; @@ -34,7 +34,7 @@ self = [super init]; if (self) { self.tokens = tokens; - self.whitespaceIgnores = malloc(10 * sizeof(BOOL)); + self.whitespaceStates = malloc(10 * sizeof(BOOL)); self.maxWhitespaceIgnores = 10; self.currentWhitespaceIgnoreState = 0; [self beginAcceptingWhitespaceTokens]; @@ -66,14 +66,14 @@ self.currentWhitespaceIgnoreState++; if (self.currentWhitespaceIgnoreState >= self.maxWhitespaceIgnores) { self.maxWhitespaceIgnores += 10; - BOOL *reallocatedWhitespaceIgnores = realloc(self.whitespaceIgnores, self.maxWhitespaceIgnores); + BOOL *reallocatedWhitespaceIgnores = realloc(self.whitespaceStates, self.maxWhitespaceIgnores); if (reallocatedWhitespaceIgnores) { - self.whitespaceIgnores = reallocatedWhitespaceIgnores; + self.whitespaceStates = reallocatedWhitespaceIgnores; } else { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Realloc Failed" userInfo:nil]; } } - self.whitespaceIgnores[self.currentWhitespaceIgnoreState] = state; + self.whitespaceStates[self.currentWhitespaceIgnoreState] = state; } @@ -85,7 +85,7 @@ - (void)skipWhitespaces { - if (self.whitespaceIgnores[self.currentWhitespaceIgnoreState]) { + if (self.whitespaceStates[self.currentWhitespaceIgnoreState]) { return; } while (_currentTokenIndex < self.tokens.count) { @@ -93,7 +93,7 @@ if (token.tokenType != MPWhitespaceToken) { return; } - [self currentTokenConsumed]; + _currentTokenIndex++; } } @@ -134,7 +134,7 @@ - (void)dealloc { - free(self.whitespaceIgnores); + free(self.whitespaceStates); } @end diff --git a/MathPad/MPVariable.h b/MathPad/MPVariable.h index bb801d3..c12ed26 100644 --- a/MathPad/MPVariable.h +++ b/MathPad/MPVariable.h @@ -6,11 +6,13 @@ // Copyright (c) 2014 Kim Wittenburg. All rights reserved. // -#import "MPValueGroup.h" +#import "MPTerm.h" @class MPVariable; -@interface MPVariable : NSObject +@interface MPVariable : MPTerm + +- (instancetype)initWithVariableName:(NSString *)variableName; /* designated initializer */ @property (readonly, nonatomic, strong) NSString *variableName; diff --git a/MathPad/MPVariable.m b/MathPad/MPVariable.m index 594002d..b29e0f3 100644 --- a/MathPad/MPVariable.m +++ b/MathPad/MPVariable.m @@ -8,63 +8,30 @@ #import "MPVariable.h" +#import "MPParsedExpression.h" + #import "MPTokenStream.h" #import "MPToken.h" #import "MPExpression.h" #import "MPEvaluationContext.h" -@implementation MPVariable { - NSRange _range; -} +@implementation MPVariable -- (instancetype)init +- (instancetype)initWithVariableName:(NSString *)variableName { self = [super init]; if (self) { + NSAssert(variableName != nil, @"variableName must not be nil."); + _variableName = variableName; } return self; } -- (instancetype)initWithTokenStream:(MPTokenStream *)tokenStream +- (NSDecimalNumber *)doEvaluation:(NSError *__autoreleasing *)error { - self = [self init]; - if (self) { - [tokenStream beginIgnoringWhitespaceTokens]; - MPToken *token = tokenStream.currentToken; - if (token.tokenType == MPVariableToken) { - _variableName = token.stringValue; - _range = token.range; - } else { - @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Expected Variable" userInfo:nil]; - } - [tokenStream currentTokenConsumed]; - [tokenStream endIgnoringOrAcceptingWhitespaceTokens]; - } - return self; -} - -- (NSRange)range -{ - return _range; -} - -- (BOOL)validate:(NSError *__autoreleasing *)error -{ - if (![[MPEvaluationContext sharedContext] isVariableDefined:self.variableName]) { - if (error) { - *error = MPParseError(9, - NSLocalizedString(@"Undefined Variable.", @"Error message. This is displayed when an expression contains an undefined variable."), - nil); - } - return NO; - } - return YES; -} - -- (NSDecimalNumber *)evaluate -{ - return [[MPEvaluationContext sharedContext] valueForVariable:self.variableName]; + return [self valueForVariable:self.variableName + error:error]; } @end