Fundamental Redesign of Evaluation
This commit is contained in:
416
MathPad/MPExpressionParser.m
Normal file
416
MathPad/MPExpressionParser.m
Normal file
@@ -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<MPToken>)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<MPToken>)token // Returns YES if list is overall negative
|
||||
{
|
||||
NSString *operatorString = [[token.stringValue stringByReplacingOccurrencesOfString:@" " withString:@""]
|
||||
stringByReplacingOccurrencesOfString:@"+" withString:@""];
|
||||
return (operatorString.length & 1) == 1;
|
||||
}
|
||||
|
||||
- (NSDecimalNumber *)parseNumber:(id<MPToken>)token
|
||||
{
|
||||
return [NSDecimalNumber decimalNumberWithString:token.stringValue
|
||||
locale:[NSLocale currentLocale]];
|
||||
}
|
||||
|
||||
- (NSString *)parseVariable:(id<MPToken>)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
|
||||
Reference in New Issue
Block a user