337 lines
13 KiB
Objective-C
337 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"
|
|
|
|
#define MPMultiplicationSymbol @"*"
|
|
|
|
@interface MPElementParser ()
|
|
|
|
@property (nonatomic) NSString *input;
|
|
@property (nonatomic) NSUInteger parsePosition;
|
|
|
|
- (void)setError:(MPParseError *)error;
|
|
|
|
- (BOOL)isAtEnd;
|
|
|
|
- (NSRange)parseVariableDefinition:(NSString *__autoreleasing *)variableName;
|
|
|
|
- (NSRange)parseWhitespaces;
|
|
- (NSRange)parseMultiplicationSymbol;
|
|
- (NSRange)parseOperators;
|
|
- (NSUInteger)countOperatorsInRange:(NSRange)range
|
|
multiplicator:(out NSDecimalNumber *__autoreleasing *)multiplicator;
|
|
- (NSRange)parseNumber;
|
|
- (NSDecimalNumber *)numberInRange:(NSRange)range;
|
|
|
|
@end
|
|
|
|
@implementation MPElementParser {
|
|
MPParseError *__autoreleasing *outError;
|
|
}
|
|
|
|
static BOOL useUserDefaults;
|
|
|
|
#pragma mark Creation Methods
|
|
|
|
+ (void)initialize
|
|
{
|
|
useUserDefaults = YES;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
self.allowsImplicitMultiplications = NO;
|
|
self.maximumOperatorChainLength = 2;
|
|
self.maximumOperatorChainLengthInMultiplication = 1;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark Properties
|
|
|
|
+ (BOOL)isUsingDefaultValuesFromUserDefaults
|
|
{
|
|
return useUserDefaults;
|
|
}
|
|
|
|
+ (void)setUsingDefaultValuesFromUserDefaults:(BOOL)flag
|
|
{
|
|
useUserDefaults = flag;
|
|
}
|
|
|
|
#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:(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:(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 (![self isAtEnd]) {
|
|
[self parseWhitespaces];
|
|
|
|
NSRange multiplicationSymbolRange = [self parseMultiplicationSymbol];
|
|
BOOL hasMultiplicationSymbol = multiplicationSymbolRange.location != NSNotFound;
|
|
|
|
NSRange operatorsRange = [self parseOperators];
|
|
BOOL hasOperators = operatorsRange.location != NSNotFound;
|
|
|
|
// NSRange functionRange = ...
|
|
// BOOL hasFunction = functionRange.location != NSNotFound;
|
|
|
|
NSRange numberRange = [self parseNumber];
|
|
BOOL hasNumber = numberRange.location != NSNotFound;
|
|
|
|
|
|
NSDecimalNumber *operatorMultiplicator;
|
|
NSUInteger operatorCount = [self countOperatorsInRange:operatorsRange
|
|
multiplicator:&operatorMultiplicator];
|
|
|
|
if (!hasNumber) {
|
|
if ([self isAtEnd] && nextFactor != nil) {
|
|
if (hasMultiplicationSymbol) {
|
|
if (operatorCount > self.maximumOperatorChainLengthInFunction) {
|
|
self.error = MPParseError(operatorsRange, @"Too many operators in multiplication.");
|
|
return nil;
|
|
}
|
|
[currentProduct addFactor:[MPParsedFactor factorWithDecimalNumber:operatorMultiplicator]];
|
|
} else if (hasOperators) {
|
|
if (operatorCount > self.maximumOperatorChainLength) {
|
|
self.error = MPParseError(operatorsRange, @"Too many operators.");
|
|
return nil;
|
|
}
|
|
[products addObject:currentProduct];
|
|
currentProduct = [[MPParsedProduct alloc] init];
|
|
[currentProduct addFactor:[MPParsedFactor factorWithDecimalNumber:[[NSDecimalNumber alloc] initWithUnsignedInteger:operatorCount]]];
|
|
} else if (self.allowsImplicitMultiplications) {
|
|
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]) {
|
|
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;
|
|
}
|
|
} else {
|
|
NSDecimalNumber *number = [self numberInRange:numberRange];
|
|
NSDecimalNumber *value = [operatorMultiplicator decimalNumberByMultiplyingBy:number];
|
|
|
|
if (hasMultiplicationSymbol) {
|
|
if (currentProduct) {
|
|
if (operatorCount > self.maximumOperatorChainLengthInMultiplication) {
|
|
self.error = MPParseError(operatorsRange, @"Too many operators in multiplication.");
|
|
return nil;
|
|
}
|
|
[currentProduct addFactor:[MPParsedFactor factorWithDecimalNumber:value]];
|
|
} else {
|
|
self.error = MPParseError(multiplicationSymbolRange, @"Unexpected Symbol. Expected Number.");
|
|
return nil;
|
|
}
|
|
} else if (hasOperators) {
|
|
if (operatorCount > self.maximumOperatorChainLength) {
|
|
self.error = MPParseError(operatorsRange, @"Too many operators.");
|
|
return nil;
|
|
}
|
|
if (currentProduct) {
|
|
[products addObject:currentProduct];
|
|
}
|
|
currentProduct = [[MPParsedProduct alloc] init];
|
|
[currentProduct addFactor:[MPParsedFactor factorWithDecimalNumber:value]];
|
|
} else if (!currentProduct) {
|
|
currentProduct = [[MPParsedProduct alloc] init];
|
|
[currentProduct addFactor:[MPParsedFactor factorWithDecimalNumber:value]];
|
|
} else if (self.allowsImplicitMultiplications) {
|
|
[currentProduct addFactor:[MPParsedFactor factorWithDecimalNumber:value]];
|
|
} else {
|
|
self.error = MPParseError(NSMakeRange(numberRange.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;
|
|
- (NSRange)parseOperators
|
|
{
|
|
if (!operatorsRegex) {
|
|
operatorsRegex = [NSRegularExpression regularExpressionWithPattern:@"\\A\\s*([+-](?:\\s*[+-])*)\\s*"
|
|
options:0
|
|
error:NULL];
|
|
}
|
|
NSTextCheckingResult *match = [operatorsRegex firstMatchInString:self.input
|
|
fromIndex:self.parsePosition];
|
|
if (match == nil) {
|
|
return NSMakeRange(NSNotFound, 0);
|
|
}
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
return [match rangeAtIndex:1];
|
|
}
|
|
|
|
- (NSUInteger)countOperatorsInRange:(NSRange)range
|
|
multiplicator:(out NSDecimalNumber *__autoreleasing *)outMultiplicator
|
|
{
|
|
if (range.location == NSNotFound) {
|
|
*outMultiplicator = [NSDecimalNumber one];
|
|
return 0;
|
|
}
|
|
NSString *operatorsString = [self.input substringWithRange:range];
|
|
NSString *operators = [[operatorsString componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:@""];
|
|
NSInteger multiplicator = 1;
|
|
for (NSUInteger characterIndex; characterIndex < operators.length; characterIndex++) {
|
|
if ([[operators substringWithRange:NSMakeRange(characterIndex, 1)] isEqualToString:@"-"]) {
|
|
multiplicator *= -1;
|
|
}
|
|
}
|
|
*outMultiplicator = [[NSDecimalNumber alloc] initWithInteger:multiplicator];
|
|
return operators.length;
|
|
}
|
|
|
|
- (NSRange)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];
|
|
if (!match) {
|
|
return NSMakeRange(NSNotFound, 0);
|
|
}
|
|
self.parsePosition = NSMaxRange(match.range);
|
|
return [match rangeAtIndex:1];
|
|
}
|
|
|
|
- (NSDecimalNumber *)numberInRange:(NSRange)range
|
|
{
|
|
NSString *numberString = [self.input substringWithRange:range];
|
|
return [NSDecimalNumber decimalNumberWithString:numberString];
|
|
}
|
|
|
|
@end
|