346 lines
13 KiB
Objective-C
346 lines
13 KiB
Objective-C
//
|
|
// MPElementParser.m
|
|
// MathPad
|
|
//
|
|
// Created by Kim Wittenburg on 10.09.14.
|
|
// Copyright (c) 2014 Kim Wittenburg. All rights reserved.
|
|
//
|
|
|
|
#import "MPElementParser.h"
|
|
#import "NSRegularExpression+MPParsingAdditions.h"
|
|
#import "MPMathRules.h"
|
|
|
|
#import "MPParsedFactor.h"
|
|
#import "MPParsedNumber.h"
|
|
#import "MPParsedVariable.h"
|
|
#import "MPParsedOperator.h"
|
|
|
|
#define MPMultiplicationSymbol @"*"
|
|
|
|
@interface MPElementParser ()
|
|
|
|
@property (nonatomic) NSString *input;
|
|
@property (nonatomic) NSUInteger parsePosition;
|
|
|
|
- (BOOL)allowsImplicitMultiplication;
|
|
- (BOOL)maximumOperatorChainLength;
|
|
- (BOOL)maximumOperatorChainLengthInMultiplication;
|
|
- (BOOL)maximumOperatorChainLengthInFunction;
|
|
|
|
- (void)setError:(MPParseError *)error;
|
|
|
|
- (BOOL)isAtEnd;
|
|
|
|
- (NSRange)parseVariableDefinition:(NSString *__autoreleasing *)variableName;
|
|
|
|
- (NSRange)parseWhitespaces;
|
|
- (NSRange)parseMultiplicationSymbol;
|
|
- (MPParsedOperator *)parseOperators;
|
|
- (id<MPParsedFactor>)parseValue;
|
|
- (MPParsedNumber *)parseNumber;
|
|
- (MPParsedVariable *)parseVariable;
|
|
|
|
@end
|
|
|
|
@implementation MPElementParser {
|
|
MPParseError *__autoreleasing *outError;
|
|
}
|
|
|
|
#pragma mark Helpers
|
|
- (BOOL)allowsImplicitMultiplication
|
|
{
|
|
return [MPMathRules sharedRules].allowsImplicitMultiplication;
|
|
}
|
|
|
|
- (BOOL)maximumOperatorChainLength
|
|
{
|
|
return [MPMathRules sharedRules].maximumOperatorChainLength;
|
|
}
|
|
|
|
- (BOOL)maximumOperatorChainLengthInFunction
|
|
{
|
|
return [MPMathRules sharedRules].maximumOperatorChainLengthInFunction;
|
|
}
|
|
|
|
- (BOOL)maximumOperatorChainLengthInMultiplication
|
|
{
|
|
return [MPMathRules sharedRules].maximumOperatorChainLengthInMultiplication;
|
|
}
|
|
|
|
#pragma mark Parsing Methods
|
|
|
|
- (BOOL)isAtEnd
|
|
{
|
|
return self.parsePosition >= self.input.length;
|
|
}
|
|
|
|
- (void)setError:(MPParseError *)error
|
|
{
|
|
if (outError != NULL) {
|
|
*outError = error;
|
|
}
|
|
}
|
|
|
|
- (NSArray *)parseElement:(NSString *)string
|
|
previousProduct:(MPParsedProduct *)previousProduct
|
|
nextFactor:(id<MPParsedFactor>)nextFactor
|
|
error:(out MPParseError *__autoreleasing *)error
|
|
{
|
|
return [self parseElement:string
|
|
previousProduct:previousProduct
|
|
nextFactor:nextFactor
|
|
definesVariable:NO
|
|
definedVariable:NULL
|
|
error:error];
|
|
}
|
|
|
|
- (NSArray *)parseElement:(NSString *)string
|
|
previousProduct:(MPParsedProduct *)previousProduct
|
|
nextFactor:(id<MPParsedFactor>)nextFactor
|
|
definesVariable:(BOOL)flag
|
|
definedVariable:(NSString *__autoreleasing *)variableName
|
|
error:(out MPParseError *__autoreleasing *)error
|
|
{
|
|
self.input = string;
|
|
outError = error;
|
|
self.parsePosition = 0;
|
|
|
|
NSString *definedVariable;
|
|
NSRange variableDefinitionRange = [self parseVariableDefinition:&definedVariable];
|
|
if (flag) {
|
|
if (!definedVariable) {
|
|
self.error = MPParseError(NSMakeRange(0, 0), @"Expected Variable Definition");
|
|
return nil;
|
|
} else if (variableName) {
|
|
*variableName = definedVariable;
|
|
}
|
|
} else if (definedVariable) {
|
|
self.error = MPParseError(variableDefinitionRange, @"Unexpected Variable Definition");
|
|
return nil;
|
|
}
|
|
|
|
NSMutableArray *products = [[NSMutableArray alloc] init];
|
|
MPParsedProduct *currentProduct = previousProduct;
|
|
|
|
while (YES) {
|
|
[self parseWhitespaces];
|
|
|
|
NSRange multiplicationSymbolRange = [self parseMultiplicationSymbol];
|
|
BOOL hasMultiplicationSymbol = multiplicationSymbolRange.location != NSNotFound;
|
|
|
|
MPParsedOperator *operators = [self parseOperators];
|
|
|
|
// NSRange functionRange = ...
|
|
// BOOL hasFunction = functionRange.location != NSNotFound;
|
|
|
|
id<MPParsedFactor> value = [self parseValue];
|
|
|
|
|
|
if (!value.exists) {
|
|
if ([self isAtEnd] && nextFactor != nil) {
|
|
if (hasMultiplicationSymbol) {
|
|
if (operators.numberOfOperators > self.maximumOperatorChainLengthInFunction) {
|
|
self.error = MPParseError(operators.range, @"Too many operators in multiplication.");
|
|
return nil;
|
|
}
|
|
[currentProduct addFactor:operators];
|
|
} else if (operators.exists) {
|
|
if (operators.numberOfOperators > self.maximumOperatorChainLength) {
|
|
self.error = MPParseError(operators.range, @"Too many operators.");
|
|
return nil;
|
|
}
|
|
[products addObject:currentProduct];
|
|
currentProduct = [[MPParsedProduct alloc] init];
|
|
[currentProduct addFactor:operators];
|
|
} else if (self.allowsImplicitMultiplication) {
|
|
if (!currentProduct) {
|
|
currentProduct = [[MPParsedProduct alloc] init];
|
|
}
|
|
} else {
|
|
self.error = MPParseError(NSMakeRange(self.parsePosition, 0), @"Implicit Multiplication not allowed.");
|
|
return nil;
|
|
}
|
|
break;
|
|
} else if ([self isAtEnd]) {
|
|
if (hasMultiplicationSymbol || operators.exists) {
|
|
self.error = MPParseError(NSMakeRange(self.parsePosition, 0), @"Unexpected End. Expected Number.");
|
|
return nil;
|
|
}
|
|
} else {
|
|
self.error = MPParseError(NSMakeRange(self.parsePosition, 1), @"Unexpected Symbol. Expected Number.");
|
|
return nil;
|
|
}
|
|
break;
|
|
} else {
|
|
if (hasMultiplicationSymbol) {
|
|
if (currentProduct) {
|
|
if (operators.numberOfOperators > self.maximumOperatorChainLengthInMultiplication) {
|
|
self.error = MPParseError(operators.range, @"Too many operators in multiplication.");
|
|
return nil;
|
|
}
|
|
[currentProduct addFactor:operators];
|
|
[currentProduct addFactor:value];
|
|
} else {
|
|
self.error = MPParseError(multiplicationSymbolRange, @"Unexpected Symbol. Expected Number.");
|
|
return nil;
|
|
}
|
|
} else if (operators.exists) {
|
|
if (operators.numberOfOperators > self.maximumOperatorChainLength) {
|
|
self.error = MPParseError(operators.range, @"Too many operators.");
|
|
return nil;
|
|
}
|
|
if (currentProduct) {
|
|
[products addObject:currentProduct];
|
|
}
|
|
currentProduct = [[MPParsedProduct alloc] initWithFactor:operators];
|
|
[currentProduct addFactor:value];
|
|
} else if (!currentProduct) {
|
|
currentProduct = [[MPParsedProduct alloc] initWithFactor:value];
|
|
} else if (self.allowsImplicitMultiplication) {
|
|
[currentProduct addFactor:value];
|
|
} else {
|
|
self.error = MPParseError(NSMakeRange(value.range.location, 0), @"Implicit Multiplication not allowed.");
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
if (nextFactor) {
|
|
[currentProduct addFactor:nextFactor];
|
|
}
|
|
[products addObject:currentProduct];
|
|
return products;
|
|
}
|
|
|
|
static NSRegularExpression *variableDefinitionRegex;
|
|
- (NSRange)parseVariableDefinition:(NSString *__autoreleasing *)variableName
|
|
{
|
|
if (!variableDefinitionRegex) {
|
|
variableDefinitionRegex = [NSRegularExpression regularExpressionWithPattern:@"\\A\\s*([A-Za-z])\\s*=\\s*"
|
|
options:0
|
|
error:NULL];
|
|
}
|
|
|
|
NSTextCheckingResult *match = [variableDefinitionRegex firstMatchInString:self.input
|
|
fromIndex:self.parsePosition];
|
|
if (!match) {
|
|
return NSMakeRange(NSNotFound, 0);
|
|
}
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
NSRange variableDefinitionRange = [match rangeAtIndex:1];
|
|
*variableName = [self.input substringWithRange:variableDefinitionRange];
|
|
return variableDefinitionRange;
|
|
}
|
|
|
|
static NSRegularExpression *whitespaceRegex;
|
|
- (NSRange)parseWhitespaces
|
|
{
|
|
if (!whitespaceRegex) {
|
|
whitespaceRegex = [NSRegularExpression regularExpressionWithPattern:@"\\A\\s*"
|
|
options:0
|
|
error:NULL];
|
|
}
|
|
NSTextCheckingResult *match = [whitespaceRegex firstMatchInString:self.input
|
|
fromIndex:self.parsePosition];
|
|
if (match) {
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
return match.range;
|
|
} else {
|
|
return NSMakeRange(NSNotFound, 0);
|
|
}
|
|
}
|
|
|
|
static NSRegularExpression *multiplicationSymbolRegex;
|
|
- (NSRange)parseMultiplicationSymbol
|
|
{
|
|
if (!multiplicationSymbolRegex) {
|
|
NSString *multiplicationSymbolString = [NSRegularExpression escapedPatternForString:MPMultiplicationSymbol];
|
|
NSString *multiplicationSymbolRegexString = [NSString stringWithFormat:@"\\A\\s*(%@)\\s*", multiplicationSymbolString];
|
|
multiplicationSymbolRegex = [NSRegularExpression regularExpressionWithPattern:multiplicationSymbolRegexString
|
|
options:0
|
|
error:NULL];
|
|
}
|
|
NSTextCheckingResult *match = [multiplicationSymbolRegex firstMatchInString:self.input
|
|
fromIndex:self.parsePosition];
|
|
if (!match) {
|
|
return NSMakeRange(NSNotFound, 0);
|
|
}
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
return [match rangeAtIndex:1];
|
|
}
|
|
|
|
static NSRegularExpression *operatorsRegex;
|
|
- (MPParsedOperator *)parseOperators
|
|
{
|
|
if (!operatorsRegex) {
|
|
operatorsRegex = [NSRegularExpression regularExpressionWithPattern:@"\\A\\s*([+-](?:\\s*[+-])*)\\s*"
|
|
options:0
|
|
error:NULL];
|
|
}
|
|
NSTextCheckingResult *match = [operatorsRegex firstMatchInString:self.input
|
|
fromIndex:self.parsePosition];
|
|
NSRange matchRange;
|
|
if (!match) {
|
|
matchRange = NSMakeRange(NSNotFound, 0);
|
|
} else {
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
matchRange = [match rangeAtIndex:1];
|
|
}
|
|
return [[MPParsedOperator alloc] initWithRange:matchRange
|
|
inString:self.input];
|
|
}
|
|
|
|
- (id<MPParsedFactor>)parseValue
|
|
{
|
|
MPParsedNumber *parsedNumber = [self parseNumber];
|
|
if (!parsedNumber.exists) {
|
|
MPParsedVariable *parsedVariable = [self parseVariable];
|
|
if (parsedVariable.exists) {
|
|
return parsedVariable;
|
|
}
|
|
}
|
|
return parsedNumber;
|
|
}
|
|
|
|
- (MPParsedNumber *)parseNumber
|
|
{
|
|
NSString *decimalSeparatorRegexString = [NSRegularExpression escapedPatternForString:[[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]];
|
|
NSString *numberRegexFormat = [NSString stringWithFormat:@"\\A\\s*((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))\\s*", decimalSeparatorRegexString, decimalSeparatorRegexString];
|
|
NSRegularExpression *numberRegex = [NSRegularExpression regularExpressionWithPattern:numberRegexFormat
|
|
options:0
|
|
error:NULL];
|
|
NSTextCheckingResult *match = [numberRegex firstMatchInString:self.input
|
|
fromIndex:self.parsePosition];
|
|
NSRange matchRange;
|
|
if (!match) {
|
|
matchRange = NSMakeRange(NSNotFound, 0);
|
|
} else {
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
matchRange = [match rangeAtIndex:1];
|
|
}
|
|
return [[MPParsedNumber alloc] initWithRange:matchRange
|
|
inString:self.input];
|
|
}
|
|
|
|
static NSRegularExpression *variableRegex;
|
|
- (MPParsedVariable *)parseVariable
|
|
{
|
|
if (!variableRegex) {
|
|
variableRegex = [NSRegularExpression regularExpressionWithPattern:@"\\A\\s*([A-Za-z])\\s*"
|
|
options:0
|
|
error:NULL];
|
|
}
|
|
NSTextCheckingResult *match = [variableRegex firstMatchInString:self.input
|
|
fromIndex:self.parsePosition];
|
|
NSRange matchRange;
|
|
if (!match) {
|
|
matchRange = NSMakeRange(NSNotFound, 0);
|
|
} else {
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
matchRange = [match rangeAtIndex:1];
|
|
}
|
|
return [[MPParsedVariable alloc] initWithRange:matchRange
|
|
inString:self.input];
|
|
}
|
|
|
|
@end
|