diff --git a/src/Call.php b/src/Call.php index 2a5335d..420dab8 100644 --- a/src/Call.php +++ b/src/Call.php @@ -34,6 +34,7 @@ public function __construct( public readonly Type $type, public readonly array $arguments, Span $location, + public readonly CallType $callType = CallType::Method, ) { $this->location = $location; } @@ -58,8 +59,12 @@ private static function compareArguments(array $a, array $b): bool public function __toString(): string { - /** @psalm-suppress ImplicitToStringCast */ - return sprintf('%s.%s:%s(%s)', $this->target, $this->name, $this->type, implode(', ', $this->arguments)); + return match ($this->callType) { + CallType::Infix => sprintf('%s %s %s', $this->target, $this->name, $this->arguments[0]), + CallType::Prefix => sprintf('%s%s', $this->name, $this->target), + /** @psalm-suppress ImplicitToStringCast */ + CallType::Method => sprintf('%s.%s:%s(%s)', $this->target, $this->name, $this->type, implode(', ', $this->arguments)), + }; } public function evaluate(Scope $scope): mixed diff --git a/src/CallType.php b/src/CallType.php new file mode 100644 index 0000000..69e75e6 --- /dev/null +++ b/src/CallType.php @@ -0,0 +1,12 @@ + - * @internal - * @psalm-internal Eventjet\Ausdruck - */ -final class Eq extends Expression -{ - /** - * @param Expression $left - * @param Expression $right - */ - public function __construct(public readonly Expression $left, public readonly Expression $right) - { - } - - public function __toString(): string - { - /** @psalm-suppress ImplicitToStringCast */ - return sprintf('%s === %s', $this->left, $this->right); - } - - public function evaluate(Scope $scope): bool - { - return $this->left->evaluate($scope) === $this->right->evaluate($scope); - } - - public function equals(Expression $other): bool - { - return $other instanceof self - && $this->left->equals($other->left) - && $this->right->equals($other->right); - } - - public function getType(): Type - { - return Type::bool(); - } - - public function location(): Span - { - return $this->left->location()->to($this->right->location()); - } -} diff --git a/src/Expr.php b/src/Expr.php index 41823b5..4953dcf 100644 --- a/src/Expr.php +++ b/src/Expr.php @@ -26,10 +26,11 @@ private function __construct() * @template T * @param Expression $left * @param Expression $right + * @return Expression */ - public static function eq(Expression $left, Expression $right): Eq + public static function eq(Expression $left, Expression $right): Expression { - return new Eq($left, $right); + return new Call($left, '===', Type::bool(), [$right], $left->location()->to($right->location()), CallType::Infix); } /** @@ -80,10 +81,11 @@ public static function call(Expression $target, string $name, Type $type, array /** * @param Expression $left * @param Expression $right + * @return Expression */ - public static function or_(Expression $left, Expression $right): Or_ + public static function or_(Expression $left, Expression $right): Expression { - return new Or_($left, $right); + return new Call($left, '||', Type::bool(), [$right], $left->location()->to($right->location()), CallType::Infix); } /** @@ -103,32 +105,40 @@ public static function lambda(Expression $body, array $parameters = [], Span|nul * @template T of int | float * @param Expression $minuend * @param Expression $subtrahend - * @return Subtract + * @return Expression */ - public static function subtract(Expression $minuend, Expression $subtrahend): Subtract + public static function subtract(Expression $minuend, Expression $subtrahend): Expression { - return new Subtract($minuend, $subtrahend); + return new Call( + $minuend, + '-', + $minuend->getType(), + [$subtrahend], + $minuend->location()->to($subtrahend->location()), + CallType::Infix, + ); } /** * @template T of int | float * @param Expression $left * @param Expression $right - * @return Gt + * @return Expression */ - public static function gt(Expression $left, Expression $right): Gt + public static function gt(Expression $left, Expression $right): Expression { - return new Gt($left, $right); + return new Call($left, '>', Type::bool(), [$right], $left->location()->to($right->location()), CallType::Infix); } /** * @template T of int | float * @param Expression $expression - * @return Negative + * @return Expression */ - public static function negative(Expression $expression, Span|null $location = null): Negative + public static function negative(Expression $expression, Span|null $location = null): Expression { - return new Negative($expression, $location ?? self::dummySpan()); + $location ??= self::dummySpan(); + return new Call($expression, '-', $expression->getType(), [], $location, CallType::Prefix); } private static function dummySpan(): Span diff --git a/src/Expression.php b/src/Expression.php index 1d192bd..56a5be8 100644 --- a/src/Expression.php +++ b/src/Expression.php @@ -15,8 +15,9 @@ abstract class Expression implements Stringable { /** * @param self $other + * @return Expression */ - public function eq(self $other): Eq + public function eq(self $other): self { return Expr::eq($this, $other); } @@ -24,9 +25,9 @@ public function eq(self $other): Eq /** * @template U of int | float * @param Expression $subtrahend - * @return Subtract + * @return Expression */ - public function subtract(self $subtrahend): Subtract + public function subtract(self $subtrahend): self { /** @var self $self */ $self = $this; @@ -36,9 +37,9 @@ public function subtract(self $subtrahend): Subtract /** * @template U of int | float * @param Expression $right - * @return Gt + * @return Expression */ - public function gt(self $right): Gt + public function gt(self $right): self { /** @var self $self */ $self = $this; @@ -47,8 +48,9 @@ public function gt(self $right): Gt /** * @param self $other + * @return Expression */ - public function or_(self $other): Or_ + public function or_(self $other): self { /** @var self $self */ $self = $this; diff --git a/src/Gt.php b/src/Gt.php deleted file mode 100644 index 69cf300..0000000 --- a/src/Gt.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @internal - * @psalm-internal Eventjet\Ausdruck - */ -final class Gt extends Expression -{ - /** - * @param Expression $left - * @param Expression $right - */ - public function __construct(private readonly Expression $left, private readonly Expression $right) - { - } - - public function __toString(): string - { - /** @psalm-suppress ImplicitToStringCast */ - return sprintf('%s > %s', $this->left, $this->right); - } - - public function evaluate(Scope $scope): bool - { - return $this->left->evaluate($scope) > $this->right->evaluate($scope); - } - - public function equals(Expression $other): bool - { - return $other instanceof self - && $this->left->equals($other->left) - && $this->right->equals($other->right); - } - - public function getType(): Type - { - return Type::bool(); - } - - public function location(): Span - { - return $this->left->location()->to($this->right->location()); - } -} diff --git a/src/Negative.php b/src/Negative.php deleted file mode 100644 index e853de6..0000000 --- a/src/Negative.php +++ /dev/null @@ -1,51 +0,0 @@ - - */ -final class Negative extends Expression -{ - use LocationTrait; - - /** - * @param Expression $expression - */ - public function __construct(public readonly Expression $expression, Span $location) - { - $this->location = $location; - } - - public function __toString(): string - { - return sprintf('-%s', $this->expression); - } - - /** - * @psalm-suppress InvalidReturnType False positive - */ - public function evaluate(Scope $scope): float|int - { - /** @psalm-suppress InvalidReturnStatement False positive */ - return -$this->expression->evaluate($scope); - } - - public function equals(Expression $other): bool - { - return $other instanceof self - && $this->expression->equals($other->expression); - } - - public function getType(): Type - { - return $this->expression->getType(); - } -} diff --git a/src/Or_.php b/src/Or_.php deleted file mode 100644 index 2520f61..0000000 --- a/src/Or_.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @internal - * @psalm-internal Eventjet\Ausdruck - */ -final class Or_ extends Expression -{ - /** - * @param Expression $left - * @param Expression $right - */ - public function __construct(public readonly Expression $left, public readonly Expression $right) - { - } - - public function __toString(): string - { - /** @psalm-suppress ImplicitToStringCast */ - return sprintf('%s || %s', $this->left, $this->right); - } - - public function evaluate(Scope $scope): bool - { - return $this->left->evaluate($scope) || $this->right->evaluate($scope); - } - - public function equals(Expression $other): bool - { - return $other instanceof self - && $this->left->equals($other->left) - && $this->right->equals($other->right); - } - - public function getType(): Type - { - return Type::bool(); - } - - public function location(): Span - { - return $this->left->location()->to($this->right->location()); - } -} diff --git a/src/Scope.php b/src/Scope.php index 50eeab0..3607fb3 100644 --- a/src/Scope.php +++ b/src/Scope.php @@ -41,6 +41,10 @@ final class Scope public function __construct(private readonly array $vars = [], array $funcs = [], private readonly Scope|null $parent = null) { $predefinedFuncs = $this->parent === null ? [ + '||' => self::or(...), + '===' => self::eq(...), + '-' => self::subtract(...), + '>' => self::gt(...), 'contains' => self::contains(...), 'count' => self::count(...), 'filter' => self::filter(...), @@ -193,6 +197,33 @@ private static function identity(mixed $value): mixed return $value; } + private static function or(bool $left, bool $right): bool + { + return $left || $right; + } + + private static function eq(mixed $left, mixed $right): bool + { + return $left === $right; + } + + /** + * @return ($minuend is float ? float : ($subtrahend is float ? float : int)) + */ + private static function subtract(int|float $minuend, int|float|null $subtrahend = null): int|float + { + if ($subtrahend === null) { + return -$minuend; + } + /** @psalm-suppress InvalidOperand */ + return $minuend - $subtrahend; + } + + private static function gt(int|float $left, int|float $right): bool + { + return $left > $right; + } + /** * @internal */ diff --git a/src/Subtract.php b/src/Subtract.php deleted file mode 100644 index c8dbfc7..0000000 --- a/src/Subtract.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @internal - * @psalm-internal Eventjet\Ausdruck - */ -final class Subtract extends Expression -{ - /** - * @param Expression $minuend - * @param Expression $subtrahend - */ - public function __construct(public readonly Expression $minuend, public readonly Expression $subtrahend) - { - } - - public function __toString(): string - { - /** @psalm-suppress ImplicitToStringCast */ - return sprintf('%s - %s', $this->minuend, $this->subtrahend); - } - - /** - * @psalm-suppress InvalidReturnType False positive - */ - public function evaluate(Scope $scope): int|float - { - /** - * @psalm-suppress InvalidOperand False positive; they are guaranteed to have the same type - * @psalm-suppress InvalidReturnStatement False positive - */ - return $this->minuend->evaluate($scope) - $this->subtrahend->evaluate($scope); - } - - public function equals(Expression $other): bool - { - return $other instanceof self - && $this->minuend->equals($other->minuend) - && $this->subtrahend->equals($other->subtrahend); - } - - public function getType(): Type - { - return $this->minuend->getType(); - } - - public function location(): Span - { - return $this->minuend->location()->to($this->subtrahend->location()); - } -} diff --git a/tests/unit/ExpressionComparisonTest.php b/tests/unit/ExpressionComparisonTest.php index 5ee7664..14d1621 100644 --- a/tests/unit/ExpressionComparisonTest.php +++ b/tests/unit/ExpressionComparisonTest.php @@ -5,18 +5,13 @@ namespace Eventjet\Ausdruck\Test\Unit; use Eventjet\Ausdruck\Call; -use Eventjet\Ausdruck\Eq; use Eventjet\Ausdruck\Expr; use Eventjet\Ausdruck\Expression; use Eventjet\Ausdruck\Get; -use Eventjet\Ausdruck\Gt; use Eventjet\Ausdruck\Lambda; use Eventjet\Ausdruck\ListLiteral; use Eventjet\Ausdruck\Literal; -use Eventjet\Ausdruck\Negative; -use Eventjet\Ausdruck\Or_; use Eventjet\Ausdruck\Parser\Span; -use Eventjet\Ausdruck\Subtract; use Eventjet\Ausdruck\Type; use PHPUnit\Framework\TestCase; @@ -66,15 +61,15 @@ public static function equalsCases(): iterable */ public static function notEqualsCases(): iterable { - yield Eq::class . ': left is different' => [ + yield '===: left is different' => [ Expr::eq(Expr::literal('a'), Expr::literal('a')), Expr::eq(Expr::literal('b'), Expr::literal('a')), ]; - yield Eq::class . ': right is different' => [ + yield '===: right is different' => [ Expr::eq(Expr::literal('a'), Expr::literal('a')), Expr::eq(Expr::literal('a'), Expr::literal('b')), ]; - yield Eq::class . ': different type' => [ + yield '===: different type' => [ Expr::eq(Expr::literal('a'), Expr::literal('a')), Expr::or_(Expr::literal(true), Expr::literal(false)), ]; @@ -90,15 +85,15 @@ public static function notEqualsCases(): iterable Expr::get('a', Type::string()), Expr::literal('a'), ]; - yield Lambda::class .': different number of parameters' => [ + yield Lambda::class . ': different number of parameters' => [ Expr::lambda(Expr::literal(true), ['foo']), Expr::lambda(Expr::literal(true), ['foo', 'bar']), ]; - yield Lambda::class .': different parameter' => [ + yield Lambda::class . ': different parameter' => [ Expr::lambda(Expr::literal(true), ['a', 'b', 'c']), Expr::lambda(Expr::literal(true), ['a', 'x', 'c']), ]; - yield Lambda::class .': different body' => [ + yield Lambda::class . ': different body' => [ Expr::lambda(Expr::literal(true), ['a', 'b', 'c']), Expr::lambda(Expr::literal(false), ['a', 'b', 'c']), ]; @@ -114,35 +109,35 @@ public static function notEqualsCases(): iterable Expr::literal('foo'), Expr::get('foo', Type::string()), ]; - yield Or_::class . ': left is different' => [ + yield '||: left is different' => [ Expr::or_(Expr::literal(true), Expr::literal(false)), Expr::or_(Expr::literal(false), Expr::literal(false)), ]; - yield Or_::class . ': right is different' => [ + yield '||: right is different' => [ Expr::or_(Expr::literal(true), Expr::literal(false)), Expr::or_(Expr::literal(true), Expr::literal(true)), ]; - yield Or_::class . ': both are different' => [ + yield '||: both are different' => [ Expr::or_(Expr::literal(true), Expr::literal(false)), Expr::or_(Expr::literal(false), Expr::literal(true)), ]; - yield Or_::class . ': different type' => [ + yield '||: different type' => [ Expr::or_(Expr::literal(true), Expr::literal(false)), Expr::eq(Expr::literal(true), Expr::literal(false)), ]; - yield Subtract::class . ': minuend is different' => [ + yield '-: minuend is different' => [ Expr::subtract(Expr::literal(1), Expr::literal(2)), Expr::subtract(Expr::literal(2), Expr::literal(2)), ]; - yield Subtract::class . ': subtrahend is different' => [ + yield '-: subtrahend is different' => [ Expr::subtract(Expr::literal(1), Expr::literal(2)), Expr::subtract(Expr::literal(1), Expr::literal(1)), ]; - yield Subtract::class . ': subtrahend and minuend are different' => [ + yield '-: subtrahend and minuend are different' => [ Expr::subtract(Expr::literal(1), Expr::literal(2)), Expr::subtract(Expr::literal(2), Expr::literal(1)), ]; - yield Subtract::class . ': different type' => [ + yield '-: different type' => [ Expr::subtract(Expr::literal(1), Expr::literal(2)), Expr::literal(1), ]; @@ -170,27 +165,27 @@ public static function notEqualsCases(): iterable Expr::call(Expr::literal(1), 'foo', Type::int(), []), Expr::literal(1), ]; - yield Gt::class . ': left is different' => [ + yield '>: left is different' => [ Expr::gt(Expr::literal(1), Expr::literal(2)), Expr::gt(Expr::literal(2), Expr::literal(2)), ]; - yield Gt::class . ': right is different' => [ + yield '>: right is different' => [ Expr::gt(Expr::literal(1), Expr::literal(2)), Expr::gt(Expr::literal(1), Expr::literal(1)), ]; - yield Gt::class . ': both are different' => [ + yield '>: both are different' => [ Expr::gt(Expr::literal(1), Expr::literal(2)), Expr::gt(Expr::literal(2), Expr::literal(1)), ]; - yield Gt::class . ': different type' => [ + yield '>: different type' => [ Expr::gt(Expr::literal(1), Expr::literal(2)), Expr::eq(Expr::literal(1), Expr::literal(2)), ]; - yield Negative::class . ': different type' => [ + yield 'Negative: different type' => [ Expr::negative(Expr::literal(1)), Expr::literal(1), ]; - yield Negative::class . ': different expression' => [ + yield 'Negative: different expression' => [ Expr::negative(Expr::literal(1)), Expr::negative(Expr::literal(2)), ]; diff --git a/tests/unit/ExpressionTest.php b/tests/unit/ExpressionTest.php index b51e4b2..81dbde4 100644 --- a/tests/unit/ExpressionTest.php +++ b/tests/unit/ExpressionTest.php @@ -220,6 +220,7 @@ public static function toStringCases(): iterable ]; yield 'Any type' => ['myval:any', 'myval:any']; yield 'List literal' => ['["foo", "bar"]', '["foo", "bar"]']; + yield 'Negative variable' => ['-myval:int', '-myval:int']; } /**