Sitelet https://github.com/phpbench/phpbench/pull/1153/files
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lib/Expression/Ast/CallableNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace PhpBench\Expression\Ast;

final class CallableNode extends Node
{
public function __construct(private Node $callable, private ArgumentListNode $args)
{
}


public function callable(): Node
{
return $this->callable;
}

public function args(): ArgumentListNode
{
return $this->args;
}
}
21 changes: 21 additions & 0 deletions lib/Expression/Ast/LambdaNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace PhpBench\Expression\Ast;

final class LambdaNode extends Node
{
public function __construct(private ArgumentListNode $node, private Node $expression)
{
}

public function params(): ArgumentListNode
{
return $this->node;
}

public function expression(): Node
{
return $this->expression;
}

}
23 changes: 23 additions & 0 deletions lib/Expression/Func/UserFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace PhpBench\Expression\Func;

use PhpBench\Expression\Ast\LambdaNode;

final class UserFunction
{
public function __construct(
private LambdaNode $expr
) {
}

public function lambda(): LambdaNode
{
return $this->expr;
}

public function __invoke(...$args): array

Check failure on line 19 in lib/Expression/Func/UserFunction.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5)

Method PhpBench\Expression\Func\UserFunction::__invoke() return type has no value type specified in iterable type array.

Check failure on line 19 in lib/Expression/Func/UserFunction.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5)

Method PhpBench\Expression\Func\UserFunction::__invoke() has parameter $args with no type specified.
{
return $args;
}
}
1 change: 1 addition & 0 deletions lib/Expression/Lexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ final class Lexer
'-' => Token::T_MINUS,
'*' => Token::T_MULTIPLY,
'%' => Token::T_PERCENTAGE,
'=>' => Token::T_LAMBDA,
'/' => Token::T_DIVIDE,
'>=' => Token::T_GTE,
'<=' => Token::T_LTE,
Expand Down
34 changes: 34 additions & 0 deletions lib/Expression/NodeEvaluator/CallableEvaluator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace PhpBench\Expression\NodeEvaluator;

use PhpBench\Expression\Ast\CallableNode;
use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\Ast\ScalarValue;
use PhpBench\Expression\Evaluator;
use PhpBench\Expression\Exception\EvaluationError;
use PhpBench\Expression\NodeEvaluator;

class CallableEvaluator implements NodeEvaluator
{
public function evaluate(Evaluator $evaluator, Node $node, array $params): ?Node
{
if (!$node instanceof CallableNode) {
return null;
}
$args = array_map(function (Node $node) use ($evaluator, $params) {
$value = $evaluator->evaluate($node, $params);

if (!$value instanceof ScalarValue) {
throw new EvaluationError($node, sprintf(
'Expected expression to resolve to a scalar value, got %s',
$value::class
));
}

return $value->value();
}, $node->args()->nodes());

return $evaluator->evaluate($node->callable(), $args);
}
}
6 changes: 6 additions & 0 deletions lib/Expression/NodeEvaluator/FunctionEvaluator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
namespace PhpBench\Expression\NodeEvaluator;

use PhpBench\Expression\Ast\ArgumentListNode;
use PhpBench\Expression\Ast\CallableNode;
use PhpBench\Expression\Ast\FunctionNode;
use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\Ast\PhpValue;
use PhpBench\Expression\Evaluator;
use PhpBench\Expression\Exception\EvaluationError;
use PhpBench\Expression\ExpressionFunctions;
use PhpBench\Expression\Func\UserFunction;
use PhpBench\Expression\LazyExpr;
use PhpBench\Expression\LazyFunction;
use PhpBench\Expression\NodeEvaluator;
Expand Down Expand Up @@ -51,6 +53,10 @@ public function doEvaluate(Evaluator $evaluator, FunctionNode $node, array $para
try {
$function = $this->functions->get($node->name());

if ($function instanceof UserFunction) {
return $evaluator->evaluate(new CallableNode($function->lambda(), $node->args()), []);
}

if ($function instanceof LazyFunction) {
$args = array_map(function (Node $node) use ($evaluator, $params) {
return new LazyExpr($evaluator, $node, $params);
Expand Down
36 changes: 36 additions & 0 deletions lib/Expression/NodeEvaluator/LambdaEvaluator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace PhpBench\Expression\NodeEvaluator;

use PhpBench\Expression\Ast\LambdaNode;
use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\Ast\VariableNode;
use PhpBench\Expression\Evaluator;
use PhpBench\Expression\Exception\EvaluationError;
use PhpBench\Expression\Exception\SyntaxError;
use PhpBench\Expression\NodeEvaluator;

final class LambdaEvaluator implements NodeEvaluator
{
public function evaluate(Evaluator $evaluator, Node $node, array $params): ?Node
{
if (!$node instanceof LambdaNode) {
return null;
}

$names = array_map(function (VariableNode $param) {

Check failure on line 21 in lib/Expression/NodeEvaluator/LambdaEvaluator.php

View workflow job for this annotation

GitHub Actions / PHPStan (8.5)

Parameter #1 $callback of function array_map expects (callable(PhpBench\Expression\Ast\Node): mixed)|null, Closure(PhpBench\Expression\Ast\VariableNode): string given.
return $param->name();
}, $node->params()->nodes());

if (count($names) !== count($params)) {
throw new EvaluationError($node, sprintf('Expected %d arguments, got %d',
count($names),
count($params)
));
}

$args = array_combine($names, $params);

return $evaluator->evaluate($node->expression(), $args);
}
}
24 changes: 24 additions & 0 deletions lib/Expression/NodePrinter/CallablePrinter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace PhpBench\Expression\NodePrinter;

use PhpBench\Expression\Ast\CallableNode;
use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\NodePrinter;
use PhpBench\Expression\Printer;

class CallablePrinter implements NodePrinter
{
public function print(Printer $printer, Node $node): ?string
{
if (!$node instanceof CallableNode) {
return null;
}

return sprintf(
'(%s)(%s)',
$printer->print($node->callable()),
$printer->print($node->args())
);
}
}
26 changes: 26 additions & 0 deletions lib/Expression/NodePrinter/LambdaPrinter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace PhpBench\Expression\NodePrinter;

use PhpBench\Expression\Ast\CallableNode;
use PhpBench\Expression\Ast\ComparisonNode;
use PhpBench\Expression\Ast\LambdaNode;
use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\NodePrinter;
use PhpBench\Expression\Printer;

class LambdaPrinter implements NodePrinter
{
public function print(Printer $printer, Node $node): ?string
{
if (!$node instanceof LambdaNode) {
return null;
}

return sprintf(
'(%s) => %s',
$printer->print($node->params()),
$printer->print($node->expression())
);
}
}
45 changes: 45 additions & 0 deletions lib/Expression/Parselet/CallableParselet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace PhpBench\Expression\Parselet;

use PhpBench\Expression\Ast\ArgumentListNode;
use PhpBench\Expression\Ast\CallableNode;
use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\Ast\ParenthesisNode;
use PhpBench\Expression\Exception\SyntaxError;
use PhpBench\Expression\Parser;
use PhpBench\Expression\SuffixParselet;
use PhpBench\Expression\Token;
use PhpBench\Expression\Tokens;

class CallableParselet implements SuffixParselet
{
public function tokenType(): string
{
return Token::T_OPEN_PAREN;
}

public function parse(Parser $parser, Node $left, Tokens $tokens): Node
{
$callable = $left;
$tokens->chomp(Token::T_OPEN_PAREN);
$args = [];
if ($tokens->current()->type !== Token::T_CLOSE_PAREN) {
$args = $parser->parseList($tokens);
if (!$args instanceof ArgumentListNode) {
$args = new ArgumentListNode([$args]);
}
}
$tokens->chomp(Token::T_CLOSE_PAREN);

if (!$callable instanceof ParenthesisNode) {
throw SyntaxError::forToken(
$tokens,
$tokens->current(),
'Callable expression must be parenthesized'
);
}

return new CallableNode($callable->expression(), $args);
}
}
60 changes: 60 additions & 0 deletions lib/Expression/Parselet/LambdaParselet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace PhpBench\Expression\Parselet;

use PhpBench\Expression\Ast\ArgumentListNode;
use PhpBench\Expression\Ast\LambdaNode;
use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\Ast\ParenthesisNode;
use PhpBench\Expression\Ast\VariableNode;
use PhpBench\Expression\Exception\SyntaxError;
use PhpBench\Expression\InfixParselet;
use PhpBench\Expression\Parser;
use PhpBench\Expression\Precedence;
use PhpBench\Expression\Token;
use PhpBench\Expression\Tokens;

class LambdaParselet implements InfixParselet
{
public function tokenType(): string
{
return Token::T_LAMBDA;
}

public function parse(Parser $parser, Node $left, Tokens $tokens): Node
{
$argList = $left;
$lambda = $tokens->chomp();

if (!$argList instanceof ParenthesisNode) {
throw SyntaxError::forToken($tokens, $lambda, sprintf(
'Left operand to lambda must be a parenthesized list of arguments, got: %s',
$left::class
));
}

$argList = $argList->expression();

if (!$argList instanceof ArgumentListNode) {
$argList = new ArgumentListNode([$argList]);
}

$expression = $parser->parseExpression($tokens);

return new LambdaNode(new ArgumentListNode(array_map(function (Node $node) use ($tokens, $lambda) {
if (!$node instanceof VariableNode) {
throw SyntaxError::forToken($tokens, $lambda, sprintf(
'Invalid parameter of type: %s',
$node::class
));
}

return $node;
}, $argList->nodes())), $expression);
}

public function precedence(): int
{
return Precedence::LAMBDA;
}
}
3 changes: 2 additions & 1 deletion lib/Expression/Parselet/PercentageParselet.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpBench\Expression\Ast\Node;
use PhpBench\Expression\Ast\PercentageNode;
use PhpBench\Expression\Parser;
use PhpBench\Expression\SuffixParselet;
use PhpBench\Expression\Token;
use PhpBench\Expression\Tokens;
Expand All @@ -15,7 +16,7 @@ public function tokenType(): string
return Token::T_PERCENTAGE;
}

public function parse(Node $left, Tokens $tokens): Node
public function parse(Parser $parser, Node $left, Tokens $tokens): Node
{
$tokens->chomp();

Expand Down
3 changes: 2 additions & 1 deletion lib/Expression/Parselet/ValueWithUnitParselet.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use PhpBench\Expression\Ast\StringNode;
use PhpBench\Expression\Ast\UnitNode;
use PhpBench\Expression\Ast\ValueWithUnitNode;
use PhpBench\Expression\Parser;
use PhpBench\Expression\SuffixParselet;
use PhpBench\Expression\Token;
use PhpBench\Expression\Tokens;
Expand All @@ -17,7 +18,7 @@ public function tokenType(): string
return Token::T_UNIT;
}

public function parse(Node $left, Tokens $tokens): Node
public function parse(Parser $parser, Node $left, Tokens $tokens): Node
{
return new ValueWithUnitNode($left, new UnitNode(new StringNode($tokens->chomp()->value)));
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Expression/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function parseExpression(Tokens $tokens, int $precedence = 0): Node
$suffixParser = $this->suffixParselets->forTokenOrNull($tokens->current());

if ($suffixParser instanceof SuffixParselet) {
$left = $suffixParser->parse($left, $tokens);
$left = $suffixParser->parse($this, $left, $tokens);
}

while ($precedence < $this->infixPrecedence($tokens->current())) {
Expand Down
1 change: 1 addition & 0 deletions lib/Expression/Precedence.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

final class Precedence
{
public const LAMBDA = 200;
public const ACCESS = 120;
public const AS = 110;
public const PRODUCT = 100;
Expand Down
2 changes: 1 addition & 1 deletion lib/Expression/SuffixParselet.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

interface SuffixParselet extends Parselet
{
public function parse(Node $left, Tokens $tokens): Node;
public function parse(Parser $parser, Node $left, Tokens $tokens): Node;
}
1 change: 1 addition & 0 deletions lib/Expression/Token.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ final class Token
public const T_STRING = 'string';
public const T_PARAMETER = 'parameter';
public const T_QUESTION = 'question';
public const T_LAMBDA = 'lambda';

public function __construct(public string $type, public string $value, public int $offset)
{
Expand Down
Loading
Loading