From 77f988c1c90e6dff2130c816c61aec5fb9d4e16b Mon Sep 17 00:00:00 2001 From: Muqsit Date: Tue, 9 Jul 2024 16:02:39 +0000 Subject: [PATCH] Implement logical operators (address #9) --- src/muqsit/arithmexp/Scanner.php | 4 +- .../arithmexp/expression/RawExpression.php | 118 ++++++++++++------ .../arithmexp/operator/OperatorPrecedence.php | 5 + .../binary/BinaryOperatorRegistry.php | 20 +++ src/muqsit/arithmexp/token/OpcodeToken.php | 44 ++++--- 5 files changed, 133 insertions(+), 58 deletions(-) diff --git a/src/muqsit/arithmexp/Scanner.php b/src/muqsit/arithmexp/Scanner.php index b8a6e4b..60f34cd 100644 --- a/src/muqsit/arithmexp/Scanner.php +++ b/src/muqsit/arithmexp/Scanner.php @@ -23,9 +23,9 @@ public static function createDefault(OperatorManager $operator_manager) : self{ new ParenthesisTokenBuilder(), new NumericLiteralTokenBuilder(), new FunctionCallTokenBuilder(), - new IdentifierTokenBuilder(), UnaryOperatorTokenBuilder::createDefault($operator_manager->unary_registry), - BinaryOperatorTokenBuilder::createDefault($operator_manager->binary_registry) + BinaryOperatorTokenBuilder::createDefault($operator_manager->binary_registry), + new IdentifierTokenBuilder() ]); } diff --git a/src/muqsit/arithmexp/expression/RawExpression.php b/src/muqsit/arithmexp/expression/RawExpression.php index fc11b02..0e3ce12 100644 --- a/src/muqsit/arithmexp/expression/RawExpression.php +++ b/src/muqsit/arithmexp/expression/RawExpression.php @@ -41,28 +41,33 @@ public function __construct(string $expression, array $postfix_expression_tokens $this->by_kind[($i++ << 5) | match(true){ $token instanceof OpcodeExpressionToken => match($token->code){ OpcodeToken::OP_BINARY_ADD => 0, - OpcodeToken::OP_BINARY_DIV => 1, - OpcodeToken::OP_BINARY_EQUAL => 2, - OpcodeToken::OP_BINARY_EQUAL_NOT => 3, - OpcodeToken::OP_BINARY_EXP => 4, - OpcodeToken::OP_BINARY_GREATER_THAN => 5, - OpcodeToken::OP_BINARY_GREATER_THAN_EQUAL_TO => 6, - OpcodeToken::OP_BINARY_IDENTICAL => 7, - OpcodeToken::OP_BINARY_IDENTICAL_NOT => 8, - OpcodeToken::OP_BINARY_LESSER_THAN => 9, - OpcodeToken::OP_BINARY_LESSER_THAN_EQUAL_TO => 10, - OpcodeToken::OP_BINARY_MOD => 11, - OpcodeToken::OP_BINARY_MUL => 12, - OpcodeToken::OP_BINARY_SPACESHIP => 13, - OpcodeToken::OP_BINARY_SUB => 14, - OpcodeToken::OP_UNARY_NOT => 15, - OpcodeToken::OP_UNARY_NVE => 16, - OpcodeToken::OP_UNARY_PVE => 17 + OpcodeToken::OP_BINARY_AND_SYMBOL => 1, + OpcodeToken::OP_BINARY_AND_TEXTUAL => 2, + OpcodeToken::OP_BINARY_DIV => 3, + OpcodeToken::OP_BINARY_EQUAL => 4, + OpcodeToken::OP_BINARY_EQUAL_NOT => 5, + OpcodeToken::OP_BINARY_EXP => 6, + OpcodeToken::OP_BINARY_GREATER_THAN => 7, + OpcodeToken::OP_BINARY_GREATER_THAN_EQUAL_TO => 8, + OpcodeToken::OP_BINARY_IDENTICAL => 9, + OpcodeToken::OP_BINARY_IDENTICAL_NOT => 10, + OpcodeToken::OP_BINARY_LESSER_THAN => 11, + OpcodeToken::OP_BINARY_LESSER_THAN_EQUAL_TO => 12, + OpcodeToken::OP_BINARY_MOD => 13, + OpcodeToken::OP_BINARY_MUL => 14, + OpcodeToken::OP_BINARY_OR_SYMBOL => 15, + OpcodeToken::OP_BINARY_OR_TEXTUAL => 16, + OpcodeToken::OP_BINARY_SPACESHIP => 17, + OpcodeToken::OP_BINARY_SUB => 18, + OpcodeToken::OP_BINARY_XOR => 19, + OpcodeToken::OP_UNARY_NOT => 20, + OpcodeToken::OP_UNARY_NVE => 21, + OpcodeToken::OP_UNARY_PVE => 22 }, $token instanceof NumericLiteralExpressionToken, - $token instanceof BooleanLiteralExpressionToken => 18, - $token instanceof VariableExpressionToken => 19, - $token instanceof FunctionCallExpressionToken => 20, + $token instanceof BooleanLiteralExpressionToken => 23, + $token instanceof VariableExpressionToken => 24, + $token instanceof FunctionCallExpressionToken => 25, default => 31 }] = $token; } @@ -80,122 +85,157 @@ public function evaluate(array $variable_values = []) : int|float|bool{ $stack[--$ptr] += $rvalue; break; case 1: + assert($token instanceof OpcodeExpressionToken); + assert($token->code === OpcodeToken::OP_BINARY_AND_SYMBOL); + $lvalue = $stack[$ptr - 1]; + $rvalue = $stack[$ptr]; + $stack[--$ptr] = $lvalue && $rvalue; + break; + case 2: + assert($token instanceof OpcodeExpressionToken); + assert($token->code === OpcodeToken::OP_BINARY_AND_TEXTUAL); + $lvalue = $stack[$ptr - 1]; + $rvalue = $stack[$ptr]; + $stack[--$ptr] = ($lvalue and $rvalue); + break; + case 3: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_DIV); $rvalue = $stack[$ptr]; $stack[--$ptr] /= $rvalue; break; - case 2: + case 4: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_EQUAL); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue == $rvalue; break; - case 3: + case 5: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_EQUAL_NOT); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue != $rvalue; break; - case 4: + case 6: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_EXP); $rvalue = $stack[$ptr]; $stack[--$ptr] **= $rvalue; break; - case 5: + case 7: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_GREATER_THAN); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue > $rvalue; break; - case 6: + case 8: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_GREATER_THAN_EQUAL_TO); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue >= $rvalue; break; - case 7: + case 9: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_IDENTICAL); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue === $rvalue; break; - case 8: + case 10: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_IDENTICAL_NOT); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue !== $rvalue; break; - case 9: + case 11: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_LESSER_THAN); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue < $rvalue; break; - case 10: + case 12: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_LESSER_THAN_EQUAL_TO); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue <= $rvalue; break; - case 11: + case 13: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_MOD); $rvalue = $stack[$ptr]; $stack[--$ptr] %= $rvalue; break; - case 12: + case 14: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_MUL); $rvalue = $stack[$ptr]; $stack[--$ptr] *= $rvalue; break; - case 13: + case 15: + assert($token instanceof OpcodeExpressionToken); + assert($token->code === OpcodeToken::OP_BINARY_OR_SYMBOL); + $lvalue = $stack[$ptr - 1]; + $rvalue = $stack[$ptr]; + $stack[--$ptr] = $lvalue || $rvalue; + break; + case 16: + assert($token instanceof OpcodeExpressionToken); + assert($token->code === OpcodeToken::OP_BINARY_OR_TEXTUAL); + $lvalue = $stack[$ptr - 1]; + $rvalue = $stack[$ptr]; + $stack[--$ptr] = ($lvalue or $rvalue); + break; + case 17: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_SPACESHIP); $lvalue = $stack[$ptr - 1]; $rvalue = $stack[$ptr]; $stack[--$ptr] = $lvalue <=> $rvalue; break; - case 14: + case 18: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_BINARY_SUB); $rvalue = $stack[$ptr]; $stack[--$ptr] -= $rvalue; break; - case 15: + case 19: + assert($token instanceof OpcodeExpressionToken); + assert($token->code === OpcodeToken::OP_BINARY_XOR); + $lvalue = $stack[$ptr - 1]; + $rvalue = $stack[$ptr]; + $stack[--$ptr] = ($lvalue xor $rvalue); + break; + case 20: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_UNARY_NOT); $stack[$ptr] = !$stack[$ptr]; break; - case 16: + case 21: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_UNARY_NVE); $stack[$ptr] = -$stack[$ptr]; break; - case 17: + case 22: assert($token instanceof OpcodeExpressionToken); assert($token->code === OpcodeToken::OP_UNARY_PVE); $stack[$ptr] = +$stack[$ptr]; break; - case 18: + case 23: assert($token instanceof BooleanLiteralExpressionToken || $token instanceof NumericLiteralExpressionToken); $stack[++$ptr] = $token->value; break; - case 19: + case 24: assert($token instanceof VariableExpressionToken); $stack[++$ptr] = $variable_values[$token->label] ?? throw new InvalidArgumentException("No value supplied for variable \"{$token->label}\" in \"{$this->expression}\"");; break; - case 20: + case 25: assert($token instanceof FunctionCallExpressionToken); $ptr -= $token->argument_count - 1; $stack[$ptr] = ($token->function)(...array_slice($stack, $ptr, $token->argument_count)); diff --git a/src/muqsit/arithmexp/operator/OperatorPrecedence.php b/src/muqsit/arithmexp/operator/OperatorPrecedence.php index d64c18a..2bd5f8a 100644 --- a/src/muqsit/arithmexp/operator/OperatorPrecedence.php +++ b/src/muqsit/arithmexp/operator/OperatorPrecedence.php @@ -13,4 +13,9 @@ interface OperatorPrecedence{ public const ADDITION_SUBTRACTION = 4; public const COMPARISON_GREATER_LESSER = 5; public const COMPARISON_EQUALITY = 6; + public const AND_SYMBOL = 7; + public const OR_SYMBOL = 8; + public const AND_TEXTUAL = 9; + public const XOR = 10; + public const OR_TEXTUAL = 11; } \ No newline at end of file diff --git a/src/muqsit/arithmexp/operator/binary/BinaryOperatorRegistry.php b/src/muqsit/arithmexp/operator/binary/BinaryOperatorRegistry.php index f5099f4..41848de 100644 --- a/src/muqsit/arithmexp/operator/binary/BinaryOperatorRegistry.php +++ b/src/muqsit/arithmexp/operator/binary/BinaryOperatorRegistry.php @@ -82,6 +82,26 @@ public static function createDefault() : self{ SimpleFunctionInfo::from(static fn(int|float|bool $x, int|float|bool $y) : int => $x <=> $y, FunctionFlags::DETERMINISTIC), static fn(Parser $parser, string $expression, Token $token, string $function_name, int $argument_count, array $args) : array => [...$args, new OpcodeToken($token->getPos(), OpcodeToken::OP_BINARY_SPACESHIP, $token)] ))); + $registry->register(new SimpleBinaryOperator("&&", "And", OperatorPrecedence::AND_SYMBOL, LeftOperatorAssignment::instance(), new MacroFunctionInfo( + SimpleFunctionInfo::from(static fn(int|float|bool $x, int|float|bool $y) : bool => $x && $y, FunctionFlags::DETERMINISTIC), + static fn(Parser $parser, string $expression, Token $token, string $function_name, int $argument_count, array $args) : array => [...$args, new OpcodeToken($token->getPos(), OpcodeToken::OP_BINARY_AND_SYMBOL, $token)] + ))); + $registry->register(new SimpleBinaryOperator("||", "Or", OperatorPrecedence::OR_SYMBOL, LeftOperatorAssignment::instance(), new MacroFunctionInfo( + SimpleFunctionInfo::from(static fn(int|float|bool $x, int|float|bool $y) : bool => $x || $y, FunctionFlags::DETERMINISTIC), + static fn(Parser $parser, string $expression, Token $token, string $function_name, int $argument_count, array $args) : array => [...$args, new OpcodeToken($token->getPos(), OpcodeToken::OP_BINARY_OR_SYMBOL, $token)] + ))); + $registry->register(new SimpleBinaryOperator("and", "And", OperatorPrecedence::AND_TEXTUAL, LeftOperatorAssignment::instance(), new MacroFunctionInfo( + SimpleFunctionInfo::from(static fn(int|float|bool $x, int|float|bool $y) : bool => $x and $y, FunctionFlags::DETERMINISTIC), + static fn(Parser $parser, string $expression, Token $token, string $function_name, int $argument_count, array $args) : array => [...$args, new OpcodeToken($token->getPos(), OpcodeToken::OP_BINARY_AND_TEXTUAL, $token)] + ))); + $registry->register(new SimpleBinaryOperator("or", "Or", OperatorPrecedence::OR_TEXTUAL, LeftOperatorAssignment::instance(), new MacroFunctionInfo( + SimpleFunctionInfo::from(static fn(int|float|bool $x, int|float|bool $y) : bool => $x or $y, FunctionFlags::DETERMINISTIC), + static fn(Parser $parser, string $expression, Token $token, string $function_name, int $argument_count, array $args) : array => [...$args, new OpcodeToken($token->getPos(), OpcodeToken::OP_BINARY_OR_TEXTUAL, $token)] + ))); + $registry->register(new SimpleBinaryOperator("xor", "Xor", OperatorPrecedence::XOR, LeftOperatorAssignment::instance(), new MacroFunctionInfo( + SimpleFunctionInfo::from(static fn(int|float|bool $x, int|float|bool $y) : bool => $x xor $y, FunctionFlags::DETERMINISTIC), + static fn(Parser $parser, string $expression, Token $token, string $function_name, int $argument_count, array $args) : array => [...$args, new OpcodeToken($token->getPos(), OpcodeToken::OP_BINARY_XOR, $token)] + ))); return $registry; } diff --git a/src/muqsit/arithmexp/token/OpcodeToken.php b/src/muqsit/arithmexp/token/OpcodeToken.php index eea7938..00c05be 100644 --- a/src/muqsit/arithmexp/token/OpcodeToken.php +++ b/src/muqsit/arithmexp/token/OpcodeToken.php @@ -11,23 +11,28 @@ final class OpcodeToken extends SimpleToken{ public const OP_BINARY_ADD = 0; - public const OP_BINARY_DIV = 1; - public const OP_BINARY_EQUAL = 2; - public const OP_BINARY_EQUAL_NOT = 3; - public const OP_BINARY_EXP = 4; - public const OP_BINARY_GREATER_THAN = 5; - public const OP_BINARY_GREATER_THAN_EQUAL_TO = 6; - public const OP_BINARY_IDENTICAL = 7; - public const OP_BINARY_IDENTICAL_NOT = 8; - public const OP_BINARY_LESSER_THAN = 9; - public const OP_BINARY_LESSER_THAN_EQUAL_TO = 10; - public const OP_BINARY_MOD = 11; - public const OP_BINARY_MUL = 12; - public const OP_BINARY_SPACESHIP = 13; - public const OP_BINARY_SUB = 14; - public const OP_UNARY_NOT = 15; - public const OP_UNARY_NVE = 16; - public const OP_UNARY_PVE = 17; + public const OP_BINARY_AND_SYMBOL = 1; + public const OP_BINARY_AND_TEXTUAL = 2; + public const OP_BINARY_DIV = 3; + public const OP_BINARY_EQUAL = 4; + public const OP_BINARY_EQUAL_NOT = 5; + public const OP_BINARY_EXP = 6; + public const OP_BINARY_GREATER_THAN = 7; + public const OP_BINARY_GREATER_THAN_EQUAL_TO = 8; + public const OP_BINARY_IDENTICAL = 9; + public const OP_BINARY_IDENTICAL_NOT = 10; + public const OP_BINARY_LESSER_THAN = 11; + public const OP_BINARY_LESSER_THAN_EQUAL_TO = 12; + public const OP_BINARY_MOD = 13; + public const OP_BINARY_MUL = 14; + public const OP_BINARY_OR_SYMBOL = 15; + public const OP_BINARY_OR_TEXTUAL = 16; + public const OP_BINARY_SPACESHIP = 17; + public const OP_BINARY_SUB = 18; + public const OP_BINARY_XOR = 19; + public const OP_UNARY_NOT = 20; + public const OP_UNARY_NVE = 21; + public const OP_UNARY_PVE = 22; /** * @param self::OP_* $code @@ -36,6 +41,8 @@ final class OpcodeToken extends SimpleToken{ public static function opcodeToString(int $code) : string{ return match($code){ self::OP_BINARY_ADD, self::OP_UNARY_PVE => "+", + self::OP_BINARY_AND_SYMBOL => "&&", + self::OP_BINARY_AND_TEXTUAL => "and", self::OP_BINARY_DIV => "/", self::OP_BINARY_EQUAL => "==", self::OP_BINARY_EQUAL_NOT => "!=", @@ -48,7 +55,10 @@ public static function opcodeToString(int $code) : string{ self::OP_BINARY_LESSER_THAN_EQUAL_TO => "<=", self::OP_BINARY_MOD => "%", self::OP_BINARY_MUL => "*", + self::OP_BINARY_OR_SYMBOL => "||", + self::OP_BINARY_OR_TEXTUAL => "or", self::OP_BINARY_SUB, self::OP_UNARY_NVE => "-", + self::OP_BINARY_XOR => "xor", self::OP_UNARY_NOT => "!" }; }