Skip to content

Commit 3e6b4f8

Browse files
committed
Implement interpolation
1 parent 962ccdc commit 3e6b4f8

File tree

10 files changed

+95
-22
lines changed

10 files changed

+95
-22
lines changed

examples/interpolation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
name = 'Bob';
22

3-
return `Hello ${name}, can you ${(function (verb) { return verb; })('tell')}?`;
3+
return `Hello ${name}, \${not} can you ${(function (verb) { return verb; })('tell')}?`;

examples/interpolation.return

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Hello Bob, can you tell?
1+
Hello Bob, ${not} can you tell?

src/JsPhpize/Compiler/Compiler.php

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
class Compiler
2222
{
2323
use DyiadeTrait;
24+
use InterpolationTrait;
2425

2526
/**
2627
* @const string
@@ -42,9 +43,15 @@ class Compiler
4243
*/
4344
protected $arrayShortSyntax;
4445

45-
public function __construct(JsPhpize $engine)
46+
/**
47+
* @var string
48+
*/
49+
protected $filename;
50+
51+
public function __construct(JsPhpize $engine, $filename = null)
4652
{
4753
$this->engine = $engine;
54+
$this->filename = $filename;
4855
$this->setPrefixes($engine->getVarPrefix(), $engine->getConstPrefix());
4956
$this->arrayShortSyntax = $engine->getOption('arrayShortSyntax', false);
5057
}
@@ -139,13 +146,24 @@ protected function visitConstant(Constant $constant)
139146
{
140147
$value = $constant->value;
141148

142-
if ($constant->type === 'string' && mb_substr($constant->value, 0, 1) === '"') {
143-
$value = str_replace('$', '\\$', $value);
149+
if ($constant->type === 'string') {
150+
if (substr($value, 0, 1) === '`') {
151+
return implode(
152+
' . ',
153+
iterator_to_array($this->readInterpolation(substr($value, 1, -1)))
154+
);
155+
}
156+
157+
if (mb_substr($constant->value, 0, 1) === '"') {
158+
return str_replace('$', '\\$', $value);
159+
}
144160
}
145161

146162
if ($constant->type === 'regexp') {
147-
$regExp = $this->engine->getHelperName('regExp');
148-
$value = $this->helperWrap($regExp, [var_export($value, true)]);
163+
return $this->helperWrap(
164+
$this->engine->getHelperName('regExp'),
165+
[var_export($value, true)]
166+
);
149167
}
150168

151169
return $value;
@@ -273,6 +291,7 @@ public function visitNode(Node $node, $indent)
273291
get_class($node)
274292
);
275293
$php = method_exists($this, $method) ? $this->$method($node, $indent) : '';
294+
276295
if ($node instanceof Value) {
277296
$php = $node->getBefore() . $php . $node->getAfter();
278297
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace JsPhpize\Compiler;
4+
5+
use JsPhpize\Parser\Parser;
6+
7+
trait InterpolationTrait
8+
{
9+
protected function readInterpolation($value)
10+
{
11+
while (strlen($value)) {
12+
preg_match('/^(.*)(?:(\\\\|\\${).*)?$/U', $value, $match);
13+
14+
yield var_export($match[1], true);
15+
16+
$value = mb_substr($value, mb_strlen($match[1]));
17+
18+
if (isset($match[2])) {
19+
if ($match[2] === '\\') {
20+
yield var_export(mb_substr($value, 1, 1), true);
21+
22+
$value = mb_substr($value, 2);
23+
24+
continue;
25+
}
26+
27+
$value = mb_substr($value, 2);
28+
29+
$parser = new Parser($this->engine, $value, $this->filename);
30+
31+
yield rtrim($this->visitInstruction($parser->parse()->instructions[0], ''), ";\t\n\r\0\x0B ");
32+
33+
$value = $parser->rest();
34+
}
35+
}
36+
}
37+
}

src/JsPhpize/JsPhpize.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ public function compile($input, $filename = null)
5252

5353
$start = '';
5454
$end = '';
55+
5556
if (preg_match('/^([)}\]\s]*)(.*?)([({\[\s]*)$/', trim($input), $match)) {
5657
list(, $start, $input, $end) = $match;
5758
}
5859

5960
$parser = new Parser($this, $input, $filename);
60-
$compiler = new Compiler($this);
61+
$compiler = new Compiler($this, $filename);
6162
$block = $parser->parse();
6263
$php = $compiler->compile($block);
6364

@@ -70,6 +71,7 @@ public function compile($input, $filename = null)
7071
}
7172

7273
$dependencies = $compiler->getDependencies();
74+
7375
if ($this->getOption('catchDependencies')) {
7476
$this->dependencies = array_unique(array_merge($this->dependencies, $dependencies));
7577
$dependencies = [];

src/JsPhpize/Lexer/Lexer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public function exceptionInfos()
6161
' near from ' . trim($this->consumed);
6262
}
6363

64+
public function rest()
65+
{
66+
return $this->input;
67+
}
68+
6469
protected function consume($consumed)
6570
{
6671
$consumed = is_int($consumed) ? mb_substr($this->input, 0, $consumed) : $consumed;

src/JsPhpize/Lexer/Scanner.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,7 @@ public function scanNumber($matches)
6464

6565
public function scanString($matches)
6666
{
67-
/** @var Constant $stringToken */
68-
$stringToken = $this->valueToken('string', $matches);
69-
$delimiter = substr($stringToken->value, 0, 1);
70-
71-
if ($delimiter === '`') {
72-
var_dump($stringToken->value);
73-
exit;
74-
}
75-
76-
return $stringToken;
67+
return $this->valueToken('string', $matches);
7768
}
7869

7970
public function scanOperator($matches)

src/JsPhpize/Parser/Parser.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,25 @@ public function __construct(JsPhpize $engine, $input, $filename)
4242
$this->lexer = new Lexer($engine, $input, $filename);
4343
}
4444

45+
public function rest()
46+
{
47+
return $this->lexer->rest();
48+
}
49+
4550
protected function parseLambda(Value $parameters)
4651
{
4752
$lambda = new Block('function');
4853
$lambda->setValue($parameters);
4954
$next = $this->next();
55+
5056
if ($next) {
5157
if ($next->is('{')) {
5258
$this->parseBlock($lambda);
5359
$this->skip();
5460

5561
return $lambda;
5662
}
63+
5764
$return = new Block('return');
5865
$return->setValue($this->expectValue($next));
5966
$lambda->addInstruction($return);
@@ -71,6 +78,7 @@ protected function parseParentheses($allowedSeparators = [',', ';'])
7178
while ($token = $this->next()) {
7279
if ($token->is(')')) {
7380
$next = $this->get(0);
81+
7482
if ($next && $next->type === 'lambda') {
7583
$this->skip();
7684

@@ -204,6 +212,7 @@ protected function parseFunctionCallChildren($function, $applicant = null)
204212
$children[] = $value;
205213

206214
$next = $this->get(0);
215+
207216
if ($next && $next->is('(')) {
208217
$this->skip();
209218

@@ -225,6 +234,7 @@ protected function parseVariable($name)
225234
{
226235
$children = [];
227236
$variable = null;
237+
228238
while ($next = $this->get(0)) {
229239
if ($next->type === 'lambda') {
230240
$this->skip();
@@ -238,6 +248,7 @@ protected function parseVariable($name)
238248
$children[] = $value;
239249

240250
$next = $this->get(0);
251+
241252
if ($next && $next->is('(')) {
242253
$this->skip();
243254

@@ -257,6 +268,7 @@ protected function parseVariable($name)
257268

258269
for ($i = count($this->stack) - 1; $i >= 0; $i--) {
259270
$block = $this->stack[$i];
271+
260272
if ($block->isLet($name)) {
261273
$variable->setScope($block);
262274

@@ -272,6 +284,7 @@ protected function parseTernary(Value $condition)
272284
{
273285
$trueValue = $this->expectValue($this->next());
274286
$next = $this->next();
287+
275288
if (!$next) {
276289
throw new Exception("Ternary expression not properly closed after '?' " . $this->exceptionInfos(), 14);
277290
}
@@ -281,6 +294,7 @@ protected function parseTernary(Value $condition)
281294
}
282295

283296
$next = $this->next();
297+
284298
if (!$next) {
285299
throw new Exception("Ternary expression not properly closed after ':' " . $this->exceptionInfos(), 16);
286300
}
@@ -294,6 +308,7 @@ protected function parseTernary(Value $condition)
294308
protected function jsonMethodToPhpFunction($method)
295309
{
296310
$function = null;
311+
297312
switch ($method) {
298313
case 'stringify':
299314
$function = 'json_encode';
@@ -345,6 +360,7 @@ protected function parseFunction()
345360
$function = new Block('function');
346361
$function->enableMultipleInstructions();
347362
$token = $this->get(0);
363+
348364
if ($token && $token->type === 'variable') {
349365
$this->skip();
350366
$token = $this->get(0);
@@ -357,6 +373,7 @@ protected function parseFunction()
357373
$this->skip();
358374
$function->setValue($this->parseParentheses());
359375
$token = $this->get(0);
376+
360377
if ($token && !$token->is('{')) {
361378
throw $this->unexpected($token);
362379
}
@@ -374,7 +391,9 @@ protected function parseKeywordStatement($token)
374391

375392
switch ($name) {
376393
case 'typeof':
394+
// @codeCoverageIgnoreStart
377395
throw new Exception('typeof keyword not supported', 26);
396+
// @codeCoverageIgnoreEnd
378397
break;
379398
case 'new':
380399
case 'clone':
@@ -451,6 +470,7 @@ protected function parseInstruction($block, $token, &$initNext)
451470
if ($initNext && $instruction instanceof Variable) {
452471
$instruction = new Assignation('=', $instruction, new Constant('constant', 'null'));
453472
}
473+
454474
$initNext = false;
455475
$block->addInstruction($instruction);
456476

tests/render.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ public function caseProvider()
1010
$cases = [];
1111

1212
$examples = __DIR__ . '/../examples';
13+
1314
foreach (scandir($examples) as $file) {
14-
if ($file !== 'interpolation.return') {
15-
continue;
16-
}
1715
if (substr($file, -7) === '.return') {
1816
$cases[] = [$file, substr($file, 0, -7) . '.js'];
1917
}
@@ -23,7 +21,6 @@ public function caseProvider()
2321
}
2422

2523
/**
26-
* @group i
2724
* @group examples
2825
* @dataProvider caseProvider
2926
*/
@@ -43,6 +40,7 @@ public function testJsPhpizeGeneration($returnFile, $jsFile)
4340
$contents = $jsPhpize->compile($examples . '/' . $jsFile);
4441
$message = "\n" . get_class($error) . ' in ' . $jsFile . ' line ' . $error->getLine() .
4542
"\n" . $error->getMessage() . "\n";
43+
4644
foreach (explode("\n", $contents) as $index => $line) {
4745
$number = $index + 1;
4846
$message .= ($number === $error->getLine() ? '>' : ' ') .

tests/stream.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ public function testStreamEmulator()
2020
$this->assertTrue(is_array($stream->url_stat('foo', 0)));
2121
$this->assertSame('r', $stream->stream_read(2));
2222
$this->assertTrue($stream->stream_eof());
23+
$this->assertTrue($stream->stream_set_option(1, 2, 3));
2324
}
2425
}

0 commit comments

Comments
 (0)