Skip to content

Commit

Permalink
Allow expressions to return bool value (address #9)
Browse files Browse the repository at this point in the history
  • Loading branch information
Muqsit committed Jul 8, 2024
1 parent 4e581f2 commit edb8239
Show file tree
Hide file tree
Showing 15 changed files with 169 additions and 26 deletions.
11 changes: 10 additions & 1 deletion src/muqsit/arithmexp/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use muqsit\arithmexp\macro\MacroRegistry;
use muqsit\arithmexp\operator\OperatorManager;
use muqsit\arithmexp\token\BinaryOperatorToken;
use muqsit\arithmexp\token\BooleanLiteralToken;
use muqsit\arithmexp\token\builder\ExpressionTokenBuilderState;
use muqsit\arithmexp\token\FunctionCallArgumentSeparatorToken;
use muqsit\arithmexp\token\FunctionCallToken;
Expand All @@ -29,6 +30,9 @@
use function assert;
use function count;
use function is_array;
use function is_bool;
use function is_float;
use function is_int;
use function iterator_to_array;
use function min;

Expand Down Expand Up @@ -336,7 +340,12 @@ private function transformFunctionCallTokens(string $expression, array &$token_t
for($j = 0; $j < $params_c; ++$j){
if($params[$j] === null){
if(isset($fallback_param_values[$j])){
$params[$j] = new NumericLiteralToken($token->getPos()->offset($l, $l), $fallback_param_values[$j]);
$params[$j] = match(true){
is_float($fallback_param_values[$j]),
is_int($fallback_param_values[$j]) => new NumericLiteralToken($token->getPos()->offset($l, $l), $fallback_param_values[$j]),
is_bool($fallback_param_values[$j]) => new BooleanLiteralToken($token->getPos()->offset($l, $l), $fallback_param_values[$j]),
default => throw new RuntimeException("Default value for function call ({$token->function}) parameter " . ($j + 1) . " is of an invalid type " . gettype($fallback_param_values[$j]))
};
++$l;
}else{
throw ParseException::unresolvableFcallNoDefaultParamValue($expression, $token, $j + 1);
Expand Down
2 changes: 2 additions & 0 deletions src/muqsit/arithmexp/Scanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use muqsit\arithmexp\operator\OperatorManager;
use muqsit\arithmexp\token\builder\BinaryOperatorTokenBuilder;
use muqsit\arithmexp\token\builder\BooleanLiteralTokenBuilder;
use muqsit\arithmexp\token\builder\FunctionCallTokenBuilder;
use muqsit\arithmexp\token\builder\IdentifierTokenBuilder;
use muqsit\arithmexp\token\builder\NumericLiteralTokenBuilder;
Expand All @@ -23,6 +24,7 @@ public static function createDefault(OperatorManager $operator_manager) : self{
new ParenthesisTokenBuilder(),
new NumericLiteralTokenBuilder(),
new FunctionCallTokenBuilder(),
new BooleanLiteralTokenBuilder(),
new IdentifierTokenBuilder(),
UnaryOperatorTokenBuilder::createDefault($operator_manager->unary_registry),
BinaryOperatorTokenBuilder::createDefault($operator_manager->binary_registry)
Expand Down
2 changes: 1 addition & 1 deletion src/muqsit/arithmexp/constant/ConstantRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function unregister(string $identifier) : ConstantInfo{
return $info;
}

public function registerLabel(string $identifier, int|float $value) : void{
public function registerLabel(string $identifier, int|float|bool $value) : void{
$this->register($identifier, new SimpleConstantInfo($value));
}

Expand Down
13 changes: 11 additions & 2 deletions src/muqsit/arithmexp/constant/SimpleConstantInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@

namespace muqsit\arithmexp\constant;

use muqsit\arithmexp\expression\token\BooleanLiteralExpressionToken;
use muqsit\arithmexp\expression\token\NumericLiteralExpressionToken;
use muqsit\arithmexp\Parser;
use muqsit\arithmexp\token\builder\ExpressionTokenBuilderState;
use muqsit\arithmexp\token\IdentifierToken;
use RuntimeException;
use function gettype;
use function is_float;
use function is_int;

final class SimpleConstantInfo implements ConstantInfo{

public function __construct(
readonly public int|float $value
readonly public int|float|bool $value
){}

public function writeExpressionTokens(Parser $parser, string $expression, IdentifierToken $token, ExpressionTokenBuilderState $state) : void{
$state->current_group[$state->current_index] = new NumericLiteralExpressionToken($token->getPos(), $this->value);
$state->current_group[$state->current_index] = match(true){
is_int($this->value), is_float($this->value) => new NumericLiteralExpressionToken($token->getPos(), $this->value),
is_bool($this->value) => new BooleanLiteralExpressionToken($token->getPos(), $this->value),
default => throw new RuntimeException("Unexpected value type " . gettype($this->value))
};
}
}
4 changes: 2 additions & 2 deletions src/muqsit/arithmexp/expression/ConstantExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ final class ConstantExpression implements Expression{

public function __construct(
string $expression,
readonly private int|float $value
readonly private int|float|bool $value
){
$this->__parentConstruct($expression, []);
}

public function evaluate(array $variable_values = []) : int|float{
public function evaluate(array $variable_values = []) : int|float|bool{
return $this->value;
}

Expand Down
6 changes: 3 additions & 3 deletions src/muqsit/arithmexp/expression/Expression.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public function getPostfixExpressionTokens() : array;
public function findVariables() : Generator;

/**
* @param array<string, int|float> $variable_values
* @return int|float
* @param array<string, int|float|bool> $variable_values
* @return int|float|bool
*/
public function evaluate(array $variable_values = []) : int|float;
public function evaluate(array $variable_values = []) : int|float|bool;
}
2 changes: 1 addition & 1 deletion src/muqsit/arithmexp/expression/RawExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public function __construct(string $expression, array $postfix_expression_tokens
}
}

public function evaluate(array $variable_values = []) : int|float{
public function evaluate(array $variable_values = []) : int|float|bool{
$stack = [];
$ptr = -1;
foreach($this->by_kind as $index => $token){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
use function array_slice;
use function array_splice;
use function count;
use function is_float;
use function is_int;

final class ConstantFoldingExpressionOptimizer implements ExpressionOptimizer{

Expand All @@ -29,10 +31,10 @@ final class ConstantFoldingExpressionOptimizer implements ExpressionOptimizer{
* @param Expression $expression
* @param ExpressionToken $token
* @param list<ExpressionToken> $arguments
* @return int|float|null
* @return int|float|bool|null
* @throws ParseException
*/
public static function evaluateDeterministicTokens(Parser $parser, Expression $expression, ExpressionToken $token, array $arguments) : int|float|null{
public static function evaluateDeterministicTokens(Parser $parser, Expression $expression, ExpressionToken $token, array $arguments) : int|float|bool|null{
if(count(array_filter($arguments, static fn(ExpressionToken $token) : bool => Util::asFunctionCallExpressionToken($parser, $token) !== null || !$token->isDeterministic())) > 0){
return null;
}
Expand Down Expand Up @@ -94,7 +96,7 @@ public function run(Parser $parser, Expression $expression) : Expression{
/** @var list<ExpressionToken> $arg_tokens */

$value = self::evaluateDeterministicTokens($parser, $expression, $token, $arg_tokens);
if($value === null){
if(!is_int($value) && !is_float($value)){
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use function count;
use function gettype;
use function is_array;
use function is_float;
use function is_int;
use function is_nan;
use const NAN;

Expand Down Expand Up @@ -443,7 +445,8 @@ private function processDivisionBetween(Parser $parser, Expression $expression,
$left_operand[0] instanceof NumericLiteralExpressionToken &&
count($right_operand) === 1 &&
$right_operand[0] instanceof NumericLiteralExpressionToken &&
($result = ConstantFoldingExpressionOptimizer::evaluateDeterministicTokens($parser, $expression, $operator_token, [...$left_operand, ...$right_operand])) !== null
($result = ConstantFoldingExpressionOptimizer::evaluateDeterministicTokens($parser, $expression, $operator_token, [...$left_operand, ...$right_operand])) !== null &&
(is_int($result) || is_float($result))
){
return [
[new NumericLiteralExpressionToken(Util::positionContainingExpressionTokens($left_operand), $result)],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace muqsit\arithmexp\expression\token;

use muqsit\arithmexp\Position;

final class BooleanLiteralExpressionToken implements ExpressionToken{

public function __construct(
public Position $position,
public bool $value
){}

public function getPos() : Position{
return $this->position;
}

public function isDeterministic() : bool{
return true;
}

public function equals(ExpressionToken $other) : bool{
return $other instanceof self && $other->value === $this->value;
}

public function __toString() : string{
return $this->value ? "true" : "false";
}
}
2 changes: 1 addition & 1 deletion src/muqsit/arithmexp/function/FunctionInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface FunctionInfo{
public function getClosure() : Closure;

/**
* @return list<int|float|null>
* @return list<int|float|bool|null>
*/
public function getFallbackParamValues() : array;

Expand Down
8 changes: 4 additions & 4 deletions src/muqsit/arithmexp/function/SimpleFunctionInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ final class SimpleFunctionInfo implements FunctionInfo{
*/
public static function from(Closure $callback, int $flags) : self{
$_function = new ReflectionFunction($callback);
return new self($callback, array_map(static function(ReflectionParameter $_parameter) : int|float|null{
return new self($callback, array_map(static function(ReflectionParameter $_parameter) : int|float|bool|null{
if($_parameter->isDefaultValueAvailable()){
$value = $_parameter->getDefaultValue();
if(!is_int($value) && !is_float($value)){
throw new InvalidArgumentException("Expected default parameter value to be int|float, got " . gettype($value) . " for parameter \"{$_parameter->getName()}\"");
if(!is_int($value) && !is_float($value) && !is_bool($value)){
throw new InvalidArgumentException("Expected default parameter value to be int|float|bool, got " . gettype($value) . " for parameter \"{$_parameter->getName()}\"");
}
return $value;
}
Expand All @@ -40,7 +40,7 @@ public static function from(Closure $callback, int $flags) : self{

/**
* @param Closure $closure
* @param list<int|float|null> $fallback_param_values
* @param list<int|float|bool|null> $fallback_param_values
* @param bool $variadic
* @param int-mask-of<FunctionFlags::*> $flags
*/
Expand Down
37 changes: 37 additions & 0 deletions src/muqsit/arithmexp/token/BooleanLiteralToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace muqsit\arithmexp\token;

use muqsit\arithmexp\expression\token\BooleanLiteralExpressionToken;
use muqsit\arithmexp\Position;
use muqsit\arithmexp\token\builder\ExpressionTokenBuilderState;

final class BooleanLiteralToken extends SimpleToken{

public function __construct(
Position $position,
readonly public bool $value
){
parent::__construct(TokenType::BOOLEAN_LITERAL(), $position);
}

public function repositioned(Position $position) : self{
return new self($position, $this->value);
}

public function writeExpressionTokens(ExpressionTokenBuilderState $state) : void{
$state->current_group[$state->current_index] = new BooleanLiteralExpressionToken($this->position, $this->value);
}

public function __debugInfo() : array{
$info = parent::__debugInfo();
$info["value"] = $this->value;
return $info;
}

public function jsonSerialize() : string{
return $this->value ? "true" : "false";
}
}
20 changes: 13 additions & 7 deletions src/muqsit/arithmexp/token/TokenType.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,25 @@
final class TokenType{

public const BINARY_OPERATOR = 0;
public const FUNCTION_CALL = 1;
public const FUNCTION_CALL_ARGUMENT_SEPARATOR = 2;
public const IDENTIFIER = 3;
public const NUMERIC_LITERAL = 4;
public const OPCODE = 5;
public const PARENTHESIS = 6;
public const UNARY_OPERATOR = 7;
public const BOOLEAN_LITERAL = 1;
public const FUNCTION_CALL = 2;
public const FUNCTION_CALL_ARGUMENT_SEPARATOR = 3;
public const IDENTIFIER = 4;
public const NUMERIC_LITERAL = 5;
public const OPCODE = 6;
public const PARENTHESIS = 7;
public const UNARY_OPERATOR = 8;

public static function BINARY_OPERATOR() : self{
static $instance = null;
return $instance ??= new self(self::BINARY_OPERATOR, "Binary Operator");
}

public static function BOOLEAN_LITERAL() : self{
static $instance = null;
return $instance ??= new self(self::BOOLEAN_LITERAL, "Boolean Literal");
}

public static function FUNCTION_CALL() : self{
static $instance = null;
return $instance ??= new self(self::FUNCTION_CALL, "Function Call");
Expand Down
44 changes: 44 additions & 0 deletions src/muqsit/arithmexp/token/builder/BooleanLiteralTokenBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace muqsit\arithmexp\token\builder;

use Generator;
use muqsit\arithmexp\Position;
use muqsit\arithmexp\token\BooleanLiteralToken;
use muqsit\arithmexp\token\NumericLiteralToken;
use function rtrim;
use function str_contains;
use function strlen;
use function substr;

final class BooleanLiteralTokenBuilder implements TokenBuilder{

private const VALUES = ["true" => true, "false" => false];
private const BUFFER_LENGTH = 5; // max(len(keys(self::VALUES)))

public function __construct(){
}

public function build(TokenBuilderState $state) : Generator{
$buffer = "";
$offset = $state->offset;
$start = $offset;
$length = $state->length;
$expression = $state->expression;
while($offset < $length){
$char = $expression[$offset];
$buffer = substr($buffer . $char, -self::BUFFER_LENGTH);
if(isset(self::VALUES[$buffer])){
$value = self::VALUES[$buffer];
yield new BooleanLiteralToken(new Position($start, $offset), $value);
break;
}
$offset++;
}
}

public function transform(TokenBuilderState $state) : void{
}
}

0 comments on commit edb8239

Please sign in to comment.