Skip to content

Commit 3515f7e

Browse files
ljqxariya
authored andcommitted
Parse nullish coalescing (fix #2039)
1 parent cbc8800 commit 3515f7e

11 files changed

+55
-81
lines changed

src/nodes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export class BinaryExpression {
159159
readonly left: Expression;
160160
readonly right: Expression;
161161
constructor(operator: string, left: Expression, right: Expression) {
162-
const logical = (operator === '||' || operator === '&&');
162+
const logical = (operator === '||' || operator === '&&' || operator === '??');
163163
this.type = logical ? Syntax.LogicalExpression : Syntax.BinaryExpression;
164164
this.operator = operator;
165165
this.left = left;

src/parser.ts

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -103,27 +103,28 @@ export class Parser {
103103
',': 0,
104104
'=': 0,
105105
']': 0,
106-
'||': 1,
107-
'&&': 2,
108-
'|': 3,
109-
'^': 4,
110-
'&': 5,
111-
'==': 6,
112-
'!=': 6,
113-
'===': 6,
114-
'!==': 6,
115-
'<': 7,
116-
'>': 7,
117-
'<=': 7,
118-
'>=': 7,
119-
'<<': 8,
120-
'>>': 8,
121-
'>>>': 8,
122-
'+': 9,
123-
'-': 9,
124-
'*': 11,
125-
'/': 11,
126-
'%': 11
106+
'??': 5,
107+
'||': 6,
108+
'&&': 7,
109+
'|': 8,
110+
'^': 9,
111+
'&': 10,
112+
'==': 11,
113+
'!=': 11,
114+
'===': 11,
115+
'!==': 11,
116+
'<': 12,
117+
'>': 12,
118+
'<=': 12,
119+
'>=': 12,
120+
'<<': 13,
121+
'>>': 13,
122+
'>>>': 13,
123+
'+': 14,
124+
'-': 14,
125+
'*': 15,
126+
'/': 15,
127+
'%': 15
127128
};
128129

129130
this.lookahead = {
@@ -1478,7 +1479,7 @@ export class Parser {
14781479
if (token.type === Token.Punctuator) {
14791480
precedence = this.operatorPrecedence[op] || 0;
14801481
} else if (token.type === Token.Keyword) {
1481-
precedence = (op === 'instanceof' || (this.context.allowIn && op === 'in')) ? 7 : 0;
1482+
precedence = (op === 'instanceof' || (this.context.allowIn && op === 'in')) ? 12 : 0;
14821483
} else {
14831484
precedence = 0;
14841485
}
@@ -1490,9 +1491,21 @@ export class Parser {
14901491

14911492
let expr = this.inheritCoverGrammar(this.parseExponentiationExpression);
14921493

1494+
let allowAndOr = true;
1495+
let allowNullishCoalescing = true;
1496+
const updateNullishCoalescingRestrictions = (token): void => {
1497+
if (token.value === '&&' || token.value === '||') {
1498+
allowNullishCoalescing = false;
1499+
}
1500+
if (token.value === '??') {
1501+
allowAndOr = false;
1502+
}
1503+
};
1504+
14931505
const token = this.lookahead;
14941506
let prec = this.binaryPrecedence(token);
14951507
if (prec > 0) {
1508+
updateNullishCoalescingRestrictions(token);
14961509
this.nextToken();
14971510

14981511
this.context.isAssignmentTarget = false;
@@ -1509,6 +1522,11 @@ export class Parser {
15091522
if (prec <= 0) {
15101523
break;
15111524
}
1525+
if ((!allowAndOr && (this.lookahead.value === '&&' || this.lookahead.value === '||')) ||
1526+
(!allowNullishCoalescing && this.lookahead.value === '??')) {
1527+
this.throwUnexpectedToken(this.lookahead);
1528+
}
1529+
updateNullishCoalescingRestrictions(this.lookahead);
15121530

15131531
// Reduce: make a binary expression from the three topmost entries.
15141532
while ((stack.length > 2) && (prec <= precedences[precedences.length - 1])) {

src/scanner.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,6 @@ export class Scanner {
602602
case '[':
603603
case ']':
604604
case ':':
605-
case '?':
606605
case '~':
607606
++this.index;
608607
break;
@@ -623,17 +622,20 @@ export class Scanner {
623622

624623
// 2-character punctuators.
625624
str = str.substr(0, 2);
626-
if (str === '&&' || str === '||' || str === '==' || str === '!=' ||
625+
if (str === '&&' || str === '||' || str === '??' ||
626+
str === '==' || str === '!=' ||
627627
str === '+=' || str === '-=' || str === '*=' || str === '/=' ||
628-
str === '++' || str === '--' || str === '<<' || str === '>>' ||
628+
str === '++' || str === '--' ||
629+
str === '<<' || str === '>>' ||
629630
str === '&=' || str === '|=' || str === '^=' || str === '%=' ||
630-
str === '<=' || str === '>=' || str === '=>' || str === '**') {
631+
str === '<=' || str === '>=' || str === '=>' ||
632+
str === '**') {
631633
this.index += 2;
632634
} else {
633635

634636
// 1-character punctuators.
635637
str = this.source[this.index];
636-
if ('<>=!+-*%&|^/'.indexOf(str) >= 0) {
638+
if ('<>=!+-*%&|?^/'.indexOf(str) >= 0) {
637639
++this.index;
638640
}
639641
}

src/tokenizer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class Reader {
3434
'&=', '|=', '^=', ',',
3535
// binary/unary operators
3636
'+', '-', '*', '**', '/', '%', '++', '--', '<<', '>>', '>>>', '&',
37-
'|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
37+
'|', '^', '!', '~', '&&', '||', '??', '?', ':', '===', '==', '>=',
3838
'<=', '<', '>', '!=', '!=='].indexOf(t) >= 0;
3939
}
4040

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"index":7,"lineNumber":1,"column":8,"message":"Error: Line 1: Unexpected token ??","description":"Unexpected token ??"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1 && 2 ?? 3
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"index":7,"lineNumber":1,"column":8,"message":"Error: Line 1: Unexpected token &&","description":"Unexpected token &&"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1 ?? 2 && 3
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"index":7,"lineNumber":1,"column":8,"message":"Error: Line 1: Unexpected token ||","description":"Unexpected token ||"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1 ?? 2 || 3

test/test-262-whitelist.txt

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15338,8 +15338,6 @@ test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-
1533815338
test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-assignment-expression-logical-or.js(strict mode)
1533915339
test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-await-expression.js(default)
1534015340
test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-await-expression.js(strict mode)
15341-
test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-expression-coalesce.js(default)
15342-
test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-expression-coalesce.js(strict mode)
1534315341
test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-integer-separators.js(default)
1534415342
test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-integer-separators.js(strict mode)
1534515343
test/language/expressions/class/cpn-class-expr-computed-property-name-from-assignment-expression-coalesce.js(default)
@@ -15350,8 +15348,6 @@ test/language/expressions/class/cpn-class-expr-computed-property-name-from-assig
1535015348
test/language/expressions/class/cpn-class-expr-computed-property-name-from-assignment-expression-logical-or.js(strict mode)
1535115349
test/language/expressions/class/cpn-class-expr-computed-property-name-from-await-expression.js(default)
1535215350
test/language/expressions/class/cpn-class-expr-computed-property-name-from-await-expression.js(strict mode)
15353-
test/language/expressions/class/cpn-class-expr-computed-property-name-from-expression-coalesce.js(default)
15354-
test/language/expressions/class/cpn-class-expr-computed-property-name-from-expression-coalesce.js(strict mode)
1535515351
test/language/expressions/class/cpn-class-expr-computed-property-name-from-integer-separators.js(default)
1535615352
test/language/expressions/class/cpn-class-expr-computed-property-name-from-integer-separators.js(strict mode)
1535715353
test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-additive-expression-add.js(default)
@@ -15558,46 +15554,6 @@ test/language/expressions/class/private-static-method-brand-check-multiple-evalu
1555815554
test/language/expressions/class/private-static-method-brand-check-multiple-evaluations-of-class-factory.js(strict mode)
1555915555
test/language/expressions/class/private-static-setter-multiple-evaluations-of-class-factory.js(default)
1556015556
test/language/expressions/class/private-static-setter-multiple-evaluations-of-class-factory.js(strict mode)
15561-
test/language/expressions/coalesce/abrupt-is-a-short-circuit.js(default)
15562-
test/language/expressions/coalesce/abrupt-is-a-short-circuit.js(strict mode)
15563-
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js(default)
15564-
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js(strict mode)
15565-
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js(default)
15566-
test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js(strict mode)
15567-
test/language/expressions/coalesce/chainable-with-bitwise-and.js(default)
15568-
test/language/expressions/coalesce/chainable-with-bitwise-and.js(strict mode)
15569-
test/language/expressions/coalesce/chainable-with-bitwise-or.js(default)
15570-
test/language/expressions/coalesce/chainable-with-bitwise-or.js(strict mode)
15571-
test/language/expressions/coalesce/chainable-with-bitwise-xor.js(default)
15572-
test/language/expressions/coalesce/chainable-with-bitwise-xor.js(strict mode)
15573-
test/language/expressions/coalesce/chainable.js(default)
15574-
test/language/expressions/coalesce/chainable.js(strict mode)
15575-
test/language/expressions/coalesce/follows-null.js(default)
15576-
test/language/expressions/coalesce/follows-null.js(strict mode)
15577-
test/language/expressions/coalesce/follows-undefined.js(default)
15578-
test/language/expressions/coalesce/follows-undefined.js(strict mode)
15579-
test/language/expressions/coalesce/short-circuit-number-0.js(default)
15580-
test/language/expressions/coalesce/short-circuit-number-0.js(strict mode)
15581-
test/language/expressions/coalesce/short-circuit-number-42.js(default)
15582-
test/language/expressions/coalesce/short-circuit-number-42.js(strict mode)
15583-
test/language/expressions/coalesce/short-circuit-number-empty-string.js(default)
15584-
test/language/expressions/coalesce/short-circuit-number-empty-string.js(strict mode)
15585-
test/language/expressions/coalesce/short-circuit-number-false.js(default)
15586-
test/language/expressions/coalesce/short-circuit-number-false.js(strict mode)
15587-
test/language/expressions/coalesce/short-circuit-number-object.js(default)
15588-
test/language/expressions/coalesce/short-circuit-number-object.js(strict mode)
15589-
test/language/expressions/coalesce/short-circuit-number-string.js(default)
15590-
test/language/expressions/coalesce/short-circuit-number-string.js(strict mode)
15591-
test/language/expressions/coalesce/short-circuit-number-symbol.js(default)
15592-
test/language/expressions/coalesce/short-circuit-number-symbol.js(strict mode)
15593-
test/language/expressions/coalesce/short-circuit-number-true.js(default)
15594-
test/language/expressions/coalesce/short-circuit-number-true.js(strict mode)
15595-
test/language/expressions/coalesce/short-circuit-prevents-evaluation.js(default)
15596-
test/language/expressions/coalesce/short-circuit-prevents-evaluation.js(strict mode)
15597-
test/language/expressions/coalesce/tco-pos-null.js(strict mode)
15598-
test/language/expressions/coalesce/tco-pos-undefined.js(strict mode)
15599-
test/language/expressions/conditional/coalesce-expr-ternary.js(default)
15600-
test/language/expressions/conditional/coalesce-expr-ternary.js(strict mode)
1560115557
test/language/expressions/logical-assignment/lgcl-and-assignment-operator-bigint.js(default)
1560215558
test/language/expressions/logical-assignment/lgcl-and-assignment-operator-bigint.js(strict mode)
1560315559
test/language/expressions/logical-assignment/lgcl-and-assignment-operator-lhs-before-rhs.js(default)
@@ -15752,8 +15708,6 @@ test/language/expressions/object/cpn-obj-lit-computed-property-name-from-assignm
1575215708
test/language/expressions/object/cpn-obj-lit-computed-property-name-from-assignment-expression-logical-or.js(strict mode)
1575315709
test/language/expressions/object/cpn-obj-lit-computed-property-name-from-await-expression.js(default)
1575415710
test/language/expressions/object/cpn-obj-lit-computed-property-name-from-await-expression.js(strict mode)
15755-
test/language/expressions/object/cpn-obj-lit-computed-property-name-from-expression-coalesce.js(default)
15756-
test/language/expressions/object/cpn-obj-lit-computed-property-name-from-expression-coalesce.js(strict mode)
1575715711
test/language/expressions/object/cpn-obj-lit-computed-property-name-from-integer-separators.js(default)
1575815712
test/language/expressions/object/cpn-obj-lit-computed-property-name-from-integer-separators.js(strict mode)
1575915713
test/language/expressions/object/ident-name-method-def-break-escaped.js(default)
@@ -16007,8 +15961,6 @@ test/language/statements/class/cpn-class-decl-accessors-computed-property-name-f
1600715961
test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-assignment-expression-logical-or.js(strict mode)
1600815962
test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-await-expression.js(default)
1600915963
test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-await-expression.js(strict mode)
16010-
test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-expression-coalesce.js(default)
16011-
test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-expression-coalesce.js(strict mode)
1601215964
test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-integer-separators.js(default)
1601315965
test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-integer-separators.js(strict mode)
1601415966
test/language/statements/class/cpn-class-decl-computed-property-name-from-assignment-expression-coalesce.js(default)
@@ -16019,8 +15971,6 @@ test/language/statements/class/cpn-class-decl-computed-property-name-from-assign
1601915971
test/language/statements/class/cpn-class-decl-computed-property-name-from-assignment-expression-logical-or.js(strict mode)
1602015972
test/language/statements/class/cpn-class-decl-computed-property-name-from-await-expression.js(default)
1602115973
test/language/statements/class/cpn-class-decl-computed-property-name-from-await-expression.js(strict mode)
16022-
test/language/statements/class/cpn-class-decl-computed-property-name-from-expression-coalesce.js(default)
16023-
test/language/statements/class/cpn-class-decl-computed-property-name-from-expression-coalesce.js(strict mode)
1602415974
test/language/statements/class/cpn-class-decl-computed-property-name-from-integer-separators.js(default)
1602515975
test/language/statements/class/cpn-class-decl-computed-property-name-from-integer-separators.js(strict mode)
1602615976
test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-additive-expression-add.js(default)
@@ -16221,8 +16171,6 @@ test/language/statements/class/ident-name-method-def-with-escaped.js(default)
1622116171
test/language/statements/class/ident-name-method-def-with-escaped.js(strict mode)
1622216172
test/language/statements/switch/scope-lex-async-generator.js(default)
1622316173
test/language/statements/switch/scope-lex-async-generator.js(strict mode)
16224-
test/annexB/language/expressions/coalesce/emulates-undefined.js(default)
16225-
test/annexB/language/expressions/coalesce/emulates-undefined.js(strict mode)
1622616174
test/annexB/language/expressions/logical-assignment/emulates-undefined-and.js(default)
1622716175
test/annexB/language/expressions/logical-assignment/emulates-undefined-and.js(strict mode)
1622816176
test/annexB/language/expressions/logical-assignment/emulates-undefined-coalesce.js(default)

0 commit comments

Comments
 (0)