// // 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