Skip to content

Commit 0714ad6

Browse files
evilebottnawialexander-akait
authored andcommitted
feat: null coalesce equal operator (#393)
1 parent c33ddb4 commit 0714ad6

File tree

10 files changed

+203
-5
lines changed

10 files changed

+203
-5
lines changed

index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ declare module "php-parser" {
141141
T_COALESCE = 230,
142142
T_POW = 231,
143143
T_POW_EQUAL = 232,
144-
T_SPACESHIP = 233
144+
T_SPACESHIP = 233,
145+
T_COALESCE_EQUAL = 234
145146
}
146147

147148
/**

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ const engine = function(options) {
9696
}
9797
options.lexer.php7 = options.parser.php7;
9898
options.lexer.php73 = options.parser.php73;
99+
options.lexer.php74 = options.parser.php74;
99100
}
100101
combine(options, this);
101102
}

src/lexer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const lexer = function(engine) {
3131
this.short_tags = false;
3232
this.php7 = true;
3333
this.php73 = true;
34+
this.php74 = true;
3435
this.yyprevcol = 0;
3536
this.keywords = {
3637
__class__: this.tok.T_CLASS_C,

src/lexer/tokens.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,13 @@ module.exports = {
154154
},
155155
"?": function() {
156156
if (this.php7 && this._input[this.offset] === "?") {
157-
this.input();
158-
return this.tok.T_COALESCE;
157+
if (this.php74 && this._input[this.offset + 1] === "=") {
158+
this.consume(2);
159+
return this.tok.T_COALESCE_EQUAL;
160+
} else {
161+
this.input();
162+
return this.tok.T_COALESCE;
163+
}
159164
}
160165
return "?";
161166
},

src/parser/expr.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,10 @@ module.exports = {
458458
if (isConst) this.error("VARIABLE");
459459
return result("assign", expr, this.next().read_expr(), ">>=");
460460

461+
case this.tok.T_COALESCE_EQUAL:
462+
if (isConst) this.error("VARIABLE");
463+
return result("assign", expr, this.next().read_expr(), "??=");
464+
461465
case this.tok.T_INC:
462466
if (isConst) this.error("VARIABLE");
463467
this.next();

src/tokens.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ module.exports = {
143143
230: "T_COALESCE",
144144
231: "T_POW",
145145
232: "T_POW_EQUAL",
146-
233: "T_SPACESHIP"
146+
233: "T_SPACESHIP",
147+
234: "T_COALESCE_EQUAL"
147148
},
148149
names: {
149150
T_HALT_COMPILER: 101,
@@ -278,6 +279,7 @@ module.exports = {
278279
T_COALESCE: 230,
279280
T_POW: 231,
280281
T_POW_EQUAL: 232,
281-
T_SPACESHIP: 233
282+
T_SPACESHIP: 233,
283+
T_COALESCE_EQUAL: 234
282284
}
283285
};

test/snapshot/__snapshots__/assign.test.js.snap

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,125 @@ Program {
234234
}
235235
`;
236236

237+
exports[`assign ??= (php < 7) 1`] = `
238+
Program {
239+
"children": Array [
240+
ExpressionStatement {
241+
"expression": RetIf {
242+
"falseExpr": undefined,
243+
"kind": "retif",
244+
"test": Variable {
245+
"curly": false,
246+
"kind": "variable",
247+
"name": "var",
248+
},
249+
"trueExpr": undefined,
250+
},
251+
"kind": "expressionstatement",
252+
},
253+
ExpressionStatement {
254+
"expression": Variable {
255+
"curly": false,
256+
"kind": "variable",
257+
"name": "var",
258+
},
259+
"kind": "expressionstatement",
260+
},
261+
],
262+
"errors": Array [
263+
Error {
264+
"expected": "EXPR",
265+
"kind": "error",
266+
"line": 1,
267+
"message": "Parse Error : syntax error, unexpected '?' on line 1",
268+
"token": "'?'",
269+
},
270+
Error {
271+
"expected": ":",
272+
"kind": "error",
273+
"line": 1,
274+
"message": "Parse Error : syntax error, unexpected '=', expecting ':' on line 1",
275+
"token": "'='",
276+
},
277+
Error {
278+
"expected": "EXPR",
279+
"kind": "error",
280+
"line": 1,
281+
"message": "Parse Error : syntax error, unexpected '=' on line 1",
282+
"token": "'='",
283+
},
284+
Error {
285+
"expected": ";",
286+
"kind": "error",
287+
"line": 1,
288+
"message": "Parse Error : syntax error, unexpected '$var' (T_VARIABLE), expecting ';' on line 1",
289+
"token": "'$var' (T_VARIABLE)",
290+
},
291+
],
292+
"kind": "program",
293+
}
294+
`;
295+
296+
exports[`assign ??= 1`] = `
297+
Program {
298+
"children": Array [
299+
ExpressionStatement {
300+
"expression": Assign {
301+
"kind": "assign",
302+
"left": Variable {
303+
"curly": false,
304+
"kind": "variable",
305+
"name": "var",
306+
},
307+
"operator": "??=",
308+
"right": Variable {
309+
"curly": false,
310+
"kind": "variable",
311+
"name": "var",
312+
},
313+
},
314+
"kind": "expressionstatement",
315+
},
316+
],
317+
"errors": Array [],
318+
"kind": "program",
319+
}
320+
`;
321+
322+
exports[`assign ??= with bin 1`] = `
323+
Program {
324+
"children": Array [
325+
ExpressionStatement {
326+
"expression": Assign {
327+
"kind": "assign",
328+
"left": Variable {
329+
"curly": false,
330+
"kind": "variable",
331+
"name": "var",
332+
},
333+
"operator": "??=",
334+
"right": Bin {
335+
"kind": "bin",
336+
"left": Variable {
337+
"curly": false,
338+
"kind": "variable",
339+
"name": "var",
340+
},
341+
"right": Number {
342+
"kind": "number",
343+
"value": "10",
344+
},
345+
"type": "+",
346+
},
347+
},
348+
"kind": "expressionstatement",
349+
},
350+
],
351+
"errors": Array [],
352+
"kind": "program",
353+
}
354+
`;
355+
237356
exports[`assign ^= 1`] = `
238357
Program {
239358
"children": Array [

test/snapshot/__snapshots__/bin.test.js.snap

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,47 @@ Program {
494494
}
495495
`;
496496

497+
exports[`bin ?? (php < 7) 1`] = `
498+
Program {
499+
"children": Array [
500+
ExpressionStatement {
501+
"expression": RetIf {
502+
"falseExpr": Variable {
503+
"curly": false,
504+
"kind": "variable",
505+
"name": "var",
506+
},
507+
"kind": "retif",
508+
"test": Variable {
509+
"curly": false,
510+
"kind": "variable",
511+
"name": "var",
512+
},
513+
"trueExpr": undefined,
514+
},
515+
"kind": "expressionstatement",
516+
},
517+
],
518+
"errors": Array [
519+
Error {
520+
"expected": "EXPR",
521+
"kind": "error",
522+
"line": 1,
523+
"message": "Parse Error : syntax error, unexpected '?' on line 1",
524+
"token": "'?'",
525+
},
526+
Error {
527+
"expected": ":",
528+
"kind": "error",
529+
"line": 1,
530+
"message": "Parse Error : syntax error, unexpected '$var' (T_VARIABLE), expecting ':' on line 1",
531+
"token": "'$var' (T_VARIABLE)",
532+
},
533+
],
534+
"kind": "program",
535+
}
536+
`;
537+
497538
exports[`bin ?? 1`] = `
498539
Program {
499540
"children": Array [

test/snapshot/assign.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,21 @@ describe('assign', () => {
4646
it('>>=', () => {
4747
expect(parser.parseEval("$var >>= $var;")).toMatchSnapshot();
4848
});
49+
it('??=', () => {
50+
expect(parser.parseEval("$var ??= $var;")).toMatchSnapshot();
51+
});
52+
it('??= with bin', () => {
53+
expect(parser.parseEval("$var ??= $var + 10;")).toMatchSnapshot();
54+
});
55+
it('??= (php < 7)', function() {
56+
const astErr = parser.parseEval(`$var ??= $var;`, {
57+
parser: {
58+
php7: false,
59+
suppressErrors: true
60+
}
61+
});
62+
expect(astErr).toMatchSnapshot();
63+
});
4964
it('with ref', () => {
5065
expect(parser.parseEval("$bar = &$foo;")).toMatchSnapshot();
5166
});

test/snapshot/bin.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ describe('bin', () => {
101101
it('??', () => {
102102
expect(parser.parseEval('$foo ?? $bar;')).toMatchSnapshot();
103103
});
104+
it('?? (php < 7)', function() {
105+
const astErr = parser.parseEval(`$var ?? $var;`, {
106+
parser: {
107+
php7: false,
108+
suppressErrors: true
109+
}
110+
});
111+
expect(astErr).toMatchSnapshot();
112+
});
104113
it('assign', () => {
105114
expect(parser.parseEval('$var = $var + $var;')).toMatchSnapshot();
106115
});

0 commit comments

Comments
 (0)