Skip to content

Commit

Permalink
added nullsafe pipe operator ?|
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Jan 9, 2024
1 parent d5de25a commit 16eb604
Show file tree
Hide file tree
Showing 14 changed files with 707 additions and 325 deletions.
9 changes: 8 additions & 1 deletion src/Latte/Compiler/Nodes/Php/Expression/FilterCallNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ public function __construct(

public function print(PrintContext $context): string
{
return $this->filter->printSimple($context, $this->expr->print($context));
for (
$node = $this, $outer = null;
$node->expr instanceof self;
$node = $node->expr
) {
$outer = fn(string $expr) => $node->filter->printSimple($context, $expr, $outer);
}
return $node->filter->printSimple($context, $node->expr->print($context), $outer);
}


Expand Down
18 changes: 13 additions & 5 deletions src/Latte/Compiler/Nodes/Php/FilterNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Latte\Compiler\Nodes\Php;

use Latte\CompileException;
use Latte\Compiler\Node;
use Latte\Compiler\Position;
use Latte\Compiler\PrintContext;
Expand All @@ -20,6 +21,7 @@ public function __construct(
public IdentifierNode $name,
/** @var ArgumentNode[] */
public array $args = [],
public bool $nullsafe = false,
public ?Position $position = null,
) {
(function (ArgumentNode ...$args) {})(...$args);
Expand All @@ -32,17 +34,23 @@ public function print(PrintContext $context): string
}


public function printSimple(PrintContext $context, string $expr): string
public function printSimple(PrintContext $context, string $expr, ?callable $outer): string
{
return '($this->filters->' . $context->objectProperty($this->name) . ')('
. $expr
. ($this->args ? ', ' . $context->implode($this->args) : '')
. ')';
$outer ??= fn($x) => $x;
return ($this->nullsafe ? '(($ʟ_fv = ' . $expr . ') === null ? null : ' : '')
. $outer('($this->filters->' . $context->objectProperty($this->name) . ')('
. ($this->nullsafe ? '$ʟ_fv' : $expr)
. ($this->args ? ', ' . $context->implode($this->args) : '')
. ')')
. ($this->nullsafe ? ')' : '');
}


public function printContentAware(PrintContext $context, string $expr): string
{
if ($this->nullsafe) {
throw new CompileException('Nullsafe pipe is not allowed here', $this->position);
}
return '$this->filters->filterContent('
. $context->encodeString($this->name->name)
. ', $ʟ_fi, '
Expand Down
4 changes: 3 additions & 1 deletion src/Latte/Compiler/Nodes/Php/ModifierNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function printSimple(PrintContext $context, string $expr): string
{
$escape = $this->escape;
$check = $this->check;
$expr = new Expression\AuxiliaryNode(fn() => $expr);
foreach ($this->filters as $filter) {
$name = $filter->name->name;
if ($name === 'nocheck' || $name === 'noCheck') {
Expand All @@ -59,9 +60,10 @@ public function printSimple(PrintContext $context, string $expr): string
if ($name === 'datastream' || $name === 'dataStream') {
$check = false;
}
$expr = $filter->printSimple($context, $expr);
$expr = new Expression\FilterCallNode($expr, $filter);
}
}
$expr = $expr->print($context);

$escaper = $context->getEscaper();
if ($check) {
Expand Down
1 change: 1 addition & 0 deletions src/Latte/Compiler/TagLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ private function tokenizeCode(): void
(?<Php_PowEqual> \*\*= )|
(?<Php_CoalesceEqual> \?\?= )|
(?<Php_Coalesce> \?\? )|
(?<Php_NullsafePipe> \?\| )|
(?<Php_BooleanOr> \|\| )|
(?<Php_BooleanAnd> && )|
(?<Php_AmpersandFollowed> & (?= [ \t\r\n]* (\$|\.\.\.) ) )|
Expand Down
636 changes: 321 additions & 315 deletions src/Latte/Compiler/TagParserData.php

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/Latte/Compiler/Token.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ final class Token
Php_Comment = 334,
Php_Null = 335,
Php_True = 336,
Php_False = 337;
Php_False = 337,
Php_NullsafePipe = 338;

public const Names = [
self::End => '[EOF]',
Expand Down Expand Up @@ -224,6 +225,7 @@ final class Token
self::Php_Null => "'null'",
self::Php_True => "'true'",
self::Php_False => "'false'",
self::Php_NullsafePipe => "'?|'",
];


Expand Down
6 changes: 6 additions & 0 deletions tests/common/Compiler.errors.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,9 @@ Assert::exception(
Latte\CompileException::class,
"Unexpected '</li>', expecting </a> for element started on line 2 at column 8 (on line 2 at column 37)",
);

Assert::exception(
fn() => $latte->compile('{block |trim?|trim}...{/block}'),
Latte\CompileException::class,
'Nullsafe pipe is not allowed here (on line 1 at column 13)',
);
6 changes: 6 additions & 0 deletions tests/common/TagParser.parseArguments().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ test('inline modifiers', function () {
});


test('inline nullsafe pipe', function () {
Assert::same('(($ʟ_fv = 0) === null ? null : ($this->filters->mod)($ʟ_fv))', formatArgs('(0?|mod)'));
Assert::same('(($ʟ_fv = 0) === null ? null : (($ʟ_fv = ($this->filters->mod2)(($this->filters->mod1)($ʟ_fv))) === null ? null : ($this->filters->mod3)($ʟ_fv)))', formatArgs('(0?|mod1|mod2?|mod3)'));
});


test('in operator', function () {
Assert::same("in_array(\$a, ['a', 'b'], true), 1", formatArgs('$a in [a, b], 1'));
Assert::same('$a, in_array($b->func(), [1, 2], true)', formatArgs('$a, $b->func() in [1, 2]'));
Expand Down
4 changes: 4 additions & 0 deletions tests/common/TagParser.parseModifier().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ test('depth', function () {
});


test('nullsafe pipe', function () {
Assert::same('(($ʟ_fv = @) === null ? null : (($ʟ_fv = ($this->filters->mod2)(($this->filters->mod1)($ʟ_fv))) === null ? null : ($this->filters->mod3)($ʟ_fv)))', format('?|mod1|mod2?|mod3'));
});

test('optionalChainingPass', function () {
Assert::same(
'($this->filters->mod)(@, $var?->prop?->elem[1]?->call(2)?->item)',
Expand Down
10 changes: 10 additions & 0 deletions tests/phpParser/filters.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | name: 'upper'
| | | | | position: 1:5 (offset 4)
| | | | args: array (0)
| | | | nullsafe: false
| | | | position: 1:4 (offset 3)
| | | position: 1:2 (offset 1)
| | key: null
Expand All @@ -60,13 +61,15 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | | name: 'upper'
| | | | | | position: 2:11 (offset 22)
| | | | | args: array (0)
| | | | | nullsafe: false
| | | | | position: 2:10 (offset 21)
| | | | position: 2:2 (offset 13)
| | | filter: Latte\Compiler\Nodes\Php\FilterNode
| | | | name: Latte\Compiler\Nodes\Php\IdentifierNode
| | | | | name: 'truncate'
| | | | | position: 2:17 (offset 28)
| | | | args: array (0)
| | | | nullsafe: false
| | | | position: 2:16 (offset 27)
| | | position: 2:2 (offset 13)
| | key: null
Expand Down Expand Up @@ -102,13 +105,15 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | | | unpack: false
| | | | | | | name: null
| | | | | | | position: 3:20 (offset 58)
| | | | | nullsafe: false
| | | | | position: 3:5 (offset 43)
| | | | position: 3:2 (offset 40)
| | | filter: Latte\Compiler\Nodes\Php\FilterNode
| | | | name: Latte\Compiler\Nodes\Php\IdentifierNode
| | | | | name: 'trim'
| | | | | position: 3:23 (offset 61)
| | | | args: array (0)
| | | | nullsafe: false
| | | | position: 3:22 (offset 60)
| | | position: 3:2 (offset 40)
| | key: null
Expand Down Expand Up @@ -146,19 +151,22 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | | | | | | name: 'round'
| | | | | | | | | | position: 4:24 (offset 91)
| | | | | | | | | args: array (0)
| | | | | | | | | nullsafe: false
| | | | | | | | | position: 4:23 (offset 90)
| | | | | | | | position: 4:21 (offset 88)
| | | | | | | byRef: false
| | | | | | | unpack: false
| | | | | | | name: null
| | | | | | | position: 4:20 (offset 87)
| | | | | nullsafe: false
| | | | | position: 4:5 (offset 72)
| | | | position: 4:2 (offset 69)
| | | filter: Latte\Compiler\Nodes\Php\FilterNode
| | | | name: Latte\Compiler\Nodes\Php\IdentifierNode
| | | | | name: 'trim'
| | | | | position: 4:31 (offset 98)
| | | | args: array (0)
| | | | nullsafe: false
| | | | position: 4:30 (offset 97)
| | | position: 4:2 (offset 69)
| | key: null
Expand Down Expand Up @@ -196,6 +204,7 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | | | name: 'b'
| | | | | | | position: 5:23 (offset 127)
| | | | | | position: 5:23 (offset 127)
| | | | nullsafe: false
| | | | position: 5:5 (offset 109)
| | | position: 5:2 (offset 106)
| | key: null
Expand Down Expand Up @@ -233,6 +242,7 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | | | name: 'b'
| | | | | | | position: 6:23 (offset 159)
| | | | | | position: 6:23 (offset 159)
| | | | nullsafe: false
| | | | position: 6:5 (offset 141)
| | | position: 6:2 (offset 138)
| | key: null
Expand Down
Loading

0 comments on commit 16eb604

Please sign in to comment.