From b6973dc24a9b69ebb6ba62291ee2cd8e31a2a508 Mon Sep 17 00:00:00 2001 From: Kim Wittenburg Date: Sat, 29 Nov 2014 00:19:52 +0100 Subject: [PATCH] Invalid Numbers Are Now a Syntax Error Syntax Errors Now Include a Range Syntax Errors Are Now Displayed in a Selectable List --- MathPad/MPExpressionParser.m | 38 +++++++++++++++++++++------------ MathPad/MPExpressionTokenizer.m | 21 +++++++++++------- MathPad/MPExpressionView.m | 33 +++++++++++++++------------- MathPad/MPParsedExpression.h | 10 ++++----- MathPad/MPParsedExpression.m | 2 +- MathPad/MPToken.h | 1 + 6 files changed, 62 insertions(+), 43 deletions(-) diff --git a/MathPad/MPExpressionParser.m b/MathPad/MPExpressionParser.m index 7e5e84c..89e7d34 100644 --- a/MathPad/MPExpressionParser.m +++ b/MathPad/MPExpressionParser.m @@ -25,7 +25,7 @@ #import "MPFactorialTerm.h" #define success() state = 0 -#define fail() state = -1 +#define fail() self.errorTokenIndex = self.currentTokenIndex; state = -1 @interface MPExpressionParser () @@ -85,13 +85,13 @@ if (flag && hasVariableDefinition) { result.definedVariable = variableName; } else if (flag) { - [self addErrorWithCode:3 localizedDescription:NSLocalizedString(@"Expected Variable Definition.", nil)]; + [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)]; + [self addErrorWithCode:4 localizedDescription:NSLocalizedString(@"Unexpected Variable Definition.", nil) useRange:NO]; if (errors) { *errors = self.errors; } @@ -102,7 +102,7 @@ [self skipWhitespaces]; if (self.currentTokenIndex >= self.tokens.count) { - [self addErrorWithCode:5 localizedDescription:NSLocalizedString(@"Empty Expression.", nil)]; + [self addErrorWithCode:5 localizedDescription:NSLocalizedString(@"Empty Expression.", nil) useRange:NO]; if (errors) { *errors = self.errors; } @@ -173,6 +173,7 @@ } state = 6; } else { + self.value = nil; state = 4; } break; @@ -191,7 +192,6 @@ [self.valueGroup addObject:self.value]; state = 5; } else { - self.errorTokenIndex = self.currentTokenIndex; fail(); } break; @@ -376,9 +376,10 @@ MPPowerTerm *term = [[MPPowerTerm alloc] initWithFunction:(MPFunction *)self.currentToken errors:&errors]; if (!term) { [self.errors addObjectsFromArray:errors]; + } else { + term.baseTerm = self.value; + self.value = term; } - term.baseTerm = self.value; - self.value = term; } [self nextToken]; } else { @@ -389,15 +390,22 @@ - (void)addErrorWithCode:(NSInteger)code localizedDescription:(NSString *)description + useRange:(BOOL)flag { - NSInteger errorIndex = [self.expression convertIndex:self.errorTokenIndex - fromReferenceFrame:MPTokenReferenceFrame - toReferenceFrame:MPSymbolReferenceFrame]; + 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, - MPErrorIndexKey: @(errorIndex)}]]; + MPErrorRangeKey: [NSValue valueWithRange:errorRange]}]]; } - (BOOL)errorOccured @@ -405,13 +413,15 @@ [self skipWhitespaces]; if (self.currentTokenIndex < self.tokens.count) { if (self.errorTokenIndex >= self.tokens.count) { - [self addErrorWithCode:0 localizedDescription:NSLocalizedString(@"Unexpected end. Expected Value.", nil)]; + [self addErrorWithCode:0 localizedDescription:NSLocalizedString(@"Unexpected end. Expected Value", nil) useRange:NO]; } else { id unexpectedToken = self.tokens[self.errorTokenIndex]; if (unexpectedToken.tokenType == MPPowerToken) { - [self addErrorWithCode:34 localizedDescription:NSLocalizedString(@"No base for Power", nil)]; + [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)]; + [self addErrorWithCode:1 localizedDescription:NSLocalizedString(@"Unexpected Symbol. Expected Value", nil) useRange:NO]; } } } diff --git a/MathPad/MPExpressionTokenizer.m b/MathPad/MPExpressionTokenizer.m index 665b7d2..c424bee 100644 --- a/MathPad/MPExpressionTokenizer.m +++ b/MathPad/MPExpressionTokenizer.m @@ -49,14 +49,15 @@ NSString *regexStringFormat = @"\\A(?:" @"([\\*∙⋅])|" @"([+-](?:\\s*[+-])*)|" - @"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|" + @"((?:\\d+%@(?!\\d+))|(?:(?:\\d*%@){2,}\\d*)|%@)|" // Substitute with decimal separator 3 times + @"((?:\\d+(?:%@\\d+)?)|(?:%@\\d+))|" // Substitute with decimal separator 2 times @"(sin|cos|tan|asin|acos|atan)|" @"([A-Za-z])|" @"(!)|" @"(=)|" @"(\\s+)" @")"; - NSString *regexString = [NSString stringWithFormat:regexStringFormat, decimalSeparator, decimalSeparator]; + NSString *regexString = [NSString stringWithFormat:regexStringFormat, decimalSeparator, decimalSeparator, decimalSeparator, decimalSeparator, decimalSeparator]; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString options:0 error:NULL]; @@ -69,12 +70,13 @@ if (match) { NSRange multiplicationSymbolRange = [match rangeAtIndex:1]; NSRange operatorRange = [match rangeAtIndex:2]; - NSRange numberRange = [match rangeAtIndex:3]; - NSRange elementaryFunctionRange = [match rangeAtIndex:4]; - NSRange variableRange = [match rangeAtIndex:5]; - NSRange factorialRange = [match rangeAtIndex:6]; - NSRange equalsRange = [match rangeAtIndex:7]; - NSRange whitespaceRange = [match rangeAtIndex:8]; + NSRange deformedNumberRange = [match rangeAtIndex:3]; + NSRange numberRange = [match rangeAtIndex:4]; + NSRange elementaryFunctionRange = [match rangeAtIndex:5]; + NSRange variableRange = [match rangeAtIndex:6]; + NSRange factorialRange = [match rangeAtIndex:7]; + NSRange equalsRange = [match rangeAtIndex:8]; + NSRange whitespaceRange = [match rangeAtIndex:9]; if (MPRangeExists(multiplicationSymbolRange)) { range = multiplicationSymbolRange; @@ -82,6 +84,9 @@ } else if (MPRangeExists(operatorRange)) { range = operatorRange; tokenType = MPOperatorListToken; + } else if (MPRangeExists(deformedNumberRange)) { + range = deformedNumberRange; + tokenType = MPDeformedNumberToken; } else if (MPRangeExists(numberRange)) { range = numberRange; tokenType = MPNumberToken; diff --git a/MathPad/MPExpressionView.m b/MathPad/MPExpressionView.m index b657b94..42c27ee 100644 --- a/MathPad/MPExpressionView.m +++ b/MathPad/MPExpressionView.m @@ -430,23 +430,25 @@ return; } self.syntaxErrorsPopUpButton.hidden = NO; - [self.syntaxErrorsPopUpButton addItemWithTitle:@""]; + NSString *title; if (syntaxErrors.count == 1) { - NSString *title = NSLocalizedString(@"1 Syntax Error", nil); - NSDictionary *attributes = @{NSForegroundColorAttributeName: [NSColor redColor], - NSFontAttributeName: [NSFont boldSystemFontOfSize:12.0]}; - NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:attributes]; - [self.syntaxErrorsPopUpButton itemAtIndex:0].attributedTitle = attributedTitle; + title = NSLocalizedString(@"1 Syntax Error", nil); } else { - NSString *title = [NSString stringWithFormat:NSLocalizedString(@"%ld Syntax Errors", nil), syntaxErrors.count]; - NSDictionary *attributes = @{NSForegroundColorAttributeName: [NSColor redColor], - NSFontAttributeName: [NSFont boldSystemFontOfSize:12.0]}; - NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:attributes]; - [self.syntaxErrorsPopUpButton itemAtIndex:0].attributedTitle = attributedTitle; + title = [NSString stringWithFormat:NSLocalizedString(@"%ld Syntax Errors", nil), syntaxErrors.count]; } + NSDictionary *attributes = @{NSForegroundColorAttributeName: [NSColor redColor], + NSFontAttributeName: [NSFont boldSystemFontOfSize:12.0]}; + NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:attributes]; + NSMenuItem *titleItem = [[NSMenuItem alloc] init]; + titleItem.attributedTitle = attributedTitle; + [self.syntaxErrorsPopUpButton.menu addItem:titleItem]; + NSUInteger index = 0; for (NSError *error in syntaxErrors) { - [self.syntaxErrorsPopUpButton addItemWithTitle:error.localizedDescription]; + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:error.localizedDescription + action:NULL + keyEquivalent:@""]; + [self.syntaxErrorsPopUpButton.menu addItem:item]; index++; } } @@ -538,8 +540,9 @@ { NSError *error = self.syntaxErrors[sender.indexOfSelectedItem-1]; NSIndexPath *pathToExpression = error.userInfo[MPPathToExpressionKey]; - pathToExpression = [pathToExpression indexPathByAddingIndex:[error.userInfo[MPErrorIndexKey] integerValue]]; - self.selection = MPMakeRangePath(pathToExpression, 0); + NSRange errorRange = [error.userInfo[MPErrorRangeKey] rangeValue]; + pathToExpression = [pathToExpression indexPathByAddingIndex:errorRange.location]; + self.selection = MPMakeRangePath(pathToExpression, errorRange.length); } #pragma mark Actions @@ -721,7 +724,7 @@ fromReferenceFrame:MPSymbolReferenceFrame toReferenceFrame:MPElementReferenceFrame]; NSIndexPath *functionPath = [self.selection.location indexPathByReplacingLastIndexWithIndex:functionElementIndex]; - self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:1] indexPathByAddingIndex:0], 0); + self.selection = MPMakeRangePath([[functionPath indexPathByAddingIndex:0] indexPathByAddingIndex:0], 0); } - (void)insertNewline:(id)sender diff --git a/MathPad/MPParsedExpression.h b/MathPad/MPParsedExpression.h index 56794e9..283d826 100644 --- a/MathPad/MPParsedExpression.h +++ b/MathPad/MPParsedExpression.h @@ -12,18 +12,18 @@ /*! - @const MPMathKitErrorDomain - @brief Predefined error domain for errors from the MathKit framework. + @const MPMathKitErrorDomain + @brief Predefined error domain for errors from the MathKit framework. - @discussion Errors in MathKit can occur during parsing of expressions or - during evaluation of expressions. These two can be distinguished + @discussion Errors in MathKit can occur during parsing of expressions or + during evaluation of expressions. These two can be distinguished by the error code. Parsing errors have lower error codes. Evaluation errors (math errors) have error codes from @c 100 upwards. */ FOUNDATION_EXPORT NSString *const MPMathKitErrorDomain; FOUNDATION_EXPORT NSString *const MPPathToExpressionKey; -FOUNDATION_EXPORT NSString *const MPErrorIndexKey; +FOUNDATION_EXPORT NSString *const MPErrorRangeKey; @interface MPParsedExpression : NSObject diff --git a/MathPad/MPParsedExpression.m b/MathPad/MPParsedExpression.m index 03671c1..7e6793f 100644 --- a/MathPad/MPParsedExpression.m +++ b/MathPad/MPParsedExpression.m @@ -14,7 +14,7 @@ NSString *const MPMathKitErrorDomain = @"MPMathKitErrorDomain"; NSString *const MPPathToExpressionKey = @"MPPathToExpressionKey"; -NSString *const MPErrorIndexKey = @"MPErrorIndexKey"; +NSString *const MPErrorRangeKey = @"MPErrorRangeKey"; @implementation MPParsedExpression diff --git a/MathPad/MPToken.h b/MathPad/MPToken.h index 96387be..952ae65 100644 --- a/MathPad/MPToken.h +++ b/MathPad/MPToken.h @@ -65,6 +65,7 @@ typedef NS_ENUM(NSUInteger, MPTokenType) { MPOperatorListToken, MPElementaryFunctionToken, MPNumberToken, + MPDeformedNumberToken, MPVariableToken, MPEqualsToken, MPFactorialToken,