Skip to content

Commit

Permalink
Add support for generator closures & methods and support yielding keys
Browse files Browse the repository at this point in the history
  • Loading branch information
asmblah committed May 28, 2023
1 parent 3dc41ea commit f97d267
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 5 deletions.
42 changes: 38 additions & 4 deletions src/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,9 +593,17 @@ module.exports = {
]},
{name: 'body', what: 'N_STATEMENT'}
],
processor: function (node) {
processor: function (node, parse, abort, context) {
node.static = !!node.static;

if (context.yieldEncountered) {
delete context.yieldEncountered;

// One or more yield statements encountered inside,
// therefore this is a generator and not a normal closure.
node.generator = true;
}

return node;
}
},
Expand Down Expand Up @@ -1188,15 +1196,25 @@ module.exports = {
components: {oneOf: [
[
'T_YIELD',
{
optionally: [
{name: 'key', rule: 'N_EXPRESSION_LEVEL_20'},
/=>/
]
},
{name: 'value', rule: 'N_EXPRESSION_LEVEL_20'}
],
{name: 'next', what: 'N_EXPRESSION_LEVEL_20'}
]},
processor: function (node, parse, abort, context) {
if (!node.value) {
if (node.next) {
return node.next;
}

if (!node.key) {
node.key = null;
}

// This yield expression means that the function it is inside
// is a generator and not a normal function.
context.yieldEncountered = true;
Expand Down Expand Up @@ -1464,11 +1482,19 @@ module.exports = {
},
{name: 'body', what: 'N_STATEMENT'}
],
processor: function (node) {
processor: function (node, parse, abort, context) {
if (!node.visibility) {
node.visibility = 'public';
}

if (context.yieldEncountered) {
delete context.yieldEncountered;

// One or more yield statements encountered inside,
// therefore this is a generator and not a normal closure.
node.generator = true;
}

return node;
}
},
Expand Down Expand Up @@ -1731,11 +1757,19 @@ module.exports = {
},
{name: 'body', what: 'N_STATEMENT'}
],
processor: function (node) {
processor: function (node, parse, abort, context) {
if (!node.visibility) {
node.visibility = 'public';
}

if (context.yieldEncountered) {
delete context.yieldEncountered;

// One or more yield statements encountered inside,
// therefore this is a generator and not a normal closure.
node.generator = true;
}

return node;
}
},
Expand Down
71 changes: 71 additions & 0 deletions test/integration/expressions/yieldTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* PHP-To-AST - PHP parser
* Copyright (c) Dan Phillimore (asmblah)
* http://uniter.github.com/phptoast/
*
* Released under the MIT license
* https://github.com/uniter/phptoast/raw/master/MIT-LICENSE.txt
*/

'use strict';

var _ = require('microdash'),
expect = require('chai').expect,
tools = require('../../tools');

describe('PHP Parser grammar yield expression integration', function () {
var parser;

beforeEach(function () {
parser = tools.createParser();
});

_.each({
'yielding an integer value': {
code: '<?php yield 21;',
expectedAST: {
name: 'N_PROGRAM',
statements: [{
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_INTEGER',
number: '21'
}
}
}]
}
},
'yielding a string key and float value': {
code: '<?php yield "my key" => 123.456;',
expectedAST: {
name: 'N_PROGRAM',
statements: [{
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: {
name: 'N_STRING_LITERAL',
string: 'my key'
},
value: {
name: 'N_FLOAT',
number: 123.456
}
}
}]
}
}
}, function (scenario, description) {
describe(description, function () {
// Pretty-print the code strings so any non-printable characters are readable.
describe('when the code is ' + JSON.stringify(scenario.code) + ' ?>', function () {
it('should return the expected AST', function () {
expect(parser.parse(scenario.code)).to.deep.equal(scenario.expectedAST);
});
});
});
});
});
190 changes: 189 additions & 1 deletion test/integration/statements/function/generatorTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

var _ = require('microdash'),
expect = require('chai').expect,
nowdoc = require('nowdoc'),
tools = require('../../../tools');

describe('PHP Parser grammar generator function statement integration', function () {
Expand All @@ -21,7 +22,7 @@ describe('PHP Parser grammar generator function statement integration', function
});

_.each({
'declaring a generator that yields only values': {
'declaring a generator function that yields only values': {
code: '<?php function myGenerator() { yield "first"; print "middle"; yield "last"; }',
expectedAST: {
name: 'N_PROGRAM',
Expand All @@ -39,6 +40,7 @@ describe('PHP Parser grammar generator function statement integration', function
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'first'
Expand All @@ -57,6 +59,7 @@ describe('PHP Parser grammar generator function statement integration', function
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'last'
Expand All @@ -66,6 +69,191 @@ describe('PHP Parser grammar generator function statement integration', function
}
}]
}
},
'declaring a generator closure that yields only values': {
code: '<?php return function () { yield "first"; print "middle"; yield "last"; };',
expectedAST: {
name: 'N_PROGRAM',
statements: [{
name: 'N_RETURN_STATEMENT',
expression: {
name: 'N_CLOSURE',
generator: true,
static: false,
args: [],
bindings: [],
body: {
name: 'N_COMPOUND_STATEMENT',
statements: [{
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'first'
}
}
}, {
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_PRINT_EXPRESSION',
operand: {
name: 'N_STRING_LITERAL',
string: 'middle'
}
}
}, {
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'last'
}
}
}]
}
}
}]
}
},
'declaring a generator instance method that yields only values': {
code: nowdoc(function () {/*<<<EOS
<?php
class MyClass
{
public function myMethod()
{
yield "first";
print "middle";
yield "last";
}
}
EOS
*/;}), // jshint ignore:line
expectedAST: {
name: 'N_PROGRAM',
statements: [{
name: 'N_CLASS_STATEMENT',
className: 'MyClass',
members: [{
name: 'N_METHOD_DEFINITION',
func: {
name: 'N_STRING',
string: 'myMethod'
},
generator: true,
visibility: 'public',
args: [],
body: {
name: 'N_COMPOUND_STATEMENT',
statements: [{
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'first'
}
}
}, {
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_PRINT_EXPRESSION',
operand: {
name: 'N_STRING_LITERAL',
string: 'middle'
}
}
}, {
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'last'
}
}
}]
}
}]
}]
}
},
'declaring a generator static method that yields only values': {
code: nowdoc(function () {/*<<<EOS
<?php
class MyClass
{
public static function myMethod()
{
yield "first";
print "middle";
yield "last";
}
}
EOS
*/;}), // jshint ignore:line
expectedAST: {
name: 'N_PROGRAM',
statements: [{
name: 'N_CLASS_STATEMENT',
className: 'MyClass',
members: [{
name: 'N_STATIC_METHOD_DEFINITION',
method: {
name: 'N_STRING',
string: 'myMethod'
},
generator: true,
visibility: 'public',
args: [],
body: {
name: 'N_COMPOUND_STATEMENT',
statements: [{
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'first'
}
}
}, {
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_PRINT_EXPRESSION',
operand: {
name: 'N_STRING_LITERAL',
string: 'middle'
}
}
}, {
name: 'N_EXPRESSION_STATEMENT',
expression: {
name: 'N_YIELD_EXPRESSION',
key: null,
value: {
name: 'N_STRING_LITERAL',
string: 'last'
}
}
}]
}
}]
}]
}
}
}, function (scenario, description) {
describe(description, function () {
Expand Down

0 comments on commit f97d267

Please sign in to comment.