Skip to content

Commit 1861dd0

Browse files
committed
Combine separate binary merging fns into single fn
1 parent 7dc7597 commit 1861dd0

File tree

3 files changed

+42
-13
lines changed

3 files changed

+42
-13
lines changed

src/parser.ts

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,19 +163,49 @@ function findBinaryFunction(tokens: ExprElement[], fn: string) {
163163
}
164164
}
165165

166-
// Some minuses are initially parsed as unary functions (i.e. the string "- b" is parsed an `ExprFunction` with
167-
// `.fn` = "−" and an `ExprIdentifier` "b" as its single argument.)
168-
// If any unary minus function is preceded by another token, we need to merge the pair of tokens into a binary minus
169-
// function.
170-
function findBinaryMinusFunctions(tokens: ExprElement[]) {
166+
167+
/**
168+
* Merge any tokens with a subtraction into a single term. Subtraction can be in the form of ['a', '-', 'b'], where the
169+
* token is an operator. Alternatively, due to previously parsing unary minus, it can be in the form of ['a', {function
170+
* "-" args: "b"}]. This function merges both cases into a single term.
171+
* */
172+
function findBinarySubtractionFunctions(tokens: ExprElement[]) {
171173
for (let i = 1; i < tokens.length; i++) {
172174
const token = tokens[i];
173-
if (token instanceof ExprFunction && token.fn === '−') {
175+
176+
// This is the case when we have something like ["a", "-", "b"].
177+
if (isOperator(token, '- −')) {
178+
const a = tokens[i - 1];
179+
const b = tokens[i + 1];
180+
181+
if (a instanceof ExprOperator) {
182+
throw ExprError.consecutiveOperators(a.o, token.o);
183+
}
184+
if (b instanceof ExprOperator) {
185+
throw ExprError.consecutiveOperators(token.o, b.o);
186+
}
187+
188+
const args = [removeBrackets(a), removeBrackets(b)];
189+
tokens.splice(i - 1, 3, new ExprFunction('−', args));
190+
i -= 2;
191+
}
192+
193+
// This is the case when have already parsed subtraction ExprFunctions somewhere in the expression, not preceded by
194+
// a number. For example, we may have something like [ExprIdentifier: 'x', {function '-' args: 'b'}] or ['a', '+',
195+
// {function: '-', args: ['b']}].
196+
if (token instanceof ExprFunction && token.fn === '−' && token.args.length === 1) {
174197
const a = tokens[i - 1];
175198
const b = token.args[0];
176199

177200
const args = [removeBrackets(a), removeBrackets(b)];
178-
tokens.splice(i - 1, 2, new ExprFunction('−', args));
201+
if (a instanceof ExprOperator) {
202+
// This can happen if we have a '+' before a '-'. Here, we have something like ['a', '+', {function '-' args:
203+
// 'b'}]. In this case, we merge to something of the form ['a', {function '+' args: [{function '-',
204+
// args['b']}]}].
205+
tokens.splice(i - 1, 2, new ExprFunction(a.o, [token]));
206+
} else {
207+
tokens.splice(i - 1, 2, new ExprFunction('−', args));
208+
}
179209
i -= 1;
180210
}
181211
}
@@ -340,21 +370,18 @@ export function collapseTerm(tokens: ExprElement[]): ExprElement {
340370
// Treat ± as a minus.
341371
if (isOperator(tokens[i], '− ±')) {
342372
if (tokens[i - 1] instanceof ExprNumber) continue;
343-
344373
tokens.splice(i, 2, new ExprFunction('−', [tokens[i + 1]]));
345374
}
346375
}
347376

348377
// Match multiplication operators.
349378
tokens = findAssociativeFunction(tokens, '× * ·', true);
350379

351-
findBinaryFunction(tokens, '- −');
380+
findBinarySubtractionFunctions(tokens);
352381

353382
// Match + operators.
354383
if (isOperator(tokens[0], '+')) tokens = tokens.slice(1);
355-
tokens = findAssociativeFunction(tokens, '+');
356-
357-
findBinaryMinusFunctions(tokens);
384+
tokens = findAssociativeFunction(tokens, '+', true);
358385

359386
if (tokens.length > 1) throw ExprError.invalidExpression();
360387
return tokens[0];

test/evaluate-test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ tape('Order and Brackets', (test) => {
3535
test.equal(value('2 a b', {a: 3, b: 5}), 30);
3636
test.equal(value('2 + 3 + 5'), 10);
3737
test.equal(value('2 - 3 - 5'), -6);
38+
test.equal(value('2 - 3 - - 5'), 4);
3839
test.equal(value('-2 - 3 - 5'), -10);
3940
test.equal(value('2 + 3 * 5'), 17);
4041
test.equal(value('2 * 3 - 5'), 1);
@@ -46,6 +47,7 @@ tape('Order and Brackets', (test) => {
4647
test.equal(value('- - 2'), 2);
4748
test.equal(value('3 * - 2'), -6);
4849
test.equal(value('3 + - 2'), 1);
50+
test.equal(value('3 + - 2 + - - 4'), 5);
4951
test.equal(value('-3 + - 2'), -5);
5052
test.equal(value('3 - - 2'), 5);
5153
test.equal(value('3 - - - 2'), 1);

test/parsing-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ tape('mixed numbers', (test) => {
128128
test.end();
129129
});
130130

131-
tape('Minus operators', (test) => {
131+
tape('Subtraction operators', (test) => {
132132
test.equal(expr('a * - b').collapse().toString(), 'a × (−b)');
133133
test.equal(expr('a + - b').collapse().toString(), 'a + (−b)');
134134
test.equal(expr('a - - b').collapse().toString(), 'a − (−b)');

0 commit comments

Comments
 (0)