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 10, 2024
1 parent 5744a3a commit 1f88558
Show file tree
Hide file tree
Showing 14 changed files with 715 additions and 333 deletions.
24 changes: 20 additions & 4 deletions src/Latte/Compiler/Nodes/Php/Expression/FiltersCallNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,27 @@ public function __construct(

public function print(PrintContext $context): string
{
$expr = $this->expr->print($context);
foreach ($this->filters as $filter) {
$expr = $filter->printSimple($context, $expr);
return self::printFilters($context, $this->filters, $this->expr->print($context));
}


/**
* @param FilterNode[] $filters
*/
public static function printFilters(PrintContext $context, array $filters, string $expr): string
{
$filter = array_shift($filters);
if (!$filter) {
return $expr;
}
return $expr;
$filterExpr = '($this->filters->' . $context->objectProperty($filter->name) . ')('
. ($filter->nullsafe ? '$ʟ_fv' : $expr)
. ($filter->args ? ', ' . $context->implode($filter->args) : '')
. ')';
$filterExpr = self::printFilters($context, $filters, $filterExpr);
return $filter->nullsafe
? '(($ʟ_fv = ' . $expr . ') === null ? null : ' . $filterExpr . ')'
: $filterExpr;
}


Expand Down
14 changes: 5 additions & 9 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,11 @@ public function print(PrintContext $context): string
}


public function printSimple(PrintContext $context, string $expr): string
{
return '($this->filters->' . $context->objectProperty($this->name) . ')('
. $expr
. ($this->args ? ', ' . $context->implode($this->args) : '')
. ')';
}


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
5 changes: 4 additions & 1 deletion src/Latte/Compiler/Nodes/Php/ModifierNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace Latte\Compiler\Nodes\Php;

use Latte\Compiler\Node;
use Latte\Compiler\Nodes\Php\Expression\FiltersCallNode;
use Latte\Compiler\Position;
use Latte\Compiler\PrintContext;

Expand Down Expand Up @@ -49,6 +50,7 @@ public function printSimple(PrintContext $context, string $expr): string
{
$escape = $this->escape;
$check = $this->check;
$filters = [];
foreach ($this->filters as $filter) {
$name = $filter->name->name;
if ($name === 'nocheck' || $name === 'noCheck') {
Expand All @@ -59,9 +61,10 @@ public function printSimple(PrintContext $context, string $expr): string
if ($name === 'datastream' || $name === 'dataStream') {
$check = false;
}
$expr = $filter->printSimple($context, $expr);
$filters[] = $filter;
}
}
$expr = FiltersCallNode::printFilters($context, $filters, $expr);

$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
638 changes: 322 additions & 316 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 @@ -38,6 +38,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:1 (offset 0)
| | key: null
Expand All @@ -61,12 +62,14 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | | name: 'upper'
| | | | | | position: 2:11 (offset 22)
| | | | | args: array (0)
| | | | | nullsafe: false
| | | | | position: 2:10 (offset 21)
| | | | 1 => 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:1 (offset 12)
| | key: null
Expand Down Expand Up @@ -102,12 +105,14 @@ Latte\Compiler\Nodes\Php\Expression\ArrayNode
| | | | | | | unpack: false
| | | | | | | name: null
| | | | | | | position: 3:20 (offset 58)
| | | | | nullsafe: false
| | | | | position: 3:5 (offset 43)
| | | | 1 => 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:1 (offset 39)
| | key: null
Expand Down Expand Up @@ -146,18 +151,21 @@ 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:20 (offset 87)
| | | | | | | byRef: false
| | | | | | | unpack: false
| | | | | | | name: null
| | | | | | | position: 4:20 (offset 87)
| | | | | nullsafe: false
| | | | | position: 4:5 (offset 72)
| | | | 1 => 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:1 (offset 68)
| | 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:1 (offset 105)
| | key: null
Expand Down Expand Up @@ -234,6 +243,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:1 (offset 137)
| | key: null
Expand Down
Loading

0 comments on commit 1f88558

Please sign in to comment.