-
Notifications
You must be signed in to change notification settings - Fork 6
Negative parsing fix #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Negative parsing fix #115
Changes from 14 commits
f2e78eb
f59e000
893e2e2
3aeddfa
d1284cb
268e26e
b67dd11
3f518ed
80fba5c
3895c2e
e2434b3
2f92642
7370e75
4865477
7dc7597
1861dd0
2b1af8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -163,6 +163,22 @@ function findBinaryFunction(tokens: ExprElement[], fn: string) { | |
| } | ||
| } | ||
|
|
||
| // Some minuses have been parsed as functions (i.e. an `ExprFunction` with a single argument). | ||
| // If they are preceded by something, we need to treat the expression as a unary minus. | ||
| function findBinaryMinusFunctions(tokens: ExprElement[]) { | ||
| for (let i = 1; i < tokens.length; i++) { | ||
| const token = tokens[i]; | ||
| if (token instanceof ExprFunction && token.fn === '−') { | ||
| const a = tokens[i - 1]; | ||
| const b = token.args[0]; | ||
|
|
||
| const args = [removeBrackets(a), removeBrackets(b)]; | ||
| tokens.splice(i - 1, 2, new ExprFunction('−', args)); | ||
| i -= 1; | ||
| } | ||
| } | ||
| } | ||
|
||
|
|
||
|
|
||
| // ----------------------------------------------------------------------------- | ||
| // Match Brackets | ||
|
|
@@ -241,12 +257,28 @@ function findAssociativeFunction(tokens: ExprElement[], symbol: string, implicit | |
| clearBuffer(); | ||
| result.push(t); | ||
| lastWasSymbol = false; | ||
| } else if (t instanceof ExprFunction && t.fn === '−') { | ||
| // We treat leading minuses as a special case, because they can be either multiplied (associative multiplication) | ||
| // or treated as subtraction (binary function). | ||
| if (lastWasSymbol && buffer.length) { | ||
| // When we have an explicit symbol (already parsed), we want to combine elements. | ||
| lastWasSymbol = false; | ||
| buffer.push(t); | ||
| clearBuffer(); | ||
| } else if (buffer.length) { | ||
| // When we have an implicit symbol, we want to keep the minus as a unary operator. | ||
| // We remove previous elements from the buffer, and keep the minus as a separate token in results. | ||
| clearBuffer(); | ||
| result.push(t); | ||
| } else { | ||
| // When we have no previous elements, we push the minus to the buffer to be combined with the next element. | ||
| buffer.push(t); | ||
| } | ||
| } else { | ||
| // If implicit=true, we allow implicit multiplication, except where the | ||
| // second factor is a number. For example, "3 5" is invalid. | ||
| const noImplicit = (!implicit || t instanceof ExprNumber); | ||
| if (buffer.length && !lastWasSymbol && | ||
| noImplicit) throw ExprError.invalidExpression(); | ||
| if (buffer.length && !lastWasSymbol && noImplicit) throw ExprError.invalidExpression(); | ||
| buffer.push(t); | ||
| lastWasSymbol = false; | ||
| } | ||
|
|
@@ -256,6 +288,8 @@ function findAssociativeFunction(tokens: ExprElement[], symbol: string, implicit | |
| return result; | ||
| } | ||
|
|
||
| /* Reduces an array of tokens into a single nested expression. | ||
| * For example, [2, +, 3] becomes a new ExprFunction('+', argument = [2, 3]). */ | ||
| export function collapseTerm(tokens: ExprElement[]): ExprElement { | ||
| // Filter out whitespace. | ||
| tokens = tokens.filter(t => !(t instanceof ExprSpace)); | ||
|
|
@@ -272,9 +306,9 @@ export function collapseTerm(tokens: ExprElement[]): ExprElement { | |
| } | ||
|
|
||
| // Match percentage and factorial operators. | ||
| if (isOperator(tokens[0], '%!')) throw ExprError.startOperator(tokens[0]); | ||
| if (isOperator(tokens[0], '% !')) throw ExprError.startOperator(tokens[0]); | ||
| for (let i = 0; i < tokens.length; ++i) { | ||
| if (!isOperator(tokens[i], '%!')) continue; | ||
| if (!isOperator(tokens[i], '% !')) continue; | ||
| tokens.splice(i - 1, 2, new ExprFunction((tokens[i] as ExprOperator).o, [tokens[i - 1]])); | ||
| i -= 1; | ||
| } | ||
|
|
@@ -296,19 +330,30 @@ export function collapseTerm(tokens: ExprElement[]): ExprElement { | |
| } | ||
| } | ||
|
|
||
| // Replace all operator minuses, not preceded by numbers, with functions. Each function takes only one argument, | ||
| // the next token in sequence. | ||
| // Move backwards to correctly handle nested expressions. For example, " - - a" should be parsed as function "−" with | ||
| // argument [function "-" with argument ["a"]]. | ||
| for (let i = tokens.length - 1; i >= 0; i--) { | ||
| // Treat ± as a minus. | ||
| if (isOperator(tokens[i], '− ±')) { | ||
| if (tokens[i - 1] instanceof ExprNumber) continue; | ||
|
|
||
| tokens.splice(i, 2, new ExprFunction('−', [tokens[i + 1]])); | ||
| } | ||
| } | ||
|
|
||
| // Match multiplication operators. | ||
| tokens = findAssociativeFunction(tokens, '× * ·', true); | ||
|
|
||
| // Match - and ± operators, including a unary -/± at the start of an expression. | ||
| if (isOperator(tokens[0], '− ±')) { | ||
| tokens.splice(0, 2, new ExprFunction((tokens[0] as ExprOperator).o, [tokens[1]])); | ||
| } | ||
| findBinaryFunction(tokens, '− ±'); | ||
| findBinaryFunction(tokens, '- −'); | ||
|
||
|
|
||
| // Match + operators. | ||
| if (isOperator(tokens[0], '+')) tokens = tokens.slice(1); | ||
| tokens = findAssociativeFunction(tokens, '+'); | ||
|
|
||
| findBinaryMinusFunctions(tokens); | ||
|
|
||
| if (tokens.length > 1) throw ExprError.invalidExpression(); | ||
| return tokens[0]; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, what do you think about rephrasing this comment to something like this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely clearer!