Skip to content

Commit 2781cf5

Browse files
committed
Implement coalescing code generation
This patch implements ?? syntax. The most important thing is how to correctly parenthesize it. This patch adds correct BinaryPrecedence.Coalesce to support edge cases tested in the test file.
1 parent 3a8e452 commit 2781cf5

File tree

5 files changed

+183
-25
lines changed

5 files changed

+183
-25
lines changed

escodegen.js

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -86,29 +86,31 @@
8686
Assignment: 1,
8787
Conditional: 2,
8888
ArrowFunction: 2,
89-
LogicalOR: 3,
90-
LogicalAND: 4,
91-
BitwiseOR: 5,
92-
BitwiseXOR: 6,
93-
BitwiseAND: 7,
94-
Equality: 8,
95-
Relational: 9,
96-
BitwiseSHIFT: 10,
97-
Additive: 11,
98-
Multiplicative: 12,
99-
Exponentiation: 13,
100-
Await: 14,
101-
Unary: 14,
102-
Postfix: 15,
103-
OptionalChaining: 16,
104-
Call: 17,
105-
New: 18,
106-
TaggedTemplate: 19,
107-
Member: 20,
108-
Primary: 21
89+
Coalesce: 3,
90+
LogicalOR: 4,
91+
LogicalAND: 5,
92+
BitwiseOR: 6,
93+
BitwiseXOR: 7,
94+
BitwiseAND: 8,
95+
Equality: 9,
96+
Relational: 10,
97+
BitwiseSHIFT: 11,
98+
Additive: 12,
99+
Multiplicative: 13,
100+
Exponentiation: 14,
101+
Await: 15,
102+
Unary: 15,
103+
Postfix: 16,
104+
OptionalChaining: 17,
105+
Call: 18,
106+
New: 19,
107+
TaggedTemplate: 20,
108+
Member: 21,
109+
Primary: 22
109110
};
110111

111112
BinaryPrecedence = {
113+
'??': Precedence.Coalesce,
112114
'||': Precedence.LogicalOR,
113115
'&&': Precedence.LogicalAND,
114116
'|': Precedence.BitwiseOR,
@@ -143,7 +145,8 @@
143145
F_ALLOW_UNPARATH_NEW = 1 << 2,
144146
F_FUNC_BODY = 1 << 3,
145147
F_DIRECTIVE_CTX = 1 << 4,
146-
F_SEMICOLON_OPT = 1 << 5;
148+
F_SEMICOLON_OPT = 1 << 5,
149+
F_FOUND_COALESCE = 1 << 6;
147150

148151
//Expression flag sets
149152
//NOTE: Flag order:
@@ -1819,7 +1822,7 @@
18191822
}
18201823
return parenthesize(
18211824
[
1822-
this.generateExpression(expr.test, Precedence.LogicalOR, flags),
1825+
this.generateExpression(expr.test, Precedence.Coalesce, flags),
18231826
space + '?' + space,
18241827
this.generateExpression(expr.consequent, Precedence.Assignment, flags),
18251828
space + ':' + space,
@@ -1831,6 +1834,9 @@
18311834
},
18321835

18331836
LogicalExpression: function (expr, precedence, flags) {
1837+
if (expr.operator === '??') {
1838+
flags |= F_FOUND_COALESCE;
1839+
}
18341840
return this.BinaryExpression(expr, precedence, flags);
18351841
},
18361842

@@ -1868,6 +1874,9 @@
18681874
if (expr.operator === 'in' && !(flags & F_ALLOW_IN)) {
18691875
return ['(', result, ')'];
18701876
}
1877+
if ((expr.operator === '||' || expr.operator === '&&') && (flags & F_FOUND_COALESCE)) {
1878+
return ['(', result, ')'];
1879+
}
18711880
return parenthesize(result, currentPrecedence, precedence);
18721881
},
18731882

test/compare-acorn-es2020.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function test(code, expected) {
4949
actualTree = acorn.parse(actual, options);
5050

5151
expect(actual).to.be.equal(expected);
52-
expect(tree).excludingEvery(['start', 'end']).to.deep.equal(actualTree);
52+
expect(tree).excludingEvery(['start', 'end', 'raw']).to.deep.equal(actualTree);
5353
}
5454

5555
function testMin(code, expected) {
@@ -71,7 +71,7 @@ function testMin(code, expected) {
7171
actualTree = acorn.parse(actual, options);
7272

7373
expect(actual).to.be.equal(expected);
74-
expect(tree).excludingEvery(['start', 'end']).to.deep.equal(actualTree);
74+
expect(tree).excludingEvery(['start', 'end', 'raw']).to.deep.equal(actualTree);
7575
}
7676

7777
describe('compare acorn es2020 test', function () {
@@ -92,4 +92,4 @@ describe('compare acorn es2020 test', function () {
9292
}
9393
});
9494
});
95-
/* vim: set sw=4 ts=4 et tw=80 : */
95+
/* vim: set sw=4 ts=4 et tw=80 : */
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
function testBasicCases() {
2+
shouldBe(undefined ?? 3, 3);
3+
shouldBe(null ?? 3, 3);
4+
shouldBe(true ?? 3, true);
5+
shouldBe(false ?? 3, false);
6+
shouldBe(0 ?? 3, 0);
7+
shouldBe(1 ?? 3, 1);
8+
shouldBe('' ?? 3, '');
9+
shouldBe('hi' ?? 3, 'hi');
10+
shouldBe(({} ?? 3) instanceof Object, true);
11+
shouldBe(({ x: 'hi' } ?? 3).x, 'hi');
12+
shouldBe(([] ?? 3) instanceof Array, true);
13+
shouldBe((['hi'] ?? 3)[0], 'hi');
14+
shouldBe((makeMasquerader() ?? 3) == null, true);
15+
}
16+
noInline(testBasicCases);
17+
for (let i = 0; i < 100000; i++)
18+
testBasicCases();
19+
shouldBe(1 | null ?? 3, 1);
20+
shouldBe(1 ^ null ?? 3, 1);
21+
shouldBe(1 & null ?? 3, 0);
22+
shouldBe(3 == null ?? 3, false);
23+
shouldBe(3 != null ?? 3, true);
24+
shouldBe(3 === null ?? 3, false);
25+
shouldBe(3 !== null ?? 3, true);
26+
shouldBe(1 < null ?? 3, false);
27+
shouldBe(1 > null ?? 3, true);
28+
shouldBe(1 <= null ?? 3, false);
29+
shouldBe(1 >= null ?? 3, true);
30+
shouldBe(1 << null ?? 3, 1);
31+
shouldBe(1 >> null ?? 3, 1);
32+
shouldBe(1 >>> null ?? 3, 1);
33+
shouldBe(1 + null ?? 3, 1);
34+
shouldBe(1 - null ?? 3, 1);
35+
shouldBe(1 * null ?? 3, 0);
36+
shouldBe(1 / null ?? 3, Infinity);
37+
shouldBe(isNaN(1 % null ?? 3), true);
38+
shouldBe(1 ** null ?? 3, 1);
39+
const obj = {
40+
count: 0,
41+
get x() {
42+
this.count++;
43+
return 'x';
44+
}
45+
};
46+
false ?? obj.x;
47+
shouldBe(obj.count, 0);
48+
null ?? obj.x;
49+
shouldBe(obj.count, 1);
50+
obj.x ?? obj.x;
51+
shouldBe(obj.count, 2);
52+
(0 || 1) ?? 2;
53+
0 || (1 ?? 2);
54+
(0 && 1) ?? 2;
55+
0 && (1 ?? 2);
56+
(0 ?? 1) || 2;
57+
0 ?? (1 || 2);
58+
(0 ?? 1) && 2;
59+
0 ?? (1 && 2);
60+
0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3 << 4 >> 5 >>> 6 + 7 - 8 * 9 / 0 % 1 ** 2;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
function testBasicCases(){shouldBe(undefined??3,3);shouldBe(null??3,3);shouldBe(true??3,true);shouldBe(false??3,false);shouldBe(0??3,0);shouldBe(1??3,1);shouldBe(''??3,'');shouldBe('hi'??3,'hi');shouldBe(({}??3)instanceof Object,true);shouldBe(({x:'hi'}??3).x,'hi');shouldBe(([]??3)instanceof Array,true);shouldBe((['hi']??3)[0],'hi');shouldBe((makeMasquerader()??3)==null,true)}noInline(testBasicCases);for(let i=0;i<1e5;i++)testBasicCases();shouldBe(1|null??3,1);shouldBe(1^null??3,1);shouldBe(1&null??3,0);shouldBe(3==null??3,false);shouldBe(3!=null??3,true);shouldBe(3===null??3,false);shouldBe(3!==null??3,true);shouldBe(1<null??3,false);shouldBe(1>null??3,true);shouldBe(1<=null??3,false);shouldBe(1>=null??3,true);shouldBe(1<<null??3,1);shouldBe(1>>null??3,1);shouldBe(1>>>null??3,1);shouldBe(1+null??3,1);shouldBe(1-null??3,1);shouldBe(1*null??3,0);shouldBe(1/null??3,Infinity);shouldBe(isNaN(1%null??3),true);shouldBe(1**null??3,1);const obj={count:0,get x(){this.count++;return'x'}};false??obj.x;shouldBe(obj.count,0);null??obj.x;shouldBe(obj.count,1);obj.x??obj.x;shouldBe(obj.count,2);(0||1)??2;0||(1??2);(0&&1)??2;0&&(1??2);(0??1)||2;0??(1||2);(0??1)&&2;0??(1&&2);0||1&&2|3^4&5==6!=7===8!==9<0>1<=2>=3<<4>>5>>>6+7-8*9/0%1**2
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (C) 2020 Sony Interactive Entertainment Inc.
3+
* Copyright (C) 2020 Apple Inc.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions
7+
* are met:
8+
* 1. Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* 2. Redistributions in binary form must reproduce the above copyright
11+
* notice, this list of conditions and the following disclaimer in the
12+
* documentation and/or other materials provided with the distribution.
13+
*
14+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16+
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24+
* THE POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
27+
function testBasicCases() {
28+
shouldBe(undefined ?? 3, 3);
29+
shouldBe(null ?? 3, 3);
30+
shouldBe(true ?? 3, true);
31+
shouldBe(false ?? 3, false);
32+
shouldBe(0 ?? 3, 0);
33+
shouldBe(1 ?? 3, 1);
34+
shouldBe('' ?? 3, '');
35+
shouldBe('hi' ?? 3, 'hi');
36+
shouldBe(({} ?? 3) instanceof Object, true);
37+
shouldBe(({ x: 'hi' } ?? 3).x, 'hi');
38+
shouldBe(([] ?? 3) instanceof Array, true);
39+
shouldBe((['hi'] ?? 3)[0], 'hi');
40+
shouldBe((makeMasquerader() ?? 3) == null, true);
41+
}
42+
noInline(testBasicCases);
43+
44+
for (let i = 0; i < 1e5; i++)
45+
testBasicCases();
46+
47+
shouldBe(1 | null ?? 3, 1);
48+
shouldBe(1 ^ null ?? 3, 1);
49+
shouldBe(1 & null ?? 3, 0);
50+
shouldBe(3 == null ?? 3, false);
51+
shouldBe(3 != null ?? 3, true);
52+
shouldBe(3 === null ?? 3, false);
53+
shouldBe(3 !== null ?? 3, true);
54+
shouldBe(1 < null ?? 3, false);
55+
shouldBe(1 > null ?? 3, true);
56+
shouldBe(1 <= null ?? 3, false);
57+
shouldBe(1 >= null ?? 3, true);
58+
shouldBe(1 << null ?? 3, 1);
59+
shouldBe(1 >> null ?? 3, 1);
60+
shouldBe(1 >>> null ?? 3, 1);
61+
shouldBe(1 + null ?? 3, 1);
62+
shouldBe(1 - null ?? 3, 1);
63+
shouldBe(1 * null ?? 3, 0);
64+
shouldBe(1 / null ?? 3, Infinity);
65+
shouldBe(isNaN(1 % null ?? 3), true);
66+
shouldBe(1 ** null ?? 3, 1);
67+
68+
const obj = {
69+
count: 0,
70+
get x() { this.count++; return 'x'; }
71+
};
72+
false ?? obj.x;
73+
shouldBe(obj.count, 0);
74+
null ?? obj.x;
75+
shouldBe(obj.count, 1);
76+
obj.x ?? obj.x;
77+
shouldBe(obj.count, 2);
78+
79+
(0 || 1) ?? 2;
80+
0 || (1 ?? 2);
81+
(0 && 1) ?? 2;
82+
0 && (1 ?? 2);
83+
(0 ?? 1) || 2;
84+
0 ?? (1 || 2);
85+
(0 ?? 1) && 2;
86+
0 ?? (1 && 2);
87+
88+
0 || 1 && 2 | 3 ^ 4 & 5 == 6 != 7 === 8 !== 9 < 0 > 1 <= 2 >= 3 << 4 >> 5 >>> 6 + 7 - 8 * 9 / 0 % 1 ** 2

0 commit comments

Comments
 (0)