Skip to content

Commit afb1b63

Browse files
committed
1 parent 7ecf3a3 commit afb1b63

File tree

2 files changed

+99
-13
lines changed

2 files changed

+99
-13
lines changed
Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,78 @@
11
<?php namespace lang\ast\emit;
22

3-
use lang\ast\nodes\{CallableExpression, CallableNewExpression, Variable};
3+
use lang\ast\nodes\{CallableExpression, CallableNewExpression, Literal, Variable};
44

55
/**
66
* Emulates pipelines / the pipe operator, including a null-safe version.
77
*
8+
* ```php
9+
* // Enclose expressions as follows:
10+
* $in |> $expr;
11+
* ($expr)($in);
12+
*
13+
* // Optimize for string literals:
14+
* $in |> 'strlen';
15+
* strlen($in);
16+
*
17+
* // Optimize for first-class callables:
18+
* $in |> strlen(...);
19+
* strlen($in);
20+
* ```
21+
*
822
* @see https://wiki.php.net/rfc/pipe-operator-v3
923
* @see https://externals.io/message/107661#107670
24+
* @test lang.ast.unittest.emit.EmulatePipelinesTest
1025
* @test lang.ast.unittest.emit.PipelinesTest
1126
*/
1227
trait EmulatePipelines {
1328

1429
protected function emitPipeTarget($result, $target, $arg) {
1530
if ($target instanceof CallableNewExpression) {
16-
$target->type->arguments= [new Variable(substr($arg, 1))];
31+
$target->type->arguments= [$arg];
1732
$this->emitOne($result, $target->type);
1833
$target->type->arguments= null;
1934
} else if ($target instanceof CallableExpression) {
2035
$this->emitOne($result, $target->expression);
21-
$result->out->write('('.$arg.')');
36+
$result->out->write('(');
37+
$this->emitOne($result, $arg);
38+
$result->out->write(')');
39+
} else if ($target instanceof Literal) {
40+
$result->out->write(trim($target->expression, '"\''));
41+
$result->out->write('(');
42+
$this->emitOne($result, $arg);
43+
$result->out->write(')');
2244
} else {
2345
$result->out->write('(');
2446
$this->emitOne($result, $target);
25-
$result->out->write(')('.$arg.')');
47+
$result->out->write(')(');
48+
$this->emitOne($result, $arg);
49+
$result->out->write(')');
2650
}
2751
}
2852

2953
protected function emitPipe($result, $pipe) {
3054

31-
// $expr |> strtoupper(...) => [$arg= $expr, strtoupper($arg)][1]
32-
$t= $result->temp();
33-
$result->out->write('['.$t.'=');
34-
$this->emitOne($result, $pipe->expression);
35-
$result->out->write(',');
36-
$this->emitPipeTarget($result, $pipe->target, $t);
37-
$result->out->write('][1]');
55+
// <const> |> strtoupper(...) => strtoupper(<const>)
56+
// <expr> |> strtoupper(...) => [$arg= <expr>, strtoupper($arg)][1]
57+
if ($this->isConstant($result, $pipe->expression)) {
58+
$this->emitPipeTarget($result, $pipe->target, $pipe->expression);
59+
} else {
60+
$t= $result->temp();
61+
$result->out->write('['.$t.'=');
62+
$this->emitOne($result, $pipe->expression);
63+
$result->out->write(',');
64+
$this->emitPipeTarget($result, $pipe->target, new Variable(substr($t, 1)));
65+
$result->out->write('][1]');
66+
}
3867
}
3968

4069
protected function emitNullsafePipe($result, $pipe) {
4170

42-
// $expr ?|> strtoupper(...) => null === ($arg= $expr) ? null : strtoupper($arg)
71+
// <expr> ?|> strtoupper(...) => null === ($arg= <expr>) ? null : strtoupper($arg)
4372
$t= $result->temp();
4473
$result->out->write('null===('.$t.'=');
4574
$this->emitOne($result, $pipe->expression);
4675
$result->out->write(')?null:');
47-
$this->emitPipeTarget($result, $pipe->target, $t);
76+
$this->emitPipeTarget($result, $pipe->target, new Variable(substr($t, 1)));
4877
}
4978
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php namespace lang\ast\unittest\emit;
2+
3+
use test\{Assert, Test, Values};
4+
5+
class EmulatePipelinesTest extends EmittingTest {
6+
7+
/** @return string */
8+
protected function runtime() { return 'php:8.4.0'; }
9+
10+
#[Test]
11+
public function lhs_evaluation_order() {
12+
Assert::equals(
13+
'[$_0=result(),($expr)($_0)][1];',
14+
$this->emit('result() |> $expr;')
15+
);
16+
}
17+
18+
#[Test]
19+
public function to_expression() {
20+
Assert::equals(
21+
'($expr)("hi");',
22+
$this->emit('"hi" |> $expr;')
23+
);
24+
}
25+
26+
#[Test, Values(['"strlen"', "'strlen'"])]
27+
public function to_string_literal($notation) {
28+
Assert::equals(
29+
'strlen("hi");',
30+
$this->emit('"hi" |> '.$notation.';')
31+
);
32+
}
33+
34+
#[Test]
35+
public function to_array_literal() {
36+
Assert::equals(
37+
'([$this,"func",])("hi");',
38+
$this->emit('"hi" |> [$this, "func"];')
39+
);
40+
}
41+
42+
#[Test]
43+
public function to_first_class_callable() {
44+
Assert::equals(
45+
'strlen("hi");',
46+
$this->emit('"hi" |> strlen(...);')
47+
);
48+
}
49+
50+
#[Test]
51+
public function to_callable_new() {
52+
Assert::equals(
53+
'new \\util\\Date("2025-07-12");',
54+
$this->emit('"2025-07-12" |> new \\util\\Date(...);')
55+
);
56+
}
57+
}

0 commit comments

Comments
 (0)