diff --git a/phpunit.xml b/phpunit.xml index 25fbf2f..9559f87 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,6 +9,7 @@ tests/render.php + tests/handleLet.php tests/compile.php @@ -28,7 +29,7 @@ tests/array.php - + tests/badSyntaxes.php tests/badSwitchSyntaxes.php tests/badKeywordSyntaxes.php diff --git a/src/JsPhpize/Nodes/Block.php b/src/JsPhpize/Nodes/Block.php index 8d32d2b..789eb78 100644 --- a/src/JsPhpize/Nodes/Block.php +++ b/src/JsPhpize/Nodes/Block.php @@ -56,6 +56,11 @@ public function let($variable) $this->letVariables[] = $variable; } + public function letAfter() + { + + } + public function getLetVariables() { $scope = $this; @@ -107,6 +112,7 @@ public function addInstructions($instructions) $this->inInstruction = true; $this->instructions[] = new Instruction(); } + foreach ($instructions as $instruction) { $this->instructions[count($this->instructions) - 1]->add($instruction); } @@ -140,16 +146,21 @@ public function enableMultipleInstructions() public function getReadVariables() { $variables = $this->value->getReadVariables(); + foreach ($this->instructions as $instruction) { $variables = array_merge($variables, $instruction->getReadVariables()); } + $variables = array_unique($variables); + if ($this->type === 'function') { $nodes = isset($this->value, $this->value->nodes) ? $this->value->nodes : []; + if (count($nodes)) { $nodes = array_map(function ($node) { return $node instanceof Variable ? $node->name : null; }, $nodes); + $variables = array_filter($variables, function ($variable) use ($nodes) { return !in_array($variable, $nodes); }); diff --git a/src/JsPhpize/Parser/Parser.php b/src/JsPhpize/Parser/Parser.php index 0b517b0..4667442 100644 --- a/src/JsPhpize/Parser/Parser.php +++ b/src/JsPhpize/Parser/Parser.php @@ -11,6 +11,7 @@ use JsPhpize\Nodes\FunctionCall; use JsPhpize\Nodes\HooksArray; use JsPhpize\Nodes\Main; +use JsPhpize\Nodes\Node; use JsPhpize\Nodes\Parenthesis; use JsPhpize\Nodes\Ternary; use JsPhpize\Nodes\Value; @@ -33,6 +34,11 @@ class Parser extends TokenExtractor */ protected $stack; + /** + * @var Node|null + */ + protected $letForNextBlock; + public function __construct(JsPhpize $engine, $input, $filename) { $input = str_replace(["\r\n", "\r"], ["\n", ''], $input); @@ -71,6 +77,7 @@ protected function parseParentheses($allowedSeparators = [',', ';']) while ($token = $this->next()) { if ($token->is(')')) { $next = $this->get(0); + if ($next && $next->type === 'lambda') { $this->skip(); @@ -106,9 +113,9 @@ protected function parseParentheses($allowedSeparators = [',', ';']) if ($token->type === 'keyword' && $token->valueIn(['var', 'const', 'let'])) { // @TODO handle let scope here - // if ($token->value === 'let') { - // $this->letForNextBlock = $this->parseLet(); - // } + if ($token->value === 'let') { + $this->letForNextBlock = $this->parseLet(); + } continue; } @@ -204,6 +211,7 @@ protected function parseFunctionCallChildren($function, $applicant = null) $children[] = $value; $next = $this->get(0); + if ($next && $next->is('(')) { $this->skip(); @@ -238,6 +246,7 @@ protected function parseVariable($name) $children[] = $value; $next = $this->get(0); + if ($next && $next->is('(')) { $this->skip(); @@ -257,6 +266,7 @@ protected function parseVariable($name) for ($i = count($this->stack) - 1; $i >= 0; $i--) { $block = $this->stack[$i]; + if ($block->isLet($name)) { $variable->setScope($block); @@ -272,6 +282,7 @@ protected function parseTernary(Value $condition) { $trueValue = $this->expectValue($this->next()); $next = $this->next(); + if (!$next) { throw new Exception("Ternary expression not properly closed after '?' " . $this->exceptionInfos(), 14); } @@ -281,6 +292,7 @@ protected function parseTernary(Value $condition) } $next = $this->next(); + if (!$next) { throw new Exception("Ternary expression not properly closed after ':' " . $this->exceptionInfos(), 16); } @@ -294,6 +306,7 @@ protected function parseTernary(Value $condition) protected function jsonMethodToPhpFunction($method) { $function = null; + switch ($method) { case 'stringify': $function = 'json_encode'; @@ -310,6 +323,7 @@ protected function parseJsonMethod($method) { if ($method->type === 'variable' && ($function = $this->jsonMethodToPhpFunction($method->value))) { $this->skip(2); + if (($next = $this->get(0)) && $next->is('(')) { $this->skip(); @@ -344,6 +358,7 @@ protected function parseFunction() $function = new Block('function'); $function->enableMultipleInstructions(); $token = $this->get(0); + if ($token && $token->type === 'variable') { $this->skip(); $token = $this->get(0); @@ -356,6 +371,7 @@ protected function parseFunction() $this->skip(); $function->setValue($this->parseParentheses()); $token = $this->get(0); + if ($token && !$token->is('{')) { throw $this->unexpected($token); } @@ -370,6 +386,7 @@ protected function parseKeywordStatement($token) { $name = $token->value; $keyword = new Block($name); + switch ($name) { case 'typeof': throw new Exception('typeof keyword not supported', 26); @@ -384,9 +401,11 @@ protected function parseKeywordStatement($token) 'clone' => 'Object', ]; $value = $this->get(0); + if (isset($expects[$name]) && !$value) { throw new Exception($expects[$name] . " expected after '" . $name . "'", 25); } + $this->handleOptionalValue($keyword, $value, $name); break; case 'case': @@ -407,6 +426,7 @@ protected function parseKeywordStatement($token) protected function parseKeyword($token) { $keyword = $this->parseKeywordStatement($token); + if ($keyword->handleInstructions()) { $this->parseBlock($keyword); } @@ -417,6 +437,7 @@ protected function parseKeyword($token) protected function parseLet() { $letVariable = $this->get(0); + if ($letVariable->type !== 'variable') { throw $this->unexpected($letVariable); } @@ -445,6 +466,7 @@ protected function parseInstruction($block, $token, &$initNext) if ($initNext && $instruction instanceof Variable) { $instruction = new Assignation('=', $instruction, new Constant('constant', 'null')); } + $initNext = false; $block->addInstruction($instruction); @@ -486,15 +508,24 @@ protected function parseInstructions($block) public function parseBlock($block) { $this->stack[] = $block; + if (!$block->multipleInstructions) { $next = $this->get(0); + if ($next && $next->is('{')) { $block->enableMultipleInstructions(); } + if ($block->multipleInstructions) { $this->skip(); } } + + if ($this->letForNextBlock) { + $block->letAfter($this->letForNextBlock); + $this->letForNextBlock = null; + } + $this->parseInstructions($block); array_pop($this->stack); } diff --git a/tests/handleLet.php b/tests/handleLet.php new file mode 100644 index 0000000..1a27929 --- /dev/null +++ b/tests/handleLet.php @@ -0,0 +1,23 @@ +assertSame('8 // 22', $jsPhpize->renderCode('let n = 1; +let i = 22; + +for (let i = 0; i < 3; i++) { + n *= 2; +} + +return n + \' // \' + i;')); + } +}