From 21bfe221ba0db7a5838ab13569d0a3171e79b974 Mon Sep 17 00:00:00 2001 From: Kim Wittenburg Date: Sun, 7 Sep 2014 16:45:31 +0200 Subject: [PATCH] Implemented Evaluation --- MathPad/MPElementParser.h | 18 ++++ MathPad/MPElementParser.m | 185 ++++++++++++++++++++++++++++++++ MathPad/MPExpression.h | 63 +++++++++-- MathPad/MPExpression.m | 11 +- MathPad/MPExpressionElement.h | 8 +- MathPad/MPExpressionEvaluator.h | 14 +-- MathPad/MPExpressionEvaluator.m | 176 +++++++++++++++++++++++++++--- MathPad/MPFunction.h | 3 +- MathPad/MPFunction.m | 24 +---- MathPad/MPParseError.h | 21 ++++ MathPad/MPParseError.m | 28 +++++ MathPad/MPParsedElement.h | 39 ++++--- MathPad/MPParsedElement.m | 99 +++++++++++++---- 13 files changed, 593 insertions(+), 96 deletions(-) create mode 100644 MathPad/MPElementParser.h create mode 100644 MathPad/MPElementParser.m create mode 100644 MathPad/MPParseError.h create mode 100644 MathPad/MPParseError.m diff --git a/MathPad/MPElementParser.h b/MathPad/MPElementParser.h new file mode 100644 index 0000000..3de2111 --- /dev/null +++ b/MathPad/MPElementParser.h @@ -0,0 +1,18 @@ +// +// MPElementParser.h +// MathPad +// +// Created by Kim Wittenburg on 06.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import +#import "MPParsedElement.h" +#import "MPParseError.h" + +@interface MPElementParser : NSObject + +- (MPParsedElement *)parseElement:(NSString *)string error:(MPParseError *__autoreleasing *)error; + +@end + diff --git a/MathPad/MPElementParser.m b/MathPad/MPElementParser.m new file mode 100644 index 0000000..059e6b2 --- /dev/null +++ b/MathPad/MPElementParser.m @@ -0,0 +1,185 @@ +// +// MPElementParser.m +// MathPad +// +// Created by Kim Wittenburg on 06.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPElementParser.h" + +@implementation MPElementParser { + NSScanner *scanner; +} + +- (MPParsedElement *)parseElement:(NSString *)string error:(MPParseError *__autoreleasing *)error +{ + // Setup the Scanner + scanner = [[NSScanner alloc] initWithString:string]; + scanner.charactersToBeSkipped = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + scanner.caseSensitive = YES; + + MPParsedElement *parsedElement = [[MPParsedElement alloc] init]; + + // Scan the defined variable + NSString *firstCharacters = nil; + BOOL hasLettersInFront = [scanner scanCharactersFromSet:[NSCharacterSet letterCharacterSet] + intoString:&firstCharacters]; + BOOL foundEquals = [scanner scanString:@"=" + intoString:NULL]; + + if (hasLettersInFront && firstCharacters.length == 1 && foundEquals) { + // Found variable definition + parsedElement.definedVariable = firstCharacters; + parsedElement.afterVariableDefinitionIndex = scanner.scanLocation; + } else { + // No variable definition found, reset the scanner + parsedElement.definedVariable = nil; + scanner.scanLocation = 0; + } + + // Scan the Prefix + BOOL startsWithAsterisk = [scanner scanString:@"*" + intoString:NULL]; + BOOL productClosed; + NSDecimalNumber *prefix = [self scanClosedProduct:&productClosed + error:NULL]; + + // Simple Factor + if (scanner.isAtEnd) { + parsedElement.isFactor = YES; + parsedElement.value = prefix == nil ? [NSDecimalNumber one] : prefix; + parsedElement.prefixOperatorExplicit = startsWithAsterisk; + parsedElement.suffixOperatorExplicit = !productClosed; + parsedElement.suffixIndex = scanner.scanLocation; + return parsedElement; + } else if (!productClosed) { + if (error) { + *error = MPParseError(scanner.scanLocation, @"Missing Number"); + } + return nil; + } + + parsedElement.prefixOperatorExplicit = startsWithAsterisk; + parsedElement.prefixValueExplicit = YES; + parsedElement.prefixMultiplicator = prefix; + + parsedElement.suffixValueExplicit = YES; + + // Scan Summands + NSDecimalNumber *value = [NSDecimalNumber zero]; + NSDecimalNumber *currentSummand; + while (!scanner.isAtEnd) { + + // Add every summand but the last one + if (currentSummand != nil) { + value = [value decimalNumberByAdding:currentSummand]; + } + + // Find the operator (+ or -) + NSString *operator; + BOOL found = [scanner scanString:@"+" + intoString:&operator]; + if (!found) { + found = [scanner scanString:@"-" + intoString:&operator]; + } + if (!found && !scanner.isAtEnd) { + // Two numbers separated by just a space + if (error) { + *error = MPParseError(scanner.scanLocation, @"Missing Operator"); + } + return nil; + } else if (!found) { + // Reached end of string + break; + } + + currentSummand = [self scanClosedProduct:&productClosed + error:error]; + + // No number was found + if (currentSummand == nil) { + // The string ends wit + or - + if (scanner.isAtEnd) { + productClosed = NO; + parsedElement.suffixValueExplicit = NO; + NSInteger suffix = [operator isEqualToString:@"+"] ? 1 : -1; + currentSummand = [[NSDecimalNumber alloc] initWithInteger:suffix]; + if (error) { + *error = nil; + } + break; + // Something else instead of a number + } else { + if (error) { + *error = MPParseError(scanner.scanLocation, @"Expected Number"); + } + return nil; + } + } + + // Process the current summand + if ([operator isEqualToString:@"-"]) { + currentSummand = [currentSummand decimalNumberByMultiplyingBy:[[NSDecimalNumber alloc] initWithInteger:-1]]; + } + + // The summand ends with a * + if (!productClosed) { + // The string ends with a * + if (scanner.isAtEnd) { + parsedElement.suffixValueExplicit = YES; + break; + // After a * followed something else than a number + } else { + if (error) { + *error = MPParseError(scanner.scanLocation, @"Unexpected Symbol"); + } + return nil; + } + } + } + + parsedElement.value = value; + parsedElement.suffixOperatorExplicit = !productClosed; + parsedElement.suffixMultiplicator = currentSummand; + parsedElement.suffixIndex = scanner.scanLocation; + + return parsedElement; +} + +- (NSDecimalNumber *)scanClosedProduct:(BOOL *)closed error:(MPParseError **)error +{ + NSDecimal decimal; + BOOL found = [scanner scanDecimal:&decimal]; + if (!found) { + if (error != NULL) { + *error = MPParseError(scanner.scanLocation, @"Unexpected Symbol"); + } + return nil; + } + NSDecimalNumber *product = [NSDecimalNumber decimalNumberWithDecimal:decimal]; + while (true) { + found = [scanner scanString:@"*" + intoString:NULL]; + if (found) { + found = [scanner scanDecimal:&decimal]; + if (found) { + product = [product decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithDecimal:decimal]]; + } else { + if (closed != NULL) { + *closed = NO; + } + return product; + } + } else { + break; + } + } + if (closed != NULL) { + *closed = YES; + } + return product; +} + +@end diff --git a/MathPad/MPExpression.h b/MathPad/MPExpression.h index 6862649..83fa305 100644 --- a/MathPad/MPExpression.h +++ b/MathPad/MPExpression.h @@ -9,7 +9,7 @@ @import Foundation; #import "NSString+MPExpressionElement.h" -@class MPExpression, MPFunction, MPRangePath, MPExpressionEvaluator; +@class MPExpression, MPFunction, MPRangePath, MPExpressionEvaluator, MPParseError; @protocol MPExpressionElement; /*! @@ -242,6 +242,26 @@ - (NSUInteger)indexOfElementAtSymbolLocation:(NSUInteger)location offset:(out NSUInteger *)offset; +/*! + @method locationOfElementAtIndex: + @brief Calculates the location of the element at @c index. + + @discussion @c index is an element index. Use this method to convert an + element index into the length reference frame. + + If the index exceeds the receiver's number of elements a @c + NSRangeException will be raised. + + @param index + The index of the element that is to be converted into the length + reference frame. + + @return The number of symbols (in the length reference frame) before the + element at @c index. + */ +- (NSUInteger)locationOfElementAtIndex:(NSUInteger)index; + + /*! @method replaceSymbolsInRange:withElements: @brief Replaces the elements in the given range with the contents of the @@ -271,28 +291,53 @@ /*! - @method elements - @brief Returns an array of all elements in the receiver. + @method elements + @brief Returns an array of all elements in the receiver. - @discussion The elements in the returned array are not copied before they are - returned. + @discussion The elements in the returned array are not copied before they are + returned. - @return An array of all elements from the receiver. + @return An array of all elements from the receiver. */ - (NSArray *)elements; #pragma mark Evaluating Expressions -- (double)evaluateExpression:(NSError *__autoreleasing *)error; + +/*! + @method evaluateWitError: + @brief Evaluates the receiving expression. + + @discussion This is a convenience method for evaluating an expression. If you + want more control over the evaluation process use the @c + evaluator property of the receiver. + + @param errror + If the receiver (or any of its elements) contains a syntax error + or can not be evaluated this parameter is set to an appropriate + value. Pass @c NULL if you are not interested in any errors that + might occur. + + @return The result of the evaluation or @c nil of the receiver could not + be evaluated. In that case the @c error parameter is set to an + appropriate value. + */ +- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error; -// TODO: Private? +/*! + @property evaluator + @brief Returns an object that can evaluate the receiver. + + @discussion To just evaluate an expression it is recommended to send it an + @c evaluateWithError: message. You can however use this property + instead if you need more control over the evaluation process. + */ @property (readonly, nonatomic, strong) MPExpressionEvaluator *evaluator; #pragma mark Notifications // All notification methods should create a new rangePath with the receiver's index added to the beginning of the path and then ascend the message to it's parent -// TODO: More notifications /*! @method didChangeElementsInRangePath:replacementLength: diff --git a/MathPad/MPExpression.m b/MathPad/MPExpression.m index bdae3f7..5d9e8c6 100644 --- a/MathPad/MPExpression.m +++ b/MathPad/MPExpression.m @@ -221,6 +221,15 @@ return elementIndex; } +- (NSUInteger)locationOfElementAtIndex:(NSUInteger)index +{ + NSUInteger location = 0; + for (NSUInteger i = 0; i < index; i++) { + location += [self elementAtIndex:i].length; + } + return location; +} + - (void)replaceSymbolsInRange:(NSRange)range withElements:(NSArray *)elements { @@ -268,7 +277,7 @@ return _elements; } -- (double)evaluateExpression:(NSError *__autoreleasing *)error +- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error { return [self.evaluator evaluateWithError:error]; } diff --git a/MathPad/MPExpressionElement.h b/MathPad/MPExpressionElement.h index 1aa7f57..652b1db 100644 --- a/MathPad/MPExpressionElement.h +++ b/MathPad/MPExpressionElement.h @@ -8,6 +8,8 @@ @import Foundation; +#import "MPParseError.h" + @protocol MPExpressionElement - (BOOL)isString; @@ -15,10 +17,6 @@ - (NSUInteger)length; -- (double)doubleValue; -- (float)floatValue; -- (int)intValue; -- (NSInteger)integerValue; -- (long long)longLongValue; +- (NSDecimalNumber *)evaluate:(MPParseError *__autoreleasing *)error; @end diff --git a/MathPad/MPExpressionEvaluator.h b/MathPad/MPExpressionEvaluator.h index b14d055..7313660 100644 --- a/MathPad/MPExpressionEvaluator.h +++ b/MathPad/MPExpressionEvaluator.h @@ -8,23 +8,25 @@ #import #import "MPExpression.h" +#import "MPElementParser.h" @class MPExpressionEvaluator, MPExpression, MPParsedElement; @interface MPExpressionEvaluator : NSObject +// Do not instanciate yourself, use evaluator property of MPExpression instead - (instancetype)initWithExpression:(MPExpression *)expression; -@property (readonly, nonatomic, strong) MPExpression *expression; - -#pragma mark Lexing -- (MPParsedElement *)structuredRepresentationOfElementAtIndex:(NSUInteger)index; +@property (readonly, nonatomic, weak) MPExpression *expression; #pragma mark Evaluating Expressions @property (readonly, nonatomic, strong) NSDictionary *variableBindings; -- (void)bindValue:(double)value toVariableName:(NSString *)name; +- (void)bindValue:(NSDecimalNumber *)value toVariableName:(NSString *)name; +- (void)unbindVariableName:(NSString *)name; -- (double)evaluateWithError:(NSError *__autoreleasing *)error; +- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error; +- (NSDecimalNumber *)evaluateVariableDefinition:(BOOL)flag error:(MPParseError *__autoreleasing *)error; +@property (readonly, nonatomic, strong) NSString *definedVariable; @end diff --git a/MathPad/MPExpressionEvaluator.m b/MathPad/MPExpressionEvaluator.m index d05081a..1ab29df 100644 --- a/MathPad/MPExpressionEvaluator.m +++ b/MathPad/MPExpressionEvaluator.m @@ -9,11 +9,13 @@ #import "MPExpressionEvaluator.h" #import "MPExpression.h" -#import "MPParsedElement.h" -#import "MPMath.h" +@interface MPExpressionEvaluator () +@property (readwrite, nonatomic, strong) NSString *definedVariable; +@end @implementation MPExpressionEvaluator { NSMutableDictionary *_variableBindings; + MPElementParser *parser; } - (id)initWithExpression:(MPExpression *)expression { @@ -21,36 +23,176 @@ if (self) { _expression = expression; _variableBindings = [[NSMutableDictionary alloc] init]; + parser = [[MPElementParser alloc] init]; } return self; } -#pragma mark Lexing -- (MPParsedElement *)structuredRepresentationOfElementAtIndex:(NSUInteger)index -{ - NSString *string = (NSString *)[self.expression elementAtIndex:index]; - YY_BUFFER_STATE buffer = yy_scan_string(string.UTF8String); - yyparse(); - yy_delete_buffer(buffer); - return [parseResult copy]; -} - #pragma mark Evaluating Expressions - (NSDictionary *)variableBindings { return [_variableBindings copy]; } -- (void)bindValue:(double)value toVariableName:(NSString *)name +- (void)bindValue:(NSDecimalNumber *)value toVariableName:(NSString *)name { - [_variableBindings setObject:@(value) + [_variableBindings setObject:value forKey:name]; } -- (double)evaluateWithError:(NSError *__autoreleasing *)error +- (void)unbindVariableName:(NSString *)name { - MPParsedElement *element = [self structuredRepresentationOfElementAtIndex:0]; - return element.standaloneValue; + [_variableBindings removeObjectForKey:name]; +} + +- (NSDecimalNumber *)evaluateWithError:(MPParseError *__autoreleasing *)error +{ + return [self evaluateVariableDefinition:NO error:error]; +} + +- (NSDecimalNumber *)evaluateVariableDefinition:(BOOL)flag error:(MPParseError *__autoreleasing *)error +{ +#warning Implement Cache + // Empty Expression + if (self.expression.numberOfElements == 0) { + if (error) { + *error = MPParseError(0, @"Empty Expression"); + } + return nil; + } + + // Expression of just one element + if (self.expression.numberOfElements == 1) { + id singleElement = [self.expression elementAtIndex:0]; + if ([singleElement isString]) { + MPParsedElement *parseResult = [parser parseElement:(NSString *)singleElement + error:error]; + if ([parseResult isValidStandaloneElement:error]) { + if (flag && parseResult.definedVariable) { + self.definedVariable = parseResult.definedVariable; + } else if (flag) { + if (error) { + *error = MPParseError(0, @"Expected Variable Definition"); + } + return nil; + } else if (parseResult.definedVariable) { + if (error) { + *error = MPParseError(0, @"Unexpected Variable Definition"); + } + return nil; + } + return parseResult.standaloneValue; + } else { + return nil; + } + } else { + return [singleElement evaluate:error]; + } + } + + // Expression with any number of elements + NSDecimalNumber *value = [NSDecimalNumber zero]; + NSDecimalNumber *currentSummand = nil; + + // Process the first element + id firstElement = [self.expression elementAtIndex:0]; + if ([firstElement isString]) { + MPParsedElement *parseResult = [parser parseElement:(NSString *)firstElement + error:error]; + if (parseResult && [parseResult isValidElementAtBeginning:error]) { + if (flag && parseResult.definedVariable) { + self.definedVariable = parseResult.definedVariable; + } else if (flag) { + if (error) { + *error = MPParseError(0, @"Expected Variable Definition"); + } + return nil; + } else if (parseResult.definedVariable) { + if (error) { + *error = MPParseError(0, @"Unexpected Variable Definition"); + } + return nil; + } + if ([parseResult isFactor]) { + currentSummand = parseResult.value; + } else { + value = [parseResult valueAtBeginning]; + currentSummand = parseResult.suffixMultiplicator; + } + } + } else { + currentSummand = [firstElement evaluate:error]; + } + if (!currentSummand) { + return nil; + } + + // Process the elements between the first and last element + for (NSUInteger index = 1; index < self.expression.numberOfElements-1; index++) { + id element = [self.expression elementAtIndex:index]; + if ([element isString]) { + MPParsedElement *parseResult = [parser parseElement:(NSString *)element + error:error]; + if (parseResult) { + if (parseResult.definedVariable) { + if (error) { + *error = MPParseError(index, @"Unexpected Variable Definition"); + } + return nil; + } + if ([parseResult isFactor]) { + currentSummand = [currentSummand decimalNumberByMultiplyingBy:parseResult.value]; + } else { + currentSummand = [currentSummand decimalNumberByMultiplyingBy:parseResult.prefixMultiplicator]; + value = [[value decimalNumberByAdding:currentSummand] decimalNumberByAdding:parseResult.value]; + currentSummand = parseResult.suffixMultiplicator; + } + } else { + return nil; + } + } else { + NSDecimalNumber *result = [element evaluate:error]; + if (result) { + currentSummand = [currentSummand decimalNumberByMultiplyingBy:result]; + } else { + return nil; + } + } + } + + // Process the last element + id lastElement = [self.expression elementAtIndex:self.expression.numberOfElements-1]; + if ([lastElement isString]) { + MPParsedElement *parseResult = [parser parseElement:(NSString *)lastElement + error:error]; + if (parseResult && [parseResult isValidElementAtEnd:error]) { + if (parseResult.definedVariable) { + if (error) { + *error = MPParseError(0, @"Unexpected Variable Definition"); + } + return nil; + } + if ([parseResult isFactor]) { + currentSummand = [currentSummand decimalNumberByMultiplyingBy:parseResult.value]; + } else { + currentSummand = [currentSummand decimalNumberByMultiplyingBy:parseResult.prefixMultiplicator]; + value = [[value decimalNumberByAdding:currentSummand] decimalNumberByAdding:parseResult.value]; + currentSummand = parseResult.suffixMultiplicator; + } + } else { + return nil; + } + } else { + NSDecimalNumber *result = [lastElement evaluate:error]; + if (result) { + currentSummand = [currentSummand decimalNumberByMultiplyingBy:result]; + } else { + return nil; + } + } + value = [value decimalNumberByAdding:currentSummand]; + + return value; } @end diff --git a/MathPad/MPFunction.h b/MathPad/MPFunction.h index ca7ca9c..1acffbb 100644 --- a/MathPad/MPFunction.h +++ b/MathPad/MPFunction.h @@ -8,6 +8,7 @@ @import Foundation; #import "MPExpressionElement.h" +#import "MPParseError.h" @class MPFunction, MPExpression, MPRangePath; @@ -37,7 +38,7 @@ - (id)elementAtIndexPath:(NSIndexPath *)indexPath; #pragma mark Evaluating Functions -- (double)evaluateFunction:(NSError *__autoreleasing *)error; +- (NSDecimalNumber *)evaluate:(MPParseError *__autoreleasing *)error; #pragma mark Messages - (void)didChangeElementsInRangePath:(MPRangePath *)rangePath diff --git a/MathPad/MPFunction.m b/MathPad/MPFunction.m index c70f8d4..9ba535a 100644 --- a/MathPad/MPFunction.m +++ b/MathPad/MPFunction.m @@ -82,9 +82,9 @@ } #pragma mark Evaluating Functions -- (double)evaluateFunction:(NSError *__autoreleasing *)error +- (NSDecimalNumber *)evaluate:(MPParseError *__autoreleasing *)error { - return 0; + return [NSDecimalNumber zero]; } #pragma mark Notifications @@ -182,24 +182,4 @@ return 1; } -- (float)floatValue -{ - return (float)[self doubleValue]; -} - -- (int)intValue -{ - return (int)[self doubleValue]; -} - -- (NSInteger)integerValue -{ - return (NSInteger)[self doubleValue]; -} - -- (long long)longLongValue -{ - return (long long)[self doubleValue]; -} - @end diff --git a/MathPad/MPParseError.h b/MathPad/MPParseError.h new file mode 100644 index 0000000..4308536 --- /dev/null +++ b/MathPad/MPParseError.h @@ -0,0 +1,21 @@ +// +// MPParseError.h +// MathPad +// +// Created by Kim Wittenburg on 06.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import + +#define MPParseError(index,key) [[MPParseError alloc] initWithErrorIndex:index errorMessageKey:key] + +@interface MPParseError : NSObject + +- (instancetype)initWithErrorIndex:(NSUInteger)errorIndex + errorMessageKey:(NSString *)errorMessageKey; + +@property (nonatomic) NSUInteger errorIndex; +@property (nonatomic) NSString *localizedErrorMessage; + +@end \ No newline at end of file diff --git a/MathPad/MPParseError.m b/MathPad/MPParseError.m new file mode 100644 index 0000000..751ec20 --- /dev/null +++ b/MathPad/MPParseError.m @@ -0,0 +1,28 @@ +// +// MPParseError.m +// MathPad +// +// Created by Kim Wittenburg on 06.09.14. +// Copyright (c) 2014 Kim Wittenburg. All rights reserved. +// + +#import "MPParseError.h" + +@implementation MPParseError + +- (instancetype)initWithErrorIndex:(NSUInteger)errorIndex errorMessageKey:(NSString *)errorMessageKey +{ + self = [super init]; + if (self) { + _errorIndex = errorIndex; + _localizedErrorMessage = NSLocalizedString(errorMessageKey, nil); + } + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"MPParseError", self.errorIndex, self.localizedErrorMessage]; +} + +@end diff --git a/MathPad/MPParsedElement.h b/MathPad/MPParsedElement.h index 3463b6c..cba8878 100644 --- a/MathPad/MPParsedElement.h +++ b/MathPad/MPParsedElement.h @@ -8,32 +8,37 @@ #import -// DEBUG STUFF -// Simple Operator: +/-/* -// Simple Factor: * 5.4 * / * 5,4 / 5,4 * / 5,4 -// Independant Math: -1+2-3 +#import "MPParseError.h" @interface MPParsedElement : NSObject +@property (nonatomic, copy) NSString *definedVariable; +@property (nonatomic) NSUInteger afterVariableDefinitionIndex; // Only set if defineVariable != nil + @property (nonatomic) BOOL isFactor; -@property (nonatomic) double factor; -@property (nonatomic, strong) NSMutableArray *summands; +// If isFactor is YES this is the factor otherwise it is the independant summant +// of the element (may be 0). +@property (nonatomic) NSDecimalNumber *value; -@property (nonatomic) double prefixMultiplicator; -@property (nonatomic) BOOL hasPrefixMultiplicator; @property (nonatomic, getter = isPrefixOperatorExplicit) BOOL prefixOperatorExplicit; -@property (nonatomic) double suffixMultiplicator; -@property (nonatomic) BOOL hasSuffixMultiplicator; +@property (nonatomic, getter = isPrefixValueExplicit) BOOL prefixValueExplicit; +@property (nonatomic) NSDecimalNumber *prefixMultiplicator; -// No error checking done. The string parsed may for example end with an operator -- (double)valueAtBeginning; -- (double)valueAtEnd; -- (double)standaloneValue; +@property (nonatomic, getter = isSuffixOperatorExplicit) BOOL suffixOperatorExplicit; +@property (nonatomic, getter = isSuffixValueExplicit) BOOL suffixValueExplicit; +@property (nonatomic) NSDecimalNumber *suffixMultiplicator; +@property (nonatomic) NSUInteger suffixIndex; + +// No error checking done +- (NSDecimalNumber *)valueAtBeginning; +- (NSDecimalNumber *)valueAtEnd; +- (NSDecimalNumber *)standaloneValue; // For error checking -- (BOOL)isValidElementAtBeginning; -- (BOOL)isValidElementInBetween; -- (BOOL)isValidElementAtEnd; +- (BOOL)isValidElementAtBeginning:(MPParseError **)error; +- (BOOL)isValidStandaloneElement:(MPParseError **)error; +- (BOOL)isValidElementAtEnd:(MPParseError **)error; +- (BOOL)isValidVariableDefinition:(MPParseError **)error; @end diff --git a/MathPad/MPParsedElement.m b/MathPad/MPParsedElement.m index 9c61dec..179638e 100644 --- a/MathPad/MPParsedElement.m +++ b/MathPad/MPParsedElement.m @@ -14,46 +14,109 @@ { self = [super init]; if (self) { - _summands = [[NSMutableArray alloc] init]; } return self; } -- (double)standaloneValue +- (NSDecimalNumber *)valueAtBeginning +{ + NSDecimalNumber *value = self.value; + if (self.prefixValueExplicit) { + value = [value decimalNumberByAdding:self.prefixMultiplicator]; + } + return value; +} + +- (NSDecimalNumber *)valueAtEnd +{ + NSDecimalNumber *value = self.value; + if (self.suffixValueExplicit) { + value = [value decimalNumberByAdding:self.suffixMultiplicator]; + } + return value; +} + +- (NSDecimalNumber *)standaloneValue { if (self.isFactor) { - return self.factor; + return self.value; } - return self.prefixMultiplicator + [[self.summands valueForKeyPath:@"@sum.self"] doubleValue]; + NSDecimalNumber *value = self.value; + if (self.prefixValueExplicit) { + value = [value decimalNumberByAdding:self.prefixMultiplicator]; + } + if (self.suffixValueExplicit) { + value = [value decimalNumberByAdding:self.suffixMultiplicator]; + } + return value; } -- (BOOL)isValidElementAtBeginning -{ - return NO; -} - -- (BOOL)isValidElementInBetween +- (BOOL)isValidElementAtBeginning:(MPParseError *__autoreleasing *)error { + if (self.prefixOperatorExplicit) { + if (error) { + *error = MPParseError(0, @"Expected Number"); + } + return NO; + } return YES; } -- (BOOL)isValidElementAtEnd +- (BOOL)isValidStandaloneElement:(MPParseError *__autoreleasing *)error { - return NO; + return [self isValidElementAtBeginning:error] && [self isValidElementAtEnd:error]; } -#pragma mark NSCopying +- (BOOL)isValidElementAtEnd:(MPParseError *__autoreleasing *)error +{ + if (self.suffixOperatorExplicit) { + if (error) { + *error = MPParseError(self.suffixIndex, @"Expected Number"); + } + return NO; + } + return YES; +} + +- (BOOL)isValidVariableDefinition:(MPParseError *__autoreleasing *)error +{ + if (self.definedVariable == nil) { + if (error) { + *error = MPParseError(0, @"Expected Variable Definition"); + } + return NO; + } + return [self isValidElementAtBeginning:error]; +} + +#pragma mark - NSObject Overrides + +- (NSString *)description +{ + NSMutableString *description = [[NSMutableString alloc] initWithString:@"MPParsedElement<"]; + if (self.isFactor) { + [description appendFormat:@"factor=%@%@%@", self.prefixOperatorExplicit?@"*":@"", self.value, self.suffixOperatorExplicit?@"*":@""]; + } else { + [description appendFormat:@"prefix=%@%@ prefixValueExplicit=%@", self.prefixOperatorExplicit?@"*":@"", self.prefixMultiplicator, self.prefixValueExplicit?@"YES":@"NO"]; + [description appendFormat:@" suffix=%@%@ suffixValueExplicit=%@", self.suffixMultiplicator, self.suffixOperatorExplicit?@"*":@"", self.suffixValueExplicit?@"YES":@"NO"]; + [description appendFormat:@" value=%@", self.value]; + } + [description appendString:@">"]; + return [description copy]; +} + +#pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { MPParsedElement *copy = [[MPParsedElement allocWithZone:zone] init]; copy.isFactor = self.isFactor; - copy.factor = self.factor; - copy.summands = self.summands.mutableCopy; - copy.prefixMultiplicator = self.prefixMultiplicator; - copy.hasPrefixMultiplicator = self.hasPrefixMultiplicator; + copy.value = self.value; copy.prefixOperatorExplicit = self.prefixOperatorExplicit; + copy.prefixValueExplicit = self.prefixValueExplicit; + copy.prefixMultiplicator = self.prefixMultiplicator; + copy.suffixOperatorExplicit = self.suffixOperatorExplicit; + copy.suffixValueExplicit = self.suffixValueExplicit; copy.suffixMultiplicator = self.suffixMultiplicator; - copy.hasSuffixMultiplicator = self.hasSuffixMultiplicator; return copy; }