455 lines
14 KiB
Objective-C
455 lines
14 KiB
Objective-C
//
|
|
// MPExpressionParser.m
|
|
// MathKit
|
|
//
|
|
// Created by Kim Wittenburg on 16.11.14.
|
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
|
//
|
|
|
|
#import "MPExpressionParser.h"
|
|
|
|
#import "MPToken.h"
|
|
#import "MPExpression.h"
|
|
#import "MPParsedExpression.h"
|
|
|
|
#import "MPTerm.h"
|
|
#import "MPSumTerm.h"
|
|
#import "MPProductTerm.h"
|
|
#import "MPNegatedTerm.h"
|
|
#import "MPFunctionTerm.h"
|
|
#import "MPElementaryFunctionTerm.h"
|
|
#import "MPNumber.h"
|
|
#import "MPVariable.h"
|
|
#import "MPFactorialTerm.h"
|
|
#import "MPPowerTerm.h"
|
|
|
|
|
|
#define success() state = 0
|
|
#define fail() self.errorTokenIndex = self.currentTokenIndex; 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) useRange:NO];
|
|
if (errors) {
|
|
*errors = self.errors;
|
|
}
|
|
return nil;
|
|
} else if (hasVariableDefinition) {
|
|
[self addErrorWithCode:4 localizedDescription:NSLocalizedString(@"Unexpected Variable Definition.", nil) useRange:NO];
|
|
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) useRange:NO];
|
|
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 {
|
|
self.value = nil;
|
|
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 {
|
|
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) {
|
|
if (self.value) {
|
|
NSArray *errors;
|
|
MPPowerTerm *term = [[MPPowerTerm alloc] initWithFunction:(MPFunction *)self.currentToken errors:&errors];
|
|
if (!term) {
|
|
[self.errors addObjectsFromArray:errors];
|
|
} else {
|
|
term.baseTerm = self.value;
|
|
self.value = term;
|
|
}
|
|
}
|
|
[self nextToken];
|
|
} else {
|
|
checkMore = NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- (void)addErrorWithCode:(NSInteger)code
|
|
localizedDescription:(NSString *)description
|
|
useRange:(BOOL)flag
|
|
{
|
|
NSRange errorRange;
|
|
if (flag) {
|
|
errorRange = [self.tokens[self.errorTokenIndex] range];
|
|
} else {
|
|
NSInteger errorIndex = [self.expression convertIndex:self.errorTokenIndex
|
|
fromReferenceFrame:MPTokenReferenceFrame
|
|
toReferenceFrame:MPSymbolReferenceFrame];
|
|
errorRange = NSMakeRange(errorIndex, 0);
|
|
}
|
|
[self.errors addObject:[NSError errorWithDomain:MPMathKitErrorDomain
|
|
code:code
|
|
userInfo:@{NSLocalizedDescriptionKey: description,
|
|
MPPathToExpressionKey: self.expression.indexPath,
|
|
MPErrorRangeKey: [NSValue valueWithRange:errorRange]}]];
|
|
}
|
|
|
|
|
|
- (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) useRange:NO];
|
|
} else {
|
|
id<MPToken> unexpectedToken = self.tokens[self.errorTokenIndex];
|
|
if (unexpectedToken.tokenType == MPPowerToken) {
|
|
[self addErrorWithCode:34 localizedDescription:NSLocalizedString(@"No base for Power", nil) useRange:NO];
|
|
} else if (unexpectedToken.tokenType == MPDeformedNumberToken) {
|
|
[self addErrorWithCode:42 localizedDescription:NSLocalizedString(@"Not a Number", nil) useRange:YES];
|
|
} else {
|
|
[self addErrorWithCode:1 localizedDescription:NSLocalizedString(@"Unexpected Symbol. Expected Value", nil) useRange:NO];
|
|
}
|
|
}
|
|
}
|
|
return self.errors.count > 0;
|
|
}
|
|
|
|
@end
|