diff --git a/.github/workflows/phpqa.yml b/.github/workflows/phpqa.yml index 661b9e4..f28a069 100644 --- a/.github/workflows/phpqa.yml +++ b/.github/workflows/phpqa.yml @@ -8,14 +8,14 @@ jobs: steps: - uses: actions/checkout@master - name: PHPStan - uses: docker://jakzal/phpqa:php8.0-alpine + uses: docker://jakzal/phpqa:php8.4 with: args: phpstan analyze src/ -l 1 - name: PHP-CS-Fixer - uses: docker://jakzal/phpqa:php8.0-alpine + uses: docker://jakzal/phpqa:php8.4 with: args: php-cs-fixer --dry-run --allow-risky=yes --no-interaction --ansi fix - name: Deptrac - uses: docker://jakzal/phpqa:php8.0-alpine + uses: docker://jakzal/phpqa:php8.4 with: args: deptrac --no-interaction --ansi --formatter-graphviz-display=0 diff --git a/.gitignore b/.gitignore index 35f8f3f..3e04865 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /coverage.clover /.phpunit.result.cache /vendor/ +/.idea +/.composer diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 37f56a0..64e37a8 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -35,8 +35,9 @@ tools: enabled: true excluded_dirs: [tests] build: + image: default-bionic environment: - php: 8.0.1 + php: 8.4.0 nodes: analysis: tests: diff --git a/.styleci.yml b/.styleci.yml index 03b0848..93c2ae9 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,10 +1,12 @@ --- preset: psr2 risky: true -version: 8 +version: 8.4 finder: name: - "*.php" enabled: - short_array_syntax - cast_spaces +disabled: + - lowercase_constants diff --git a/.travis.yml b/.travis.yml index fa2e489..5161ca6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: php php: - - 8.0 + - 8.4 script: - composer install --dev diff --git a/README.md b/README.md index a656d84..ad7afe6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ ## PHP Rule Engine - [![Latest Stable Version](https://travis-ci.org/nicoSWD/php-rule-parser.svg?branch=master)](https://travis-ci.org/nicoSWD/php-rule-parser) [![Build status][Master coverage image]][Master coverage] [![Code Quality][Master quality image]][Master quality] @@ -13,13 +12,10 @@ You're looking at a standalone PHP library to parse and evaluate text based rule This library has initially been used to change and configure the behavior of certain "Workflows" (without changing actual code) in an intranet application, but it may serve a purpose elsewhere. - Find me on Twitter: @[nicoSWD](https://twitter.com/nicoSWD) (If you're using PHP 5, you might want to take a look at [version 0.4.0](https://github.com/nicoSWD/php-rule-parser/tree/0.4.0)) -[![SensioLabsInsight](https://insight.sensiolabs.com/projects/67203389-970c-419c-9430-a7f9a005bd94/big.png)](https://insight.sensiolabs.com/projects/67203389-970c-419c-9430-a7f9a005bd94) - ## Install Via Composer @@ -40,23 +36,29 @@ This library works best with one of these bundles below, but they're not require Test if a value is in a given array ```php -$variables = ['foo' => 6]; +$variables = [ + 'coupon_code' => (string) $_POST['coupon_code'], +]; -$rule = new Rule('foo in [4, 6, 7]', $variables); +$rule = new Rule('coupon_code in ["summer_discount", "summer21"]', $variables); var_dump($rule->isTrue()); // bool(true) ``` -Simple array manipulation +Performing a regular expression ```php -$rule = new Rule('[1, 4, 3].join(".") === "1.4.3"'); +$variables = [ + 'coupon_code' => (string) $_POST['coupon_code'], +]; + +$rule = new Rule('coupon_code.test(/^summer20[0-9]{2}$/) == true', $variables); var_dump($rule->isTrue()); // bool(true) ``` Test if a value is between a given range ```php -$variables = ['threshold' => 80]; +$variables = ['points' => 80]; -$rule = new Rule('threshold >= 50 && threshold <= 100', $variables); +$rule = new Rule('points >= 50 && points <= 100', $variables); var_dump($rule->isTrue()); // bool(true) ``` @@ -64,8 +66,6 @@ Call methods on objects from within rules ```php class User { - // ... - public function points(): int { return 1337; @@ -180,7 +180,7 @@ $highlighter = new Rule\Highlighter\Highlighter(new Rule\Tokenizer()); // Optional custom styles $highlighter->setStyle( - Rule\Constants::GROUP_VARIABLE, + TokenType::VARIABLE, 'color: #007694; font-weight: 900;' ); diff --git a/composer.json b/composer.json index eabf79b..bbcab55 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ { "name": "Nicolas Oelgart", "role": "Developer", - "homepage": "https://nicoswd.com" + "homepage": "https://nico.es" } ], "autoload": { @@ -33,11 +33,11 @@ } }, "require": { - "php": ">=8.0" + "php": ">=8.4" }, "require-dev": { - "phpunit/phpunit": "^7.0|^9.0", - "mockery/mockery": "^1.0|^1.4" + "phpunit/phpunit": "^12.3", + "mockery/mockery": "^1.6" }, "scripts": { "test": "vendor/bin/phpunit" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index db6f484..82d373f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,16 @@ - - - src - - + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.3/phpunit.xsd" + bootstrap="src/autoload.php" + cacheDirectory=".phpunit.cache"> tests/ + + + src + + diff --git a/src/Compiler/CompilerFactory.php b/src/Compiler/CompilerFactory.php index c4fea98..0be47bc 100644 --- a/src/Compiler/CompilerFactory.php +++ b/src/Compiler/CompilerFactory.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Compiler; diff --git a/src/Compiler/CompilerFactoryInterface.php b/src/Compiler/CompilerFactoryInterface.php index 1e42aec..69a2ed6 100644 --- a/src/Compiler/CompilerFactoryInterface.php +++ b/src/Compiler/CompilerFactoryInterface.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Compiler; diff --git a/src/Compiler/CompilerInterface.php b/src/Compiler/CompilerInterface.php index 90a2121..832cce6 100644 --- a/src/Compiler/CompilerInterface.php +++ b/src/Compiler/CompilerInterface.php @@ -3,19 +3,21 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Compiler; use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\Type\Logical; +use nicoSWD\Rule\TokenStream\Token\Type\Parenthesis; interface CompilerInterface { public function getCompiledRule(): string; - public function addParentheses(BaseToken $token): void; + public function addParentheses(BaseToken & Parenthesis $token): void; - public function addLogical(BaseToken $token): void; + public function addLogical(BaseToken & Logical $token): void; public function addBoolean(bool $bool): void; } diff --git a/src/Compiler/Exception/MissingOperatorException.php b/src/Compiler/Exception/MissingOperatorException.php index ba6d049..e551ffd 100644 --- a/src/Compiler/Exception/MissingOperatorException.php +++ b/src/Compiler/Exception/MissingOperatorException.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Compiler\Exception; diff --git a/src/Compiler/StandardCompiler.php b/src/Compiler/StandardCompiler.php index 2d1d7f4..4006d39 100644 --- a/src/Compiler/StandardCompiler.php +++ b/src/Compiler/StandardCompiler.php @@ -3,26 +3,24 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Compiler; use nicoSWD\Rule\Compiler\Exception\MissingOperatorException; +use nicoSWD\Rule\Evaluator\Boolean; +use nicoSWD\Rule\Evaluator\Operator; use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenAnd; use nicoSWD\Rule\TokenStream\Token\TokenOpeningParenthesis; +use nicoSWD\Rule\TokenStream\Token\Type\Logical; +use nicoSWD\Rule\TokenStream\Token\Type\Parenthesis; -class StandardCompiler implements CompilerInterface +final class StandardCompiler implements CompilerInterface { - private const BOOL_TRUE = '1'; - private const BOOL_FALSE = '0'; - - private const LOGICAL_AND = '&'; - private const LOGICAL_OR = '|'; - - private const OPENING_PARENTHESIS = '('; - private const CLOSING_PARENTHESIS = ')'; + private const string OPENING_PARENTHESIS = '('; + private const string CLOSING_PARENTHESIS = ')'; private string $output = ''; private int $openParenthesis = 0; @@ -58,7 +56,7 @@ private function closeParenthesis(): void } /** @throws ParserException */ - public function addParentheses(BaseToken $token): void + public function addParentheses(BaseToken & Parenthesis $token): void { if ($token instanceof TokenOpeningParenthesis) { if (!$this->expectOpeningParenthesis()) { @@ -71,31 +69,27 @@ public function addParentheses(BaseToken $token): void } /** @throws ParserException */ - public function addLogical(BaseToken $token): void + public function addLogical(BaseToken & Logical $token): void { - $lastChar = $this->getLastChar(); - - if ($lastChar === self::LOGICAL_AND || $lastChar === self::LOGICAL_OR) { + if (Operator::tryFrom($this->getLastChar()) !== null) { throw ParserException::unexpectedToken($token); } if ($token instanceof TokenAnd) { - $this->output .= self::LOGICAL_AND; + $this->output .= Operator::LOGICAL_AND->value; } else { - $this->output .= self::LOGICAL_OR; + $this->output .= Operator::LOGICAL_OR->value; } } /** @throws MissingOperatorException */ public function addBoolean(bool $bool): void { - $lastChar = $this->getLastChar(); - - if ($lastChar === self::BOOL_TRUE || $lastChar === self::BOOL_FALSE) { + if (Boolean::tryFrom($this->getLastChar()) !== null) { throw new MissingOperatorException(); } - $this->output .= $bool ? self::BOOL_TRUE : self::BOOL_FALSE; + $this->output .= Boolean::fromBool($bool)->value; } private function numParenthesesMatch(): bool @@ -105,11 +99,7 @@ private function numParenthesesMatch(): bool private function isIncompleteCondition(): bool { - $lastChar = $this->getLastChar(); - - return - $lastChar === self::LOGICAL_AND || - $lastChar === self::LOGICAL_OR; + return Operator::tryFrom($this->getLastChar()) !== null; } private function expectOpeningParenthesis(): bool @@ -118,9 +108,8 @@ private function expectOpeningParenthesis(): bool return $lastChar === '' || - $lastChar === self::LOGICAL_AND || - $lastChar === self::LOGICAL_OR || - $lastChar === self::OPENING_PARENTHESIS; + $lastChar === self::OPENING_PARENTHESIS || + Operator::tryFrom($lastChar) !== null; } private function getLastChar(): string diff --git a/src/Evaluator/Boolean.php b/src/Evaluator/Boolean.php new file mode 100644 index 0000000..529ee71 --- /dev/null +++ b/src/Evaluator/Boolean.php @@ -0,0 +1,19 @@ + + */ +namespace nicoSWD\Rule\Evaluator; + +enum Boolean: string +{ + case TRUE = '1'; + case FALSE = '0'; + + final public static function fromBool(bool $bool): self + { + return $bool ? self::TRUE : self::FALSE; + } +} diff --git a/src/Evaluator/Evaluator.php b/src/Evaluator/Evaluator.php index 4d1797a..e9672ec 100644 --- a/src/Evaluator/Evaluator.php +++ b/src/Evaluator/Evaluator.php @@ -3,18 +3,14 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Evaluator; +use Closure; + final class Evaluator implements EvaluatorInterface { - private const LOGICAL_AND = '&'; - private const LOGICAL_OR = '|'; - - private const BOOL_TRUE = '1'; - private const BOOL_FALSE = '0'; - public function evaluate(string $group): bool { $evalGroup = $this->evalGroup(); @@ -22,7 +18,7 @@ public function evaluate(string $group): bool do { $group = preg_replace_callback( - '~\(([^()]+)\)~', + '~\((?[^()]+)\)~', $evalGroup, $group, limit: -1, @@ -30,22 +26,23 @@ public function evaluate(string $group): bool ); } while ($count > 0); - return (bool) $evalGroup([1 => $group]); + return (bool) $evalGroup(['match' => $group]); } - private function evalGroup(): callable + private function evalGroup(): Closure { return function (array $group): ?int { $result = null; $operator = null; $offset = 0; - while (isset($group[1][$offset])) { - $value = $group[1][$offset++]; + while (isset($group['match'][$offset])) { + $value = $group['match'][$offset++]; + $possibleOperator = Operator::tryFrom($value); - if ($this->isLogical($value)) { - $operator = $value; - } elseif ($this->isBoolean($value)) { + if ($possibleOperator) { + $operator = $possibleOperator; + } elseif (Boolean::tryFrom($value)) { $result = $this->setResult($result, (int) $value, $operator); } else { throw new Exception\UnknownSymbolException(sprintf('Unexpected "%s"', $value)); @@ -56,26 +53,16 @@ private function evalGroup(): callable }; } - private function setResult(?int $result, int $value, ?string $operator): int + private function setResult(?int $result, int $value, ?Operator $operator): int { if (!isset($result)) { $result = $value; - } elseif ($operator === self::LOGICAL_AND) { + } elseif (Operator::isAnd($operator)) { $result &= $value; - } elseif ($operator === self::LOGICAL_OR) { + } elseif (Operator::isOr($operator)) { $result |= $value; } return $result; } - - private function isLogical(string $value): bool - { - return $value === self::LOGICAL_AND || $value === self::LOGICAL_OR; - } - - private function isBoolean(string $value): bool - { - return $value === self::BOOL_TRUE || $value === self::BOOL_FALSE; - } } diff --git a/src/Evaluator/EvaluatorInterface.php b/src/Evaluator/EvaluatorInterface.php index fc04b08..dee7ecd 100644 --- a/src/Evaluator/EvaluatorInterface.php +++ b/src/Evaluator/EvaluatorInterface.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Evaluator; diff --git a/src/Evaluator/Exception/UnknownSymbolException.php b/src/Evaluator/Exception/UnknownSymbolException.php index 0737f50..85ddec5 100644 --- a/src/Evaluator/Exception/UnknownSymbolException.php +++ b/src/Evaluator/Exception/UnknownSymbolException.php @@ -3,10 +3,10 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Evaluator\Exception; -class UnknownSymbolException extends \Exception +final class UnknownSymbolException extends \Exception { } diff --git a/src/Evaluator/Operator.php b/src/Evaluator/Operator.php new file mode 100644 index 0000000..651fa30 --- /dev/null +++ b/src/Evaluator/Operator.php @@ -0,0 +1,24 @@ + + */ +namespace nicoSWD\Rule\Evaluator; + +enum Operator: string +{ + case LOGICAL_AND = '&'; + case LOGICAL_OR = '|'; + + public static function isAnd(self $operator): bool + { + return $operator === self::LOGICAL_AND; + } + + public static function isOr(self $operator): bool + { + return $operator === self::LOGICAL_OR; + } +} diff --git a/src/Expression/BaseExpression.php b/src/Expression/BaseExpression.php index 74a269d..5cff5a4 100644 --- a/src/Expression/BaseExpression.php +++ b/src/Expression/BaseExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/EqualExpression.php b/src/Expression/EqualExpression.php index dbf7a84..89b2eb6 100644 --- a/src/Expression/EqualExpression.php +++ b/src/Expression/EqualExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/EqualStrictExpression.php b/src/Expression/EqualStrictExpression.php index a8f39e6..de98cbe 100644 --- a/src/Expression/EqualStrictExpression.php +++ b/src/Expression/EqualStrictExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/ExpressionFactory.php b/src/Expression/ExpressionFactory.php index 9a3ba34..c87dab4 100644 --- a/src/Expression/ExpressionFactory.php +++ b/src/Expression/ExpressionFactory.php @@ -3,20 +3,21 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\Type\Operator; -class ExpressionFactory implements ExpressionFactoryInterface +final class ExpressionFactory implements ExpressionFactoryInterface { /** @throws ParserException */ - public function createFromOperator(BaseToken $operator): BaseExpression + public function createFromOperator(BaseToken & Operator $operator): BaseExpression { - return match (get_class($operator)) { + return match ($operator::class) { Token\TokenEqual::class => new EqualExpression(), Token\TokenEqualStrict::class => new EqualStrictExpression(), Token\TokenNotEqual::class => new NotEqualExpression(), diff --git a/src/Expression/ExpressionFactoryInterface.php b/src/Expression/ExpressionFactoryInterface.php index 0f12bfc..488b437 100644 --- a/src/Expression/ExpressionFactoryInterface.php +++ b/src/Expression/ExpressionFactoryInterface.php @@ -3,13 +3,14 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\Type\Operator; interface ExpressionFactoryInterface { - public function createFromOperator(BaseToken $operator): BaseExpression; + public function createFromOperator(BaseToken & Operator $operator): BaseExpression; } diff --git a/src/Expression/GreaterThanEqualExpression.php b/src/Expression/GreaterThanEqualExpression.php index 3bffd26..5f413d9 100644 --- a/src/Expression/GreaterThanEqualExpression.php +++ b/src/Expression/GreaterThanEqualExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/GreaterThanExpression.php b/src/Expression/GreaterThanExpression.php index 4814872..d9dedb5 100644 --- a/src/Expression/GreaterThanExpression.php +++ b/src/Expression/GreaterThanExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/InExpression.php b/src/Expression/InExpression.php index 61ab5e2..837e7ce 100644 --- a/src/Expression/InExpression.php +++ b/src/Expression/InExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/LessThanEqualExpression.php b/src/Expression/LessThanEqualExpression.php index f76bbc9..5ca7959 100644 --- a/src/Expression/LessThanEqualExpression.php +++ b/src/Expression/LessThanEqualExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/LessThanExpression.php b/src/Expression/LessThanExpression.php index 8618aa7..89d9f5f 100644 --- a/src/Expression/LessThanExpression.php +++ b/src/Expression/LessThanExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/NotEqualExpression.php b/src/Expression/NotEqualExpression.php index 4c4b3c4..fba4703 100644 --- a/src/Expression/NotEqualExpression.php +++ b/src/Expression/NotEqualExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/NotEqualStrictExpression.php b/src/Expression/NotEqualStrictExpression.php index f839e7e..162f973 100644 --- a/src/Expression/NotEqualStrictExpression.php +++ b/src/Expression/NotEqualStrictExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Expression/NotInExpression.php b/src/Expression/NotInExpression.php index 58e8f5e..518ba9d 100644 --- a/src/Expression/NotInExpression.php +++ b/src/Expression/NotInExpression.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Expression; diff --git a/src/Grammar/CallableFunction.php b/src/Grammar/CallableFunction.php index 5ac80da..4694ab8 100644 --- a/src/Grammar/CallableFunction.php +++ b/src/Grammar/CallableFunction.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar; @@ -11,8 +11,9 @@ abstract class CallableFunction implements CallableUserFunctionInterface { - public function __construct(protected ?BaseToken $token = null) - { + public function __construct( + protected readonly ?BaseToken $token = null, + ) { } protected function parseParameter(array $parameters, int $numParam): ?BaseToken diff --git a/src/Grammar/CallableUserFunctionInterface.php b/src/Grammar/CallableUserFunctionInterface.php index e3f1bd3..55ef2a4 100644 --- a/src/Grammar/CallableUserFunctionInterface.php +++ b/src/Grammar/CallableUserFunctionInterface.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar; diff --git a/src/Grammar/Definition.php b/src/Grammar/Definition.php new file mode 100644 index 0000000..324922a --- /dev/null +++ b/src/Grammar/Definition.php @@ -0,0 +1,20 @@ + + */ +namespace nicoSWD\Rule\Grammar; + +use nicoSWD\Rule\TokenStream\Token\Token; + +final readonly class Definition +{ + public function __construct( + public Token $token, + public string $regex, + public int $priority, + ) { + } +} diff --git a/src/Grammar/Grammar.php b/src/Grammar/Grammar.php index 63a9572..1ce1820 100644 --- a/src/Grammar/Grammar.php +++ b/src/Grammar/Grammar.php @@ -3,24 +3,58 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar; +use SplPriorityQueue; + abstract class Grammar { - /** @return array> */ + private string $compiledRegex = ''; + /** @var Definition[] */ + private array $tokens = []; + + /** @return Definition[] */ abstract public function getDefinition(): array; - /** @return array */ - public function getInternalFunctions(): array + /** @return InternalFunction[] */ + abstract public function getInternalFunctions(): array; + + /** @return InternalMethod[] */ + abstract public function getInternalMethods(): array; + + public function buildRegex(): string + { + if (!$this->compiledRegex) { + $this->registerTokens(); + $regex = []; + + foreach ($this->getQueue() as $token) { + $regex[] = "(?<{$token->token->value}>{$token->regex})"; + } + + $this->compiledRegex = '~(' . implode('|', $regex) . ')~As'; + } + + return $this->compiledRegex; + } + + private function getQueue(): SplPriorityQueue { - return []; + $queue = new SplPriorityQueue(); + + foreach ($this->tokens as $token) { + $queue->insert($token, $token->priority); + } + + return $queue; } - /** @return array */ - public function getInternalMethods(): array + private function registerTokens(): void { - return []; + foreach ($this->getDefinition() as $definition) { + $this->tokens[$definition->token->value] = $definition; + } } } diff --git a/src/Grammar/InternalFunction.php b/src/Grammar/InternalFunction.php new file mode 100644 index 0000000..fbfdf58 --- /dev/null +++ b/src/Grammar/InternalFunction.php @@ -0,0 +1,17 @@ + + */ +namespace nicoSWD\Rule\Grammar; + +final readonly class InternalFunction +{ + public function __construct( + public string $name, + public string $class, + ) { + } +} diff --git a/src/Grammar/InternalMethod.php b/src/Grammar/InternalMethod.php new file mode 100644 index 0000000..8bec86d --- /dev/null +++ b/src/Grammar/InternalMethod.php @@ -0,0 +1,17 @@ + + */ +namespace nicoSWD\Rule\Grammar; + +final readonly class InternalMethod +{ + public function __construct( + public string $name, + public string $class, + ) { + } +} diff --git a/src/Grammar/JavaScript/Functions/ParseFloat.php b/src/Grammar/JavaScript/Functions/ParseFloat.php index c9238bc..e627879 100644 --- a/src/Grammar/JavaScript/Functions/ParseFloat.php +++ b/src/Grammar/JavaScript/Functions/ParseFloat.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Functions; diff --git a/src/Grammar/JavaScript/Functions/ParseInt.php b/src/Grammar/JavaScript/Functions/ParseInt.php index 08edf11..b693162 100644 --- a/src/Grammar/JavaScript/Functions/ParseInt.php +++ b/src/Grammar/JavaScript/Functions/ParseInt.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Functions; diff --git a/src/Grammar/JavaScript/JavaScript.php b/src/Grammar/JavaScript/JavaScript.php index 6314217..84e84aa 100644 --- a/src/Grammar/JavaScript/JavaScript.php +++ b/src/Grammar/JavaScript/JavaScript.php @@ -3,11 +3,14 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript; +use nicoSWD\Rule\Grammar\Definition; use nicoSWD\Rule\Grammar\Grammar; +use nicoSWD\Rule\Grammar\InternalFunction; +use nicoSWD\Rule\Grammar\InternalMethod; use nicoSWD\Rule\TokenStream\Token\Token; final class JavaScript extends Grammar @@ -15,63 +18,63 @@ final class JavaScript extends Grammar public function getDefinition(): array { return [ - [Token::AND, '&&', 145], - [Token::OR, '\|\|', 140], - [Token::NOT_EQUAL_STRICT, '!==', 135], - [Token::NOT_EQUAL, '<>|!=', 130], - [Token::EQUAL_STRICT, '===', 125], - [Token::EQUAL, '==', 120], - [Token::IN, '\bin\b', 115], - [Token::NOT_IN, '\bnot\s+in\b', 116], - [Token::BOOL_TRUE, '\btrue\b', 110], - [Token::BOOL_FALSE, '\bfalse\b', 111], - [Token::NULL, '\bnull\b', 105], - [Token::METHOD, '\.\s*[a-zA-Z_]\w*\s*\(', 100], - [Token::FUNCTION, '[a-zA-Z_]\w*\s*\(', 95], - [Token::FLOAT, '-?\d+(?:\.\d+)', 90], - [Token::INTEGER, '-?\d+', 85], - [Token::ENCAPSED_STRING, '"[^"]*"|\'[^\']*\'', 80], - [Token::SMALLER_EQUAL, '<=', 75], - [Token::GREATER_EQUAL, '>=', 70], - [Token::SMALLER, '<', 65], - [Token::GREATER, '>', 60], - [Token::OPENING_PARENTHESIS, '\(', 55], - [Token::CLOSING_PARENTHESIS, '\)', 50], - [Token::OPENING_ARRAY, '\[', 45], - [Token::CLOSING_ARRAY, '\]', 40], - [Token::COMMA, ',', 35], - [Token::REGEX, '/[^/\*].*/[igm]{0,3}', 30], - [Token::COMMENT, '//[^\r\n]*|/\*.*?\*/', 25], - [Token::NEWLINE, '\r?\n', 20], - [Token::SPACE, '\s+', 15], - [Token::VARIABLE, '[a-zA-Z_]\w*', 10], - [Token::UNKNOWN, '.', 5], + new Definition(Token::AND, '&&', 145), + new Definition(Token::OR, '\|\|', 140), + new Definition(Token::NOT_EQUAL_STRICT, '!==', 135), + new Definition(Token::NOT_EQUAL, '<>|!=', 130), + new Definition(Token::EQUAL_STRICT, '===', 125), + new Definition(Token::EQUAL, '==', 120), + new Definition(Token::IN, '\bin\b', 115), + new Definition(Token::NOT_IN, '\bnot\s+in\b', 116), + new Definition(Token::BOOL_TRUE, '\btrue\b', 110), + new Definition(Token::BOOL_FALSE, '\bfalse\b', 111), + new Definition(Token::NULL, '\bnull\b', 105), + new Definition(Token::METHOD, '\.\s*[a-zA-Z_]\w*\s*\(', 100), + new Definition(Token::FUNCTION, '[a-zA-Z_]\w*\s*\(', 95), + new Definition(Token::FLOAT, '-?\d+(?:\.\d+)', 90), + new Definition(Token::INTEGER, '-?\d+', 85), + new Definition(Token::ENCAPSED_STRING, '"[^"]*"|\'[^\']*\'', 80), + new Definition(Token::SMALLER_EQUAL, '<=', 75), + new Definition(Token::GREATER_EQUAL, '>=', 70), + new Definition(Token::SMALLER, '<', 65), + new Definition(Token::GREATER, '>', 60), + new Definition(Token::OPENING_PARENTHESIS, '\(', 55), + new Definition(Token::CLOSING_PARENTHESIS, '\)', 50), + new Definition(Token::OPENING_ARRAY, '\[', 45), + new Definition(Token::CLOSING_ARRAY, '\]', 40), + new Definition(Token::COMMA, ',', 35), + new Definition(Token::REGEX, '/[^/\*].*/[igm]{0,3}', 30), + new Definition(Token::COMMENT, '//[^\r\n]*|/\*.*?\*/', 25), + new Definition(Token::NEWLINE, '\r?\n', 20), + new Definition(Token::SPACE, '\s+', 15), + new Definition(Token::VARIABLE, '[a-zA-Z_]\w*', 10), + new Definition(Token::UNKNOWN, '.', 5), ]; } public function getInternalFunctions(): array { return [ - 'parseInt' => Functions\ParseInt::class, - 'parseFloat' => Functions\ParseFloat::class, + new InternalFunction('parseInt', Functions\ParseInt::class), + new InternalFunction('parseFloat', Functions\ParseFloat::class), ]; } public function getInternalMethods(): array { return [ - 'charAt' => Methods\CharAt::class, - 'concat' => Methods\Concat::class, - 'indexOf' => Methods\IndexOf::class, - 'join' => Methods\Join::class, - 'replace' => Methods\Replace::class, - 'split' => Methods\Split::class, - 'substr' => Methods\Substr::class, - 'test' => Methods\Test::class, - 'toLowerCase' => Methods\ToLowerCase::class, - 'toUpperCase' => Methods\ToUpperCase::class, - 'startsWith' => Methods\StartsWith::class, - 'endsWith' => Methods\EndsWith::class, + new InternalMethod('charAt', Methods\CharAt::class), + new InternalMethod('concat', Methods\Concat::class), + new InternalMethod('indexOf', Methods\IndexOf::class), + new InternalMethod('join', Methods\Join::class), + new InternalMethod('replace', Methods\Replace::class), + new InternalMethod('split', Methods\Split::class), + new InternalMethod('substr', Methods\Substr::class), + new InternalMethod('test', Methods\Test::class), + new InternalMethod('toLowerCase', Methods\ToLowerCase::class), + new InternalMethod('toUpperCase', Methods\ToUpperCase::class), + new InternalMethod('startsWith', Methods\StartsWith::class), + new InternalMethod('endsWith', Methods\EndsWith::class), ]; } } diff --git a/src/Grammar/JavaScript/Methods/CharAt.php b/src/Grammar/JavaScript/Methods/CharAt.php index 2276caa..ee0063b 100644 --- a/src/Grammar/JavaScript/Methods/CharAt.php +++ b/src/Grammar/JavaScript/Methods/CharAt.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; @@ -27,11 +27,7 @@ public function call(?BaseToken ...$parameters): BaseToken $offset = $offset->getValue(); } - if (!isset($tokenValue[$offset])) { - $char = ''; - } else { - $char = $tokenValue[$offset]; - } + $char = $tokenValue[$offset] ?? ''; return new TokenString($char); } diff --git a/src/Grammar/JavaScript/Methods/Concat.php b/src/Grammar/JavaScript/Methods/Concat.php index d009a17..5d9678f 100644 --- a/src/Grammar/JavaScript/Methods/Concat.php +++ b/src/Grammar/JavaScript/Methods/Concat.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/EndsWith.php b/src/Grammar/JavaScript/Methods/EndsWith.php index ed22fd1..eddab8f 100644 --- a/src/Grammar/JavaScript/Methods/EndsWith.php +++ b/src/Grammar/JavaScript/Methods/EndsWith.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/IndexOf.php b/src/Grammar/JavaScript/Methods/IndexOf.php index d70366b..695e20d 100644 --- a/src/Grammar/JavaScript/Methods/IndexOf.php +++ b/src/Grammar/JavaScript/Methods/IndexOf.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/Join.php b/src/Grammar/JavaScript/Methods/Join.php index 0ce5f33..348c9ad 100644 --- a/src/Grammar/JavaScript/Methods/Join.php +++ b/src/Grammar/JavaScript/Methods/Join.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/Replace.php b/src/Grammar/JavaScript/Methods/Replace.php index ca6b683..209aea5 100644 --- a/src/Grammar/JavaScript/Methods/Replace.php +++ b/src/Grammar/JavaScript/Methods/Replace.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/Split.php b/src/Grammar/JavaScript/Methods/Split.php index 279e70e..c255049 100644 --- a/src/Grammar/JavaScript/Methods/Split.php +++ b/src/Grammar/JavaScript/Methods/Split.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/StartsWith.php b/src/Grammar/JavaScript/Methods/StartsWith.php index 980d7e2..de47339 100644 --- a/src/Grammar/JavaScript/Methods/StartsWith.php +++ b/src/Grammar/JavaScript/Methods/StartsWith.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/Substr.php b/src/Grammar/JavaScript/Methods/Substr.php index 14b2722..2a4fe73 100644 --- a/src/Grammar/JavaScript/Methods/Substr.php +++ b/src/Grammar/JavaScript/Methods/Substr.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; @@ -15,14 +15,8 @@ final class Substr extends CallableFunction { public function call(?BaseToken ...$parameters): BaseToken { - $params = []; $start = $this->parseParameter($parameters, numParam: 0); - - if (!$start) { - $params[] = 0; - } else { - $params[] = (int) $start->getValue(); - } + $params = [(int) $start?->getValue()]; $offset = $this->parseParameter($parameters, numParam: 1); @@ -32,6 +26,6 @@ public function call(?BaseToken ...$parameters): BaseToken $value = substr($this->token->getValue(), ...$params); - return new TokenString((string) $value); + return new TokenString($value); } } diff --git a/src/Grammar/JavaScript/Methods/Test.php b/src/Grammar/JavaScript/Methods/Test.php index 1701e77..52bcf0b 100644 --- a/src/Grammar/JavaScript/Methods/Test.php +++ b/src/Grammar/JavaScript/Methods/Test.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; @@ -19,7 +19,7 @@ final class Test extends CallableFunction public function call(?BaseToken ...$parameters): BaseToken { if (!$this->token instanceof TokenRegex) { - throw new ParserException('undefined is not a function'); + throw new ParserException('test() is not a function'); } $string = $this->parseParameter($parameters, numParam: 0); @@ -27,11 +27,11 @@ public function call(?BaseToken ...$parameters): BaseToken if (!$string) { $bool = false; } else { - // Remove "g" modifier as is does not exist in PHP + // Remove "g" modifier as it does not exist in PHP // It's also irrelevant in .test() but allowed in JS here $pattern = preg_replace_callback( '~/[igm]{0,3}$~', - fn (array $modifiers) => str_replace('g', '', $modifiers[0]), + static fn (array $modifiers): string => str_replace('g', '', $modifiers[0]), $this->token->getValue() ); diff --git a/src/Grammar/JavaScript/Methods/ToLowerCase.php b/src/Grammar/JavaScript/Methods/ToLowerCase.php index 14b259e..8b61f71 100644 --- a/src/Grammar/JavaScript/Methods/ToLowerCase.php +++ b/src/Grammar/JavaScript/Methods/ToLowerCase.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Grammar/JavaScript/Methods/ToUpperCase.php b/src/Grammar/JavaScript/Methods/ToUpperCase.php index dd3eea9..fba4b76 100644 --- a/src/Grammar/JavaScript/Methods/ToUpperCase.php +++ b/src/Grammar/JavaScript/Methods/ToUpperCase.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Grammar\JavaScript\Methods; diff --git a/src/Highlighter/Exception/InvalidGroupException.php b/src/Highlighter/Exception/InvalidGroupException.php deleted file mode 100644 index a06295d..0000000 --- a/src/Highlighter/Exception/InvalidGroupException.php +++ /dev/null @@ -1,12 +0,0 @@ - - */ -namespace nicoSWD\Rule\Highlighter\Exception; - -class InvalidGroupException extends \Exception -{ -} diff --git a/src/Highlighter/Highlighter.php b/src/Highlighter/Highlighter.php index f1bd01a..b90d28f 100644 --- a/src/Highlighter/Highlighter.php +++ b/src/Highlighter/Highlighter.php @@ -3,46 +3,29 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Highlighter; -use ArrayIterator; +use Iterator; use nicoSWD\Rule\Tokenizer\TokenizerInterface; +use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenType; +use SplObjectStorage; final class Highlighter { - /** @var string[] */ - private array $styles = [ - TokenType::COMMENT => 'color: #948a8a; font-style: italic;', - TokenType::LOGICAL => 'color: #c72d2d;', - TokenType::OPERATOR => 'color: #000;', - TokenType::PARENTHESIS => 'color: #000;', - TokenType::SPACE => '', - TokenType::UNKNOWN => '', - TokenType::VALUE => 'color: #e36700; font-style: italic;', - TokenType::VARIABLE => 'color: #007694; font-weight: 900;', - TokenType::METHOD => 'color: #000', - TokenType::SQUARE_BRACKET => '', - TokenType::COMMA => '', - TokenType::FUNCTION => '', - TokenType::INT_VALUE => '', - ]; + private SplObjectStorage $styles; - public function __construct(private TokenizerInterface $tokenizer) - { + public function __construct( + private readonly TokenizerInterface $tokenizer, + ) { + $this->styles = $this->defaultStyles(); } - public function setStyle(int $group, string $style): void + public function setStyle(TokenType $group, string $style): void { - if (!isset($this->styles[$group])) { - throw new Exception\InvalidGroupException( - 'Invalid group' - ); - } - - $this->styles[$group] = (string) $style; + $this->styles[$group] = $style; } public function highlightString(string $string): string @@ -50,14 +33,16 @@ public function highlightString(string $string): string return $this->highlightTokens($this->tokenizer->tokenize($string)); } - public function highlightTokens(ArrayIterator $tokens): string + public function highlightTokens(Iterator $tokens): string { $string = ''; foreach ($tokens as $token) { - if ($style = $this->styles[$token->getType()]) { - $value = htmlentities($token->getOriginalValue(), ENT_QUOTES, 'utf-8'); - $string .= '' . $value . ''; + /** @var BaseToken $token */ + $tokenType = $token->getType(); + + if (isset($this->styles[$tokenType])) { + $string .= '' . $this->encode($token) . ''; } else { $string .= $token->getOriginalValue(); } @@ -65,4 +50,23 @@ public function highlightTokens(ArrayIterator $tokens): string return '
' . $string . '
'; } + + private function encode(BaseToken $token): string + { + return htmlentities($token->getOriginalValue(), ENT_QUOTES, 'utf-8'); + } + + private function defaultStyles(): SplObjectStorage + { + $styles = new SplObjectStorage(); + $styles[TokenType::COMMENT] = 'color: #948a8a; font-style: italic;'; + $styles[TokenType::LOGICAL] = 'color: #c72d2d;'; + $styles[TokenType::OPERATOR] = 'color: #000;'; + $styles[TokenType::PARENTHESIS] = 'color: #000;'; + $styles[TokenType::VALUE] = 'color: #e36700; font-style: italic;'; + $styles[TokenType::VARIABLE] = 'color: #007694; font-weight: 900;'; + $styles[TokenType::METHOD] = 'color: #000'; + + return $styles; + } } diff --git a/src/Parser/EvaluatableExpression.php b/src/Parser/EvaluatableExpression.php new file mode 100644 index 0000000..49e35da --- /dev/null +++ b/src/Parser/EvaluatableExpression.php @@ -0,0 +1,69 @@ + + */ +namespace nicoSWD\Rule\Parser; + +use nicoSWD\Rule\Expression\BaseExpression; +use nicoSWD\Rule\Expression\ExpressionFactoryInterface; +use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class EvaluatableExpression +{ + public (BaseToken & Operator) | null $operator = null; + public array $values = []; + + public function __construct( + private readonly ExpressionFactoryInterface $expressionFactory, + ) { + } + + /** @throws Exception\ParserException */ + public function evaluate(): bool + { + $result = $this->expression()->evaluate(...$this->values); + $this->clear(); + + return $result; + } + + public function isComplete(): bool + { + return $this->hasOperator() && $this->hasBothValues(); + } + + public function addValue(mixed $value): void + { + $this->values[] = $value; + } + + public function hasBothValues(): bool + { + return count($this->values) === 2; + } + + public function hasNoValues(): bool + { + return count($this->values) === 0; + } + + public function hasOperator(): bool + { + return $this->operator !== null; + } + + private function clear(): void + { + $this->operator = null; + $this->values = []; + } + + private function expression(): BaseExpression + { + return $this->expressionFactory->createFromOperator($this->operator); + } +} diff --git a/src/Parser/EvaluatableExpressionFactory.php b/src/Parser/EvaluatableExpressionFactory.php new file mode 100644 index 0000000..9aa8458 --- /dev/null +++ b/src/Parser/EvaluatableExpressionFactory.php @@ -0,0 +1,20 @@ + + */ +namespace nicoSWD\Rule\Parser; + +use nicoSWD\Rule\Expression\ExpressionFactory; + +final class EvaluatableExpressionFactory +{ + public function create(): EvaluatableExpression + { + return new EvaluatableExpression( + new ExpressionFactory() + ); + } +} diff --git a/src/Parser/Exception/ParserException.php b/src/Parser/Exception/ParserException.php index 84a909a..f70e37a 100644 --- a/src/Parser/Exception/ParserException.php +++ b/src/Parser/Exception/ParserException.php @@ -3,13 +3,14 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Parser\Exception; use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\Type\Operator; -class ParserException extends \Exception +final class ParserException extends \Exception { public static function unexpectedToken(BaseToken $token): self { @@ -61,15 +62,10 @@ public static function unsupportedType(string $type): self return new self(sprintf('Unsupported PHP type: "%s"', $type)); } - public static function unknownOperator(BaseToken $token): self + public static function unknownOperator(BaseToken & Operator $token): self { return new self( sprintf('Unexpected operator %s at position %d', $token->getOriginalValue(), $token->getOffset()) ); } - - public static function unknownTokenName(string $tokenName): self - { - return new self("Unknown token $tokenName"); - } } diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index f572781..0fe5d37 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -3,118 +3,94 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Parser; use Closure; use nicoSWD\Rule\Compiler\CompilerFactoryInterface; use nicoSWD\Rule\Compiler\CompilerInterface; -use nicoSWD\Rule\Expression\ExpressionFactoryInterface; -use nicoSWD\Rule\TokenStream\AST; +use nicoSWD\Rule\TokenStream\TokenStream; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenType; +use nicoSWD\Rule\TokenStream\Token\Type\Operator; -class Parser +final readonly class Parser { - private ?BaseToken $operator; - private array $values = []; - public function __construct( - private AST $ast, - private ExpressionFactoryInterface $expressionFactory, - private CompilerFactoryInterface $compilerFactory + private TokenStream $tokenStream, + private EvaluatableExpressionFactory $expressionFactory, + private CompilerFactoryInterface $compilerFactory, ) { } + /** @throws Exception\ParserException */ public function parse(string $rule): string { $compiler = $this->compilerFactory->create(); - $this->resetState(); + $expression = $this->expressionFactory->create(); - foreach ($this->ast->getStream($rule) as $token) { - $handler = $this->getHandlerForType($token->getType()); - $handler($token, $compiler); + foreach ($this->tokenStream->getStream($rule) as $token) { + $handler = $this->getHandlerForToken($token, $expression); + $handler($compiler); - if ($this->expressionCanBeEvaluated()) { - $this->evaluateExpression($compiler); + if ($expression->isComplete()) { + $compiler->addBoolean($expression->evaluate()); } } return $compiler->getCompiledRule(); } - private function getHandlerForType(int $tokenType): Closure + private function getHandlerForToken(BaseToken $token, EvaluatableExpression $expression): Closure { - return match ($tokenType) { - TokenType::VALUE, TokenType::INT_VALUE => $this->handleValueToken(), - TokenType::OPERATOR => $this->handleOperatorToken(), - TokenType::LOGICAL => $this->handleLogicalToken(), - TokenType::PARENTHESIS => $this->handleParenthesisToken(), + return match ($token->getType()) { + TokenType::VALUE => $this->handleValueToken($token, $expression), + TokenType::OPERATOR => $this->handleOperatorToken($token, $expression), + TokenType::LOGICAL => $this->handleLogicalToken($token), + TokenType::PARENTHESIS => $this->handleParenthesisToken($token), TokenType::COMMENT, TokenType::SPACE => $this->handleDummyToken(), - default => $this->handleUnknownToken(), + default => $this->handleUnknownToken($token), }; } - private function evaluateExpression(CompilerInterface $compiler): void - { - $expression = $this->expressionFactory->createFromOperator($this->operator); - - $compiler->addBoolean( - $expression->evaluate(...$this->values) - ); - - $this->resetState(); - } - - private function expressionCanBeEvaluated(): bool + private function handleValueToken(BaseToken $token, EvaluatableExpression $expression): Closure { - return count($this->values) === 2; + return static fn () => $expression->addValue($token->getValue()); } - private function handleValueToken(): Closure + private function handleLogicalToken(BaseToken $token): Closure { - return fn (BaseToken $token) => $this->values[] = $token->getValue(); + return static fn (CompilerInterface $compiler) => $compiler->addLogical($token); } - private function handleLogicalToken(): Closure + private function handleParenthesisToken(BaseToken $token): Closure { - return fn (BaseToken $token, CompilerInterface $compiler) => $compiler->addLogical($token); + return static fn (CompilerInterface $compiler) => $compiler->addParentheses($token); } - private function handleParenthesisToken(): Closure + private function handleUnknownToken(BaseToken $token): Closure { - return fn (BaseToken $token, CompilerInterface $compiler) => $compiler->addParentheses($token); + return static fn () => throw Exception\ParserException::unknownToken($token); } - private function handleUnknownToken(): Closure + private function handleOperatorToken(BaseToken & Operator $token, EvaluatableExpression $expression): Closure { - return fn (BaseToken $token) => throw Exception\ParserException::unknownToken($token); - } - - private function handleOperatorToken(): Closure - { - return function (BaseToken $token): void { - if (isset($this->operator)) { + return static function () use ($token, $expression): void { + if ($expression->hasOperator()) { throw Exception\ParserException::unexpectedToken($token); - } elseif (empty($this->values)) { + } elseif ($expression->hasNoValues()) { throw Exception\ParserException::incompleteExpression($token); } - $this->operator = $token; + $expression->operator = $token; }; } private function handleDummyToken(): Closure { - return function (): void { + return static function (): void { // Do nothing }; } - - private function resetState(): void - { - $this->operator = null; - $this->values = []; - } } diff --git a/src/Rule.php b/src/Rule.php index b860482..3d3b8e0 100644 --- a/src/Rule.php +++ b/src/Rule.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule; @@ -12,8 +12,8 @@ class Rule { + private readonly Parser\Parser $parser; private string $rule; - private Parser\Parser $parser; private string $parsedRule = ''; private string $error = ''; private static object $container; @@ -28,6 +28,7 @@ public function __construct(string $rule, array $variables = []) $this->rule = $rule; } + /** @throws Parser\Exception\ParserException */ public function isTrue(): bool { /** @var EvaluatorInterface $evaluator */ @@ -39,6 +40,7 @@ public function isTrue(): bool ); } + /** @throws Parser\Exception\ParserException */ public function isFalse(): bool { return !$this->isTrue(); diff --git a/src/TokenStream/AST.php b/src/TokenStream/AST.php deleted file mode 100644 index 41b72d0..0000000 --- a/src/TokenStream/AST.php +++ /dev/null @@ -1,132 +0,0 @@ - - */ -namespace nicoSWD\Rule\TokenStream; - -use Closure; -use InvalidArgumentException; -use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; -use nicoSWD\Rule\Parser\Exception\ParserException; -use nicoSWD\Rule\TokenStream\Exception\UndefinedVariableException; -use nicoSWD\Rule\TokenStream\Token\BaseToken; -use nicoSWD\Rule\TokenStream\Token\TokenFactory; -use nicoSWD\Rule\Tokenizer\TokenizerInterface; -use nicoSWD\Rule\TokenStream\Token\TokenObject; - -class AST -{ - private array $functions = []; - private array $methods = []; - private array $variables = []; - - public function __construct( - private TokenizerInterface $tokenizer, - private TokenFactory $tokenFactory, - private TokenStreamFactory $tokenStreamFactory, - private CallableUserMethodFactoryInterface $userMethodFactory - ) { - } - - public function getStream(string $rule): TokenStream - { - return $this->tokenStreamFactory->create($this->tokenizer->tokenize($rule), $this); - } - - /** - * @throws Exception\UndefinedMethodException - * @throws Exception\ForbiddenMethodException - */ - public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface - { - if ($token instanceof TokenObject) { - return $this->getCallableUserMethod($token, $methodName); - } - - if (empty($this->methods)) { - $this->registerMethods(); - } - - if (!isset($this->methods[$methodName])) { - throw new Exception\UndefinedMethodException(); - } - - return new $this->methods[$methodName]($token); - } - - public function setVariables(array $variables): void - { - $this->variables = $variables; - } - - /** - * @throws UndefinedVariableException - * @throws ParserException - */ - public function getVariable(string $variableName): BaseToken - { - if (!$this->variableExists($variableName)) { - throw new UndefinedVariableException($variableName); - } - - return $this->tokenFactory->createFromPHPType($this->variables[$variableName]); - } - - public function variableExists(string $variableName): bool - { - return array_key_exists($variableName, $this->variables); - } - - /** @throws Exception\UndefinedFunctionException */ - public function getFunction(string $functionName): Closure - { - if (empty($this->functions)) { - $this->registerFunctions(); - } - - if (!isset($this->functions[$functionName])) { - throw new Exception\UndefinedFunctionException($functionName); - } - - return $this->functions[$functionName]; - } - - private function registerMethods(): void - { - $this->methods = $this->tokenizer->getGrammar()->getInternalMethods(); - } - - private function registerFunctionClass(string $functionName, string $className): void - { - $this->functions[$functionName] = function (...$args) use ($className) { - $function = new $className(); - - if (!$function instanceof CallableUserFunctionInterface) { - throw new InvalidArgumentException( - sprintf( - "%s must be an instance of %s", - $className, - CallableUserFunctionInterface::class - ) - ); - } - - return $function->call(...$args); - }; - } - - private function registerFunctions(): void - { - foreach ($this->tokenizer->getGrammar()->getInternalFunctions() as $functionName => $className) { - $this->registerFunctionClass($functionName, $className); - } - } - - private function getCallableUserMethod(BaseToken $token, string $methodName): CallableUserFunctionInterface - { - return $this->userMethodFactory->create($token, $this->tokenFactory, $methodName); - } -} diff --git a/src/TokenStream/CallableUserMethod.php b/src/TokenStream/CallableUserMethod.php index 27c14a1..a59d5ff 100644 --- a/src/TokenStream/CallableUserMethod.php +++ b/src/TokenStream/CallableUserMethod.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream; @@ -16,8 +16,9 @@ final class CallableUserMethod implements CallableUserFunctionInterface { private const MAGIC_METHOD_PREFIX = '__'; - private TokenFactory $tokenFactory; - private Closure $callable; + private readonly TokenFactory $tokenFactory; + private readonly Closure $callable; + private array $methodPrefixes = ['', 'get', 'is', 'get_', 'is_']; /** @@ -32,10 +33,10 @@ public function __construct(BaseToken $token, TokenFactory $tokenFactory, string public function call(?BaseToken ...$param): BaseToken { - $callableCopy = $this->callable; + $callable = $this->callable; return $this->tokenFactory->createFromPHPType( - $callableCopy(...$param) + $callable(...$param) ); } @@ -48,12 +49,12 @@ private function getCallable(BaseToken $token, string $methodName): Closure $object = $token->getValue(); if (property_exists($object, $methodName)) { - return fn () => $object->{$methodName}; + return static fn (): mixed => $object->{$methodName}; } $method = $this->findCallableMethod($object, $methodName); - return fn (?BaseToken ...$params) => $method( + return fn (?BaseToken ...$params): mixed => $method( ...$this->getTokenValues($params) ); } @@ -80,13 +81,7 @@ private function findCallableMethod(object $object, string $methodName): callabl private function getTokenValues(array $params): array { - $values = []; - - foreach ($params as $token) { - $values[] = $token->getValue(); - } - - return $values; + return array_map(static fn (BaseToken $token): mixed => $token->getValue(), $params); } /** @throws Exception\ForbiddenMethodException */ diff --git a/src/TokenStream/CallableUserMethodFactory.php b/src/TokenStream/CallableUserMethodFactory.php index cae71bb..3a18863 100644 --- a/src/TokenStream/CallableUserMethodFactory.php +++ b/src/TokenStream/CallableUserMethodFactory.php @@ -3,16 +3,20 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream; +use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenFactory; final class CallableUserMethodFactory implements CallableUserMethodFactoryInterface { - public function create(BaseToken $token, TokenFactory $tokenFactory, string $methodName): CallableUserMethod + /** + * {@inheritDoc} + */ + public function create(BaseToken $token, TokenFactory $tokenFactory, string $methodName): CallableUserFunctionInterface { return new CallableUserMethod($token, $tokenFactory, $methodName); } diff --git a/src/TokenStream/CallableUserMethodFactoryInterface.php b/src/TokenStream/CallableUserMethodFactoryInterface.php index 2601617..efb85de 100644 --- a/src/TokenStream/CallableUserMethodFactoryInterface.php +++ b/src/TokenStream/CallableUserMethodFactoryInterface.php @@ -3,14 +3,19 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream; +use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenFactory; interface CallableUserMethodFactoryInterface { - public function create(BaseToken $token, TokenFactory $tokenFactory, string $methodName): CallableUserMethod; + /** + * @throws Exception\ForbiddenMethodException + * @throws Exception\UndefinedMethodException + */ + public function create(BaseToken $token, TokenFactory $tokenFactory, string $methodName): CallableUserFunctionInterface; } diff --git a/src/TokenStream/Exception/ForbiddenMethodException.php b/src/TokenStream/Exception/ForbiddenMethodException.php index 8742ed5..315930e 100644 --- a/src/TokenStream/Exception/ForbiddenMethodException.php +++ b/src/TokenStream/Exception/ForbiddenMethodException.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Exception; diff --git a/src/TokenStream/Exception/UndefinedFunctionException.php b/src/TokenStream/Exception/UndefinedFunctionException.php index 5f9c317..a4115d7 100644 --- a/src/TokenStream/Exception/UndefinedFunctionException.php +++ b/src/TokenStream/Exception/UndefinedFunctionException.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Exception; diff --git a/src/TokenStream/Exception/UndefinedMethodException.php b/src/TokenStream/Exception/UndefinedMethodException.php index 5a99291..4f27202 100644 --- a/src/TokenStream/Exception/UndefinedMethodException.php +++ b/src/TokenStream/Exception/UndefinedMethodException.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Exception; diff --git a/src/TokenStream/Exception/UndefinedVariableException.php b/src/TokenStream/Exception/UndefinedVariableException.php index 41c07dc..adefc6c 100644 --- a/src/TokenStream/Exception/UndefinedVariableException.php +++ b/src/TokenStream/Exception/UndefinedVariableException.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Exception; diff --git a/src/TokenStream/Node/BaseNode.php b/src/TokenStream/Node/BaseNode.php index c9748e8..4724f3e 100644 --- a/src/TokenStream/Node/BaseNode.php +++ b/src/TokenStream/Node/BaseNode.php @@ -3,27 +3,28 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Node; use Closure; use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\Type\Method; +use nicoSWD\Rule\TokenStream\Token\Type\Whitespace; use nicoSWD\Rule\TokenStream\TokenCollection; use nicoSWD\Rule\Parser\Exception\ParserException; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\TokenIterator; use nicoSWD\Rule\TokenStream\Token\TokenType; abstract class BaseNode { - protected TokenStream $tokenStream; - private string $methodName = ''; + private string $methodName; private int $methodOffset = 0; - public function __construct(TokenStream $tokenStream) - { - $this->tokenStream = $tokenStream; + public function __construct( + protected readonly TokenIterator $tokenStream, + ) { } /** @throws ParserException */ @@ -43,12 +44,12 @@ protected function hasMethodCall(): bool if (!$token) { break; - } elseif ($token->isMethod()) { + } elseif ($token instanceof Method) { $this->methodName = $token->getValue(); $this->methodOffset = $stack->key(); $hasMethod = true; break; - } elseif (!$token->isWhitespace()) { + } elseif (!$token instanceof Whitespace) { break; } } @@ -100,7 +101,7 @@ protected function getCurrentNode(): BaseToken } /** @throws ParserException */ - private function getCommaSeparatedValues(int $stopAt): TokenCollection + private function getCommaSeparatedValues(TokenType $stopAt): TokenCollection { $items = new TokenCollection(); $commaExpected = false; @@ -108,14 +109,14 @@ private function getCommaSeparatedValues(int $stopAt): TokenCollection do { $token = $this->getNextToken(); - if ($token->isValue()) { + if (TokenType::isValue($token)) { if ($commaExpected) { throw ParserException::unexpectedToken($token); } $commaExpected = true; $items->attach($token); - } elseif ($token->isComma()) { + } elseif ($token->isOfType(TokenType::COMMA)) { if (!$commaExpected) { throw ParserException::unexpectedComma($token); } @@ -123,7 +124,7 @@ private function getCommaSeparatedValues(int $stopAt): TokenCollection $commaExpected = false; } elseif ($token->isOfType($stopAt)) { break; - } elseif (!$token->isWhitespace()) { + } elseif (!$token->canBeIgnored()) { throw ParserException::unexpectedToken($token); } } while (true); diff --git a/src/TokenStream/Node/NodeArray.php b/src/TokenStream/Node/NodeArray.php index 02d598e..bb419a9 100644 --- a/src/TokenStream/Node/NodeArray.php +++ b/src/TokenStream/Node/NodeArray.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Node; diff --git a/src/TokenStream/Node/NodeFunction.php b/src/TokenStream/Node/NodeFunction.php index 1d2bbe8..8eba994 100644 --- a/src/TokenStream/Node/NodeFunction.php +++ b/src/TokenStream/Node/NodeFunction.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Node; diff --git a/src/TokenStream/Node/NodeString.php b/src/TokenStream/Node/NodeString.php index 62b6758..9cf630c 100644 --- a/src/TokenStream/Node/NodeString.php +++ b/src/TokenStream/Node/NodeString.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Node; diff --git a/src/TokenStream/Node/NodeVariable.php b/src/TokenStream/Node/NodeVariable.php index 5e23341..7b9600a 100644 --- a/src/TokenStream/Node/NodeVariable.php +++ b/src/TokenStream/Node/NodeVariable.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Node; diff --git a/src/TokenStream/Token/BaseToken.php b/src/TokenStream/Token/BaseToken.php index 4456fff..f7c2491 100644 --- a/src/TokenStream/Token/BaseToken.php +++ b/src/TokenStream/Token/BaseToken.php @@ -3,20 +3,20 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\Parser\Exception\ParserException; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\TokenIterator; abstract class BaseToken { - abstract public function getType(): int; + abstract public function getType(): TokenType; public function __construct( - private mixed $value, - private int $offset = 0 + private readonly mixed $value, + private readonly int $offset = 0, ) { } @@ -36,48 +36,20 @@ public function getOffset(): int } /** @throws ParserException */ - public function createNode(TokenStream $tokenStream): self + public function createNode(TokenIterator $tokenStream): self { return $this; } - public function isOfType(int $type): bool + public function isOfType(TokenType $type): bool { - return ($this->getType() | $type) === $type; + return $this->getType() === $type; } - public function isValue(): bool + public function canBeIgnored(): bool { - return $this->isOfType(TokenType::VALUE | TokenType::INT_VALUE); - } - - public function isWhitespace(): bool - { - return $this->isOfType(TokenType::SPACE | TokenType::COMMENT); - } - - public function isMethod(): bool - { - return $this->isOfType(TokenType::METHOD); - } - - public function isComma(): bool - { - return $this->isOfType(TokenType::COMMA); - } - - public function isOperator(): bool - { - return $this->isOfType(TokenType::OPERATOR); - } - - public function isLogical(): bool - { - return $this->isOfType(TokenType::LOGICAL); - } - - public function isParenthesis(): bool - { - return $this->isOfType(TokenType::PARENTHESIS); + return + $this->isOfType(TokenType::SPACE) || + $this->isOfType(TokenType::COMMENT); } } diff --git a/src/TokenStream/Token/Token.php b/src/TokenStream/Token/Token.php index 8112716..95dbb2b 100644 --- a/src/TokenStream/Token/Token.php +++ b/src/TokenStream/Token/Token.php @@ -3,41 +3,41 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -class Token +enum Token: string { - public const AND = 'And'; - public const OR = 'Or'; - public const NOT_EQUAL_STRICT = 'NotEqualStrict'; - public const NOT_EQUAL = 'NotEqual'; - public const EQUAL_STRICT = 'EqualStrict'; - public const EQUAL = 'Equal'; - public const IN = 'In'; - public const NOT_IN = 'NotIn'; - public const BOOL_TRUE = 'True'; - public const BOOL_FALSE = 'False'; - public const NULL = 'Null'; - public const METHOD = 'Method'; - public const FUNCTION = 'Function'; - public const VARIABLE = 'Variable'; - public const FLOAT = 'Float'; - public const INTEGER = 'Integer'; - public const ENCAPSED_STRING = 'EncapsedString'; - public const SMALLER_EQUAL = 'SmallerEqual'; - public const GREATER_EQUAL = 'GreaterEqual'; - public const SMALLER = 'Smaller'; - public const GREATER = 'Greater'; - public const OPENING_PARENTHESIS = 'OpeningParentheses'; - public const CLOSING_PARENTHESIS = 'ClosingParentheses'; - public const OPENING_ARRAY = 'OpeningArray'; - public const CLOSING_ARRAY = 'ClosingArray'; - public const COMMA = 'Comma'; - public const REGEX = 'Regex'; - public const COMMENT = 'Comment'; - public const NEWLINE = 'Newline'; - public const SPACE = 'Space'; - public const UNKNOWN = 'Unknown'; + case AND = 'And'; + case OR = 'Or'; + case NOT_EQUAL_STRICT = 'NotEqualStrict'; + case NOT_EQUAL = 'NotEqual'; + case EQUAL_STRICT = 'EqualStrict'; + case EQUAL = 'Equal'; + case IN = 'In'; + case NOT_IN = 'NotIn'; + case BOOL_TRUE = 'True'; + case BOOL_FALSE = 'False'; + case NULL = 'Null'; + case METHOD = 'Method'; + case FUNCTION = 'Function'; + case VARIABLE = 'Variable'; + case FLOAT = 'Float'; + case INTEGER = 'Integer'; + case ENCAPSED_STRING = 'EncapsedString'; + case SMALLER_EQUAL = 'SmallerEqual'; + case GREATER_EQUAL = 'GreaterEqual'; + case SMALLER = 'Smaller'; + case GREATER = 'Greater'; + case OPENING_PARENTHESIS = 'OpeningParentheses'; + case CLOSING_PARENTHESIS = 'ClosingParentheses'; + case OPENING_ARRAY = 'OpeningArray'; + case CLOSING_ARRAY = 'ClosingArray'; + case COMMA = 'Comma'; + case REGEX = 'Regex'; + case COMMENT = 'Comment'; + case NEWLINE = 'Newline'; + case SPACE = 'Space'; + case UNKNOWN = 'Unknown'; } diff --git a/src/TokenStream/Token/TokenAnd.php b/src/TokenStream/Token/TokenAnd.php index fc6f7dd..8930c80 100644 --- a/src/TokenStream/Token/TokenAnd.php +++ b/src/TokenStream/Token/TokenAnd.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenAnd extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Logical; + +final class TokenAnd extends BaseToken implements Logical { - public function getType(): int + public function getType(): TokenType { return TokenType::LOGICAL; } diff --git a/src/TokenStream/Token/TokenArray.php b/src/TokenStream/Token/TokenArray.php index 1201b06..e85fe5f 100644 --- a/src/TokenStream/Token/TokenArray.php +++ b/src/TokenStream/Token/TokenArray.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenArray extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Value; + +final class TokenArray extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { return TokenType::VALUE; } diff --git a/src/TokenStream/Token/TokenBool.php b/src/TokenStream/Token/TokenBool.php index 1e35584..cb84ed6 100644 --- a/src/TokenStream/Token/TokenBool.php +++ b/src/TokenStream/Token/TokenBool.php @@ -3,11 +3,13 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -abstract class TokenBool extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Value; + +abstract class TokenBool extends BaseToken implements Value { public static function fromBool(bool $bool): TokenBool { @@ -17,7 +19,7 @@ public static function fromBool(bool $bool): TokenBool }; } - public function getType(): int + public function getType(): TokenType { return TokenType::VALUE; } diff --git a/src/TokenStream/Token/TokenBoolFalse.php b/src/TokenStream/Token/TokenBoolFalse.php index 7451660..3c3ccc4 100644 --- a/src/TokenStream/Token/TokenBoolFalse.php +++ b/src/TokenStream/Token/TokenBoolFalse.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; diff --git a/src/TokenStream/Token/TokenBoolTrue.php b/src/TokenStream/Token/TokenBoolTrue.php index d6f9965..e6cbf38 100644 --- a/src/TokenStream/Token/TokenBoolTrue.php +++ b/src/TokenStream/Token/TokenBoolTrue.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; diff --git a/src/TokenStream/Token/TokenClosingArray.php b/src/TokenStream/Token/TokenClosingArray.php index 3bae87f..ff0d105 100644 --- a/src/TokenStream/Token/TokenClosingArray.php +++ b/src/TokenStream/Token/TokenClosingArray.php @@ -3,13 +3,13 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; final class TokenClosingArray extends BaseToken { - public function getType(): int + public function getType(): TokenType { return TokenType::SQUARE_BRACKET; } diff --git a/src/TokenStream/Token/TokenClosingParenthesis.php b/src/TokenStream/Token/TokenClosingParenthesis.php index c77e898..9ee93f4 100644 --- a/src/TokenStream/Token/TokenClosingParenthesis.php +++ b/src/TokenStream/Token/TokenClosingParenthesis.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenClosingParenthesis extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Parenthesis; + +final class TokenClosingParenthesis extends BaseToken implements Parenthesis { - public function getType(): int + public function getType(): TokenType { return TokenType::PARENTHESIS; } diff --git a/src/TokenStream/Token/TokenComma.php b/src/TokenStream/Token/TokenComma.php index ff39217..fc1ce81 100644 --- a/src/TokenStream/Token/TokenComma.php +++ b/src/TokenStream/Token/TokenComma.php @@ -3,13 +3,13 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; final class TokenComma extends BaseToken { - public function getType(): int + public function getType(): TokenType { return TokenType::COMMA; } diff --git a/src/TokenStream/Token/TokenComment.php b/src/TokenStream/Token/TokenComment.php index e48c7ff..1866e56 100644 --- a/src/TokenStream/Token/TokenComment.php +++ b/src/TokenStream/Token/TokenComment.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenComment extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Whitespace; + +final class TokenComment extends BaseToken implements Whitespace { - public function getType(): int + public function getType(): TokenType { return TokenType::COMMENT; } diff --git a/src/TokenStream/Token/TokenEncapsedString.php b/src/TokenStream/Token/TokenEncapsedString.php index 738858c..962072d 100644 --- a/src/TokenStream/Token/TokenEncapsedString.php +++ b/src/TokenStream/Token/TokenEncapsedString.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; diff --git a/src/TokenStream/Token/TokenEqual.php b/src/TokenStream/Token/TokenEqual.php index 3a2fb64..712da78 100644 --- a/src/TokenStream/Token/TokenEqual.php +++ b/src/TokenStream/Token/TokenEqual.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenEqual extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenEqual extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenEqualStrict.php b/src/TokenStream/Token/TokenEqualStrict.php index b60e244..5f6c687 100644 --- a/src/TokenStream/Token/TokenEqualStrict.php +++ b/src/TokenStream/Token/TokenEqualStrict.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenEqualStrict extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenEqualStrict extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenFactory.php b/src/TokenStream/Token/TokenFactory.php index 4b7a1be..80e16b9 100644 --- a/src/TokenStream/Token/TokenFactory.php +++ b/src/TokenStream/Token/TokenFactory.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; @@ -23,46 +23,46 @@ public function createFromPHPType(mixed $value): BaseToken 'double' => new TokenFloat($value), 'object' => new TokenObject($value), 'array' => $this->buildTokenCollection($value), - default => throw ParserException::unsupportedType(gettype($value)) + default => throw ParserException::unsupportedType(gettype($value)), }; } - /** @throws ParserException */ - public function createFromTokenName(string $tokenName): string + public function createFromToken(Token $token, array $matches, int $offset): BaseToken { - return match ($tokenName) { - Token::AND => TokenAnd::class, - Token::OR => TokenOr::class, - Token::NOT_EQUAL_STRICT => TokenNotEqualStrict::class, - Token::NOT_EQUAL => TokenNotEqual::class, - Token::EQUAL_STRICT => TokenEqualStrict::class, - Token::EQUAL => TokenEqual::class, - Token::IN => TokenIn::class, - Token::NOT_IN => TokenNotIn::class, - Token::BOOL_TRUE => TokenBoolTrue::class, - Token::BOOL_FALSE => TokenBoolFalse::class, - Token::NULL => TokenNull::class, - Token::METHOD => TokenMethod::class, - Token::FUNCTION => TokenFunction::class, - Token::VARIABLE => TokenVariable::class, - Token::FLOAT => TokenFloat::class, - Token::INTEGER => TokenInteger::class, - Token::ENCAPSED_STRING => TokenEncapsedString::class, - Token::SMALLER_EQUAL => TokenSmallerEqual::class, - Token::GREATER_EQUAL => TokenGreaterEqual::class, - Token::SMALLER => TokenSmaller::class, - Token::GREATER => TokenGreater::class, - Token::OPENING_PARENTHESIS => TokenOpeningParenthesis::class, - Token::CLOSING_PARENTHESIS => TokenClosingParenthesis::class, - Token::OPENING_ARRAY => TokenOpeningArray::class, - Token::CLOSING_ARRAY => TokenClosingArray::class, - Token::COMMA => TokenComma::class, - Token::REGEX => TokenRegex::class, - Token::COMMENT => TokenComment::class, - Token::NEWLINE => TokenNewline::class, - Token::SPACE => TokenSpace::class, - Token::UNKNOWN => TokenUnknown::class, - default => throw ParserException::unknownTokenName($tokenName) + $args = [$matches[$token->value], $offset]; + + return match ($token) { + Token::AND => new TokenAnd(...$args), + Token::OR => new TokenOr(...$args), + Token::NOT_EQUAL_STRICT => new TokenNotEqualStrict(...$args), + Token::NOT_EQUAL => new TokenNotEqual(...$args), + Token::EQUAL_STRICT => new TokenEqualStrict(...$args), + Token::EQUAL => new TokenEqual(...$args), + Token::IN => new TokenIn(...$args), + Token::NOT_IN => new TokenNotIn(...$args), + Token::BOOL_TRUE => new TokenBoolTrue(...$args), + Token::BOOL_FALSE => new TokenBoolFalse(...$args), + Token::NULL => new TokenNull(...$args), + Token::METHOD => new TokenMethod(...$args), + Token::FUNCTION => new TokenFunction(...$args), + Token::VARIABLE => new TokenVariable(...$args), + Token::FLOAT => new TokenFloat(...$args), + Token::INTEGER => new TokenInteger(...$args), + Token::ENCAPSED_STRING => new TokenEncapsedString(...$args), + Token::SMALLER_EQUAL => new TokenSmallerEqual(...$args), + Token::GREATER_EQUAL => new TokenGreaterEqual(...$args), + Token::SMALLER => new TokenSmaller(...$args), + Token::GREATER => new TokenGreater(...$args), + Token::OPENING_PARENTHESIS => new TokenOpeningParenthesis(...$args), + Token::CLOSING_PARENTHESIS => new TokenClosingParenthesis(...$args), + Token::OPENING_ARRAY => new TokenOpeningArray(...$args), + Token::CLOSING_ARRAY => new TokenClosingArray(...$args), + Token::COMMA => new TokenComma(...$args), + Token::REGEX => new TokenRegex(...$args), + Token::COMMENT => new TokenComment(...$args), + Token::NEWLINE => new TokenNewline(...$args), + Token::SPACE => new TokenSpace(...$args), + Token::UNKNOWN => new TokenUnknown(...$args), }; } diff --git a/src/TokenStream/Token/TokenFloat.php b/src/TokenStream/Token/TokenFloat.php index 018c92b..eba6bd0 100644 --- a/src/TokenStream/Token/TokenFloat.php +++ b/src/TokenStream/Token/TokenFloat.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenFloat extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Value; + +final class TokenFloat extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { return TokenType::VALUE; } diff --git a/src/TokenStream/Token/TokenFunction.php b/src/TokenStream/Token/TokenFunction.php index c93fed1..7d27e80 100644 --- a/src/TokenStream/Token/TokenFunction.php +++ b/src/TokenStream/Token/TokenFunction.php @@ -3,21 +3,21 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\TokenStream\Node\NodeFunction; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\TokenIterator; final class TokenFunction extends BaseToken { - public function getType(): int + public function getType(): TokenType { return TokenType::FUNCTION; } - public function createNode(TokenStream $tokenStream): BaseToken + public function createNode(TokenIterator $tokenStream): BaseToken { return (new NodeFunction($tokenStream))->getNode(); } diff --git a/src/TokenStream/Token/TokenGreater.php b/src/TokenStream/Token/TokenGreater.php index a306b88..07920bb 100644 --- a/src/TokenStream/Token/TokenGreater.php +++ b/src/TokenStream/Token/TokenGreater.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenGreater extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenGreater extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenGreaterEqual.php b/src/TokenStream/Token/TokenGreaterEqual.php index faed030..4e01558 100644 --- a/src/TokenStream/Token/TokenGreaterEqual.php +++ b/src/TokenStream/Token/TokenGreaterEqual.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenGreaterEqual extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenGreaterEqual extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenIn.php b/src/TokenStream/Token/TokenIn.php index 88334b9..43a2f34 100644 --- a/src/TokenStream/Token/TokenIn.php +++ b/src/TokenStream/Token/TokenIn.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenIn extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenIn extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenInteger.php b/src/TokenStream/Token/TokenInteger.php index 45c950e..3f1f281 100644 --- a/src/TokenStream/Token/TokenInteger.php +++ b/src/TokenStream/Token/TokenInteger.php @@ -3,15 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenInteger extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Value; + +final class TokenInteger extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { - return TokenType::INT_VALUE; + return TokenType::VALUE; } public function getValue(): int diff --git a/src/TokenStream/Token/TokenMethod.php b/src/TokenStream/Token/TokenMethod.php index 18c0684..945fe27 100644 --- a/src/TokenStream/Token/TokenMethod.php +++ b/src/TokenStream/Token/TokenMethod.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenMethod extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Method; + +final class TokenMethod extends BaseToken implements Method { - public function getType(): int + public function getType(): TokenType { return TokenType::METHOD; } diff --git a/src/TokenStream/Token/TokenNewline.php b/src/TokenStream/Token/TokenNewline.php index 955ba39..71db1ed 100644 --- a/src/TokenStream/Token/TokenNewline.php +++ b/src/TokenStream/Token/TokenNewline.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenNewline extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Whitespace; + +final class TokenNewline extends BaseToken implements Whitespace { - public function getType(): int + public function getType(): TokenType { return TokenType::SPACE; } diff --git a/src/TokenStream/Token/TokenNotEqual.php b/src/TokenStream/Token/TokenNotEqual.php index 163a1b9..2ec6f8e 100644 --- a/src/TokenStream/Token/TokenNotEqual.php +++ b/src/TokenStream/Token/TokenNotEqual.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenNotEqual extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenNotEqual extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenNotEqualStrict.php b/src/TokenStream/Token/TokenNotEqualStrict.php index e913d18..1949e7c 100644 --- a/src/TokenStream/Token/TokenNotEqualStrict.php +++ b/src/TokenStream/Token/TokenNotEqualStrict.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenNotEqualStrict extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenNotEqualStrict extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenNotIn.php b/src/TokenStream/Token/TokenNotIn.php index 79a213a..813abef 100644 --- a/src/TokenStream/Token/TokenNotIn.php +++ b/src/TokenStream/Token/TokenNotIn.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenNotIn extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenNotIn extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenNull.php b/src/TokenStream/Token/TokenNull.php index 452719c..b8f4dba 100644 --- a/src/TokenStream/Token/TokenNull.php +++ b/src/TokenStream/Token/TokenNull.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenNull extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Value; + +final class TokenNull extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { return TokenType::VALUE; } diff --git a/src/TokenStream/Token/TokenObject.php b/src/TokenStream/Token/TokenObject.php index c6c3e0a..619c72c 100644 --- a/src/TokenStream/Token/TokenObject.php +++ b/src/TokenStream/Token/TokenObject.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenObject extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Value; + +final class TokenObject extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { return TokenType::VALUE; } diff --git a/src/TokenStream/Token/TokenOpeningArray.php b/src/TokenStream/Token/TokenOpeningArray.php index b2d5275..991b2a7 100644 --- a/src/TokenStream/Token/TokenOpeningArray.php +++ b/src/TokenStream/Token/TokenOpeningArray.php @@ -3,21 +3,21 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\TokenStream\Node\NodeArray; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\TokenIterator; final class TokenOpeningArray extends BaseToken { - public function getType(): int + public function getType(): TokenType { return TokenType::SQUARE_BRACKET; } - public function createNode(TokenStream $tokenStream): BaseToken + public function createNode(TokenIterator $tokenStream): BaseToken { return (new NodeArray($tokenStream))->getNode(); } diff --git a/src/TokenStream/Token/TokenOpeningParenthesis.php b/src/TokenStream/Token/TokenOpeningParenthesis.php index a3d4058..f180b46 100644 --- a/src/TokenStream/Token/TokenOpeningParenthesis.php +++ b/src/TokenStream/Token/TokenOpeningParenthesis.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenOpeningParenthesis extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Parenthesis; + +final class TokenOpeningParenthesis extends BaseToken implements Parenthesis { - public function getType(): int + public function getType(): TokenType { return TokenType::PARENTHESIS; } diff --git a/src/TokenStream/Token/TokenOr.php b/src/TokenStream/Token/TokenOr.php index b4dcfdc..8d2c446 100644 --- a/src/TokenStream/Token/TokenOr.php +++ b/src/TokenStream/Token/TokenOr.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenOr extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Logical; + +final class TokenOr extends BaseToken implements Logical { - public function getType(): int + public function getType(): TokenType { return TokenType::LOGICAL; } diff --git a/src/TokenStream/Token/TokenRegex.php b/src/TokenStream/Token/TokenRegex.php index ad93008..c8e2f7c 100644 --- a/src/TokenStream/Token/TokenRegex.php +++ b/src/TokenStream/Token/TokenRegex.php @@ -3,21 +3,22 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\TokenStream\Node\NodeString; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\Token\Type\Value; +use nicoSWD\Rule\TokenStream\TokenIterator; -final class TokenRegex extends BaseToken +final class TokenRegex extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { return TokenType::VALUE; } - public function createNode(TokenStream $tokenStream): BaseToken + public function createNode(TokenIterator $tokenStream): BaseToken { return (new NodeString($tokenStream))->getNode(); } diff --git a/src/TokenStream/Token/TokenSmaller.php b/src/TokenStream/Token/TokenSmaller.php index 91b7617..9be3dee 100644 --- a/src/TokenStream/Token/TokenSmaller.php +++ b/src/TokenStream/Token/TokenSmaller.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenSmaller extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenSmaller extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenSmallerEqual.php b/src/TokenStream/Token/TokenSmallerEqual.php index 045aac7..cf79a31 100644 --- a/src/TokenStream/Token/TokenSmallerEqual.php +++ b/src/TokenStream/Token/TokenSmallerEqual.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenSmallerEqual extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Operator; + +final class TokenSmallerEqual extends BaseToken implements Operator { - public function getType(): int + public function getType(): TokenType { return TokenType::OPERATOR; } diff --git a/src/TokenStream/Token/TokenSpace.php b/src/TokenStream/Token/TokenSpace.php index 170b17b..6e2279c 100644 --- a/src/TokenStream/Token/TokenSpace.php +++ b/src/TokenStream/Token/TokenSpace.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -final class TokenSpace extends BaseToken +use nicoSWD\Rule\TokenStream\Token\Type\Whitespace; + +final class TokenSpace extends BaseToken implements Whitespace { - public function getType(): int + public function getType(): TokenType { return TokenType::SPACE; } diff --git a/src/TokenStream/Token/TokenString.php b/src/TokenStream/Token/TokenString.php index c320852..cc0459f 100644 --- a/src/TokenStream/Token/TokenString.php +++ b/src/TokenStream/Token/TokenString.php @@ -3,21 +3,22 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\TokenStream\Node\NodeString; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\Token\Type\Value; +use nicoSWD\Rule\TokenStream\TokenIterator; -class TokenString extends BaseToken +class TokenString extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { return TokenType::VALUE; } - public function createNode(TokenStream $tokenStream): BaseToken + public function createNode(TokenIterator $tokenStream): BaseToken { return (new NodeString($tokenStream))->getNode(); } diff --git a/src/TokenStream/Token/TokenType.php b/src/TokenStream/Token/TokenType.php index 76c79e0..3f58cf5 100644 --- a/src/TokenStream/Token/TokenType.php +++ b/src/TokenStream/Token/TokenType.php @@ -3,23 +3,27 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; -class TokenType +enum TokenType { - public const OPERATOR = 1; - public const INT_VALUE = 2; - public const VALUE = 4; - public const LOGICAL = 8; - public const VARIABLE = 16; - public const COMMENT = 32; - public const SPACE = 64; - public const UNKNOWN = 128; - public const PARENTHESIS = 256; - public const SQUARE_BRACKET = 512; - public const COMMA = 1024; - public const METHOD = 2048; - public const FUNCTION = 4098; + case OPERATOR; + case VALUE; + case LOGICAL; + case VARIABLE; + case COMMENT; + case SPACE; + case UNKNOWN; + case PARENTHESIS; + case SQUARE_BRACKET; + case COMMA; + case METHOD; + case FUNCTION; + + public static function isValue(BaseToken $token): bool + { + return $token->getType() === self::VALUE; + } } diff --git a/src/TokenStream/Token/TokenUnknown.php b/src/TokenStream/Token/TokenUnknown.php index f2747b5..367ad21 100644 --- a/src/TokenStream/Token/TokenUnknown.php +++ b/src/TokenStream/Token/TokenUnknown.php @@ -3,13 +3,13 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; final class TokenUnknown extends BaseToken { - public function getType(): int + public function getType(): TokenType { return TokenType::UNKNOWN; } diff --git a/src/TokenStream/Token/TokenVariable.php b/src/TokenStream/Token/TokenVariable.php index 6a6b0f1..f390a38 100644 --- a/src/TokenStream/Token/TokenVariable.php +++ b/src/TokenStream/Token/TokenVariable.php @@ -3,21 +3,22 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\TokenStream\Node\NodeVariable; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\Token\Type\Value; +use nicoSWD\Rule\TokenStream\TokenIterator; -final class TokenVariable extends BaseToken +final class TokenVariable extends BaseToken implements Value { - public function getType(): int + public function getType(): TokenType { return TokenType::VARIABLE; } - public function createNode(TokenStream $tokenStream): BaseToken + public function createNode(TokenIterator $tokenStream): BaseToken { return (new NodeVariable($tokenStream))->getNode(); } diff --git a/src/TokenStream/Token/Type/Logical.php b/src/TokenStream/Token/Type/Logical.php new file mode 100644 index 0000000..aa49492 --- /dev/null +++ b/src/TokenStream/Token/Type/Logical.php @@ -0,0 +1,12 @@ + + */ +namespace nicoSWD\Rule\TokenStream\Token\Type; + +interface Logical +{ +} diff --git a/src/TokenStream/Token/Type/Method.php b/src/TokenStream/Token/Type/Method.php new file mode 100644 index 0000000..dabe45a --- /dev/null +++ b/src/TokenStream/Token/Type/Method.php @@ -0,0 +1,12 @@ + + */ +namespace nicoSWD\Rule\TokenStream\Token\Type; + +interface Method +{ +} diff --git a/src/TokenStream/Token/Type/Operator.php b/src/TokenStream/Token/Type/Operator.php new file mode 100644 index 0000000..ca1667f --- /dev/null +++ b/src/TokenStream/Token/Type/Operator.php @@ -0,0 +1,12 @@ + + */ +namespace nicoSWD\Rule\TokenStream\Token\Type; + +interface Operator +{ +} diff --git a/src/TokenStream/Token/Type/Parenthesis.php b/src/TokenStream/Token/Type/Parenthesis.php new file mode 100644 index 0000000..865a064 --- /dev/null +++ b/src/TokenStream/Token/Type/Parenthesis.php @@ -0,0 +1,12 @@ + + */ +namespace nicoSWD\Rule\TokenStream\Token\Type; + +interface Parenthesis +{ +} diff --git a/src/TokenStream/Token/Type/Value.php b/src/TokenStream/Token/Type/Value.php new file mode 100644 index 0000000..54d7888 --- /dev/null +++ b/src/TokenStream/Token/Type/Value.php @@ -0,0 +1,12 @@ + + */ +namespace nicoSWD\Rule\TokenStream\Token\Type; + +interface Value +{ +} diff --git a/src/TokenStream/Token/Type/Whitespace.php b/src/TokenStream/Token/Type/Whitespace.php new file mode 100644 index 0000000..64bb32a --- /dev/null +++ b/src/TokenStream/Token/Type/Whitespace.php @@ -0,0 +1,12 @@ + + */ +namespace nicoSWD\Rule\TokenStream\Token\Type; + +interface Whitespace +{ +} diff --git a/src/TokenStream/TokenCollection.php b/src/TokenStream/TokenCollection.php index 4fe6d56..1ba867e 100644 --- a/src/TokenStream/TokenCollection.php +++ b/src/TokenStream/TokenCollection.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream; diff --git a/src/TokenStream/TokenIterator.php b/src/TokenStream/TokenIterator.php new file mode 100644 index 0000000..71e5f60 --- /dev/null +++ b/src/TokenStream/TokenIterator.php @@ -0,0 +1,92 @@ + + */ +namespace nicoSWD\Rule\TokenStream; + +use Closure; +use Iterator; +use nicoSWD\Rule\Parser\Exception\ParserException; +use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; +use nicoSWD\Rule\TokenStream\Token\BaseToken; + +readonly class TokenIterator implements Iterator +{ + public function __construct( + private Iterator $stack, + private TokenStream $tokenStream, + ) { + } + + public function next(): void + { + $this->stack->next(); + } + + public function valid(): bool + { + return $this->stack->valid(); + } + + /** @throws ParserException */ + public function current(): BaseToken + { + return $this->getCurrentToken()->createNode($this); + } + + public function key(): int + { + return $this->stack->key(); + } + + public function rewind(): void + { + $this->stack->rewind(); + } + + /** @return Iterator */ + public function getStack(): Iterator + { + return $this->stack; + } + + private function getCurrentToken(): BaseToken + { + return $this->stack->current(); + } + + /** @throws ParserException */ + public function getVariable(string $variableName): BaseToken + { + try { + return $this->tokenStream->getVariable($variableName); + } catch (Exception\UndefinedVariableException) { + throw ParserException::undefinedVariable($variableName, $this->getCurrentToken()); + } + } + + /** @throws ParserException */ + public function getFunction(string $functionName): Closure + { + try { + return $this->tokenStream->getFunction($functionName); + } catch (Exception\UndefinedFunctionException) { + throw ParserException::undefinedFunction($functionName, $this->getCurrentToken()); + } + } + + /** @throws ParserException */ + public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface + { + try { + return $this->tokenStream->getMethod($methodName, $token); + } catch (Exception\UndefinedMethodException) { + throw ParserException::undefinedMethod($methodName, $this->getCurrentToken()); + } catch (Exception\ForbiddenMethodException) { + throw ParserException::forbiddenMethod($methodName, $this->getCurrentToken()); + } + } +} diff --git a/src/TokenStream/TokenIteratorFactory.php b/src/TokenStream/TokenIteratorFactory.php new file mode 100644 index 0000000..18c6dc8 --- /dev/null +++ b/src/TokenStream/TokenIteratorFactory.php @@ -0,0 +1,18 @@ + + */ +namespace nicoSWD\Rule\TokenStream; + +use Iterator; + +final class TokenIteratorFactory +{ + public function create(Iterator $stack, TokenStream $tokenStream): TokenIterator + { + return new TokenIterator($stack, $tokenStream); + } +} diff --git a/src/TokenStream/TokenStream.php b/src/TokenStream/TokenStream.php index 14ba919..d00dcca 100644 --- a/src/TokenStream/TokenStream.php +++ b/src/TokenStream/TokenStream.php @@ -3,90 +3,136 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\TokenStream; -use ArrayIterator; use Closure; -use nicoSWD\Rule\Parser\Exception\ParserException; +use InvalidArgumentException; use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; +use nicoSWD\Rule\Parser\Exception\ParserException; +use nicoSWD\Rule\TokenStream\Exception\UndefinedVariableException; use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\TokenFactory; +use nicoSWD\Rule\Tokenizer\TokenizerInterface; +use nicoSWD\Rule\TokenStream\Token\TokenObject; -class TokenStream extends ArrayIterator +class TokenStream { + private array $functions = []; + private array $methods = []; + private array $variables = []; + public function __construct( - private ArrayIterator $stack, - private AST $ast + private readonly TokenizerInterface $tokenizer, + private readonly TokenFactory $tokenFactory, + private readonly TokenIteratorFactory $tokenIteratorFactory, + private readonly CallableUserMethodFactoryInterface $userMethodFactory, ) { } - public function next(): void + public function getStream(string $rule): TokenIterator { - $this->stack->next(); + return $this->tokenIteratorFactory->create($this->tokenizer->tokenize($rule), $this); } - public function valid(): bool + /** + * @throws Exception\UndefinedMethodException + * @throws Exception\ForbiddenMethodException + */ + public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface { - return $this->stack->valid(); + if ($token instanceof TokenObject) { + return $this->getCallableUserMethod($token, $methodName); + } + + if (empty($this->methods)) { + $this->registerMethods(); + } + + if (!isset($this->methods[$methodName])) { + throw new Exception\UndefinedMethodException(); + } + + return new $this->methods[$methodName]($token); } - /** @throws ParserException */ - public function current(): BaseToken + public function setVariables(array $variables): void { - return $this->getCurrentToken()->createNode($this); + $this->variables = $variables; } - public function key(): int + /** + * @throws UndefinedVariableException + * @throws ParserException + */ + public function getVariable(string $variableName): BaseToken { - return $this->stack->key(); + if (!$this->variableExists($variableName)) { + throw new UndefinedVariableException($variableName); + } + + return $this->tokenFactory->createFromPHPType($this->variables[$variableName]); } - public function rewind(): void + public function variableExists(string $variableName): bool { - $this->stack->rewind(); + return array_key_exists($variableName, $this->variables); } - /** @return ArrayIterator */ - public function getStack(): ArrayIterator + /** @throws Exception\UndefinedFunctionException */ + public function getFunction(string $functionName): Closure { - return $this->stack; + if (empty($this->functions)) { + $this->registerFunctions(); + } + + if (!isset($this->functions[$functionName])) { + throw new Exception\UndefinedFunctionException($functionName); + } + + return $this->functions[$functionName]; } - private function getCurrentToken(): BaseToken + private function registerMethods(): void { - return $this->stack->current(); + foreach ($this->tokenizer->grammar->getInternalMethods() as $internalMethod) { + $this->methods[$internalMethod->name] = $internalMethod->class; + } } - /** @throws ParserException */ - public function getVariable(string $variableName): BaseToken + private function registerFunctions(): void { - try { - return $this->ast->getVariable($variableName); - } catch (Exception\UndefinedVariableException) { - throw ParserException::undefinedVariable($variableName, $this->getCurrentToken()); + foreach ($this->tokenizer->grammar->getInternalFunctions() as $function) { + $this->registerFunctionClass($function->name, $function->class); } } - /** @throws ParserException */ - public function getFunction(string $functionName): Closure + private function registerFunctionClass(string $functionName, string $className): void { - try { - return $this->ast->getFunction($functionName); - } catch (Exception\UndefinedFunctionException) { - throw ParserException::undefinedFunction($functionName, $this->getCurrentToken()); - } + $this->functions[$functionName] = function (?BaseToken ...$args) use ($className) { + $function = new $className(); + + if (!$function instanceof CallableUserFunctionInterface) { + throw new InvalidArgumentException( + sprintf( + '%s must be an instance of %s', + $className, + CallableUserFunctionInterface::class + ) + ); + } + + return $function->call(...$args); + }; } - /** @throws ParserException */ - public function getMethod(string $methodName, BaseToken $token): CallableUserFunctionInterface + /** + * @throws Exception\ForbiddenMethodException + * @throws Exception\UndefinedMethodException + */ + private function getCallableUserMethod(BaseToken $token, string $methodName): CallableUserFunctionInterface { - try { - return $this->ast->getMethod($methodName, $token); - } catch (Exception\UndefinedMethodException) { - throw ParserException::undefinedMethod($methodName, $this->getCurrentToken()); - } catch (Exception\ForbiddenMethodException) { - throw ParserException::forbiddenMethod($methodName, $this->getCurrentToken()); - } + return $this->userMethodFactory->create($token, $this->tokenFactory, $methodName); } } diff --git a/src/TokenStream/TokenStreamFactory.php b/src/TokenStream/TokenStreamFactory.php deleted file mode 100644 index 1a1db0b..0000000 --- a/src/TokenStream/TokenStreamFactory.php +++ /dev/null @@ -1,18 +0,0 @@ - - */ -namespace nicoSWD\Rule\TokenStream; - -use ArrayIterator; - -class TokenStreamFactory -{ - public function create(ArrayIterator $stack, AST $ast): TokenStream - { - return new TokenStream($stack, $ast); - } -} diff --git a/src/Tokenizer/Tokenizer.php b/src/Tokenizer/Tokenizer.php index 702394c..fe2af69 100644 --- a/src/Tokenizer/Tokenizer.php +++ b/src/Tokenizer/Tokenizer.php @@ -3,97 +3,47 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Tokenizer; use ArrayIterator; +use Iterator; use nicoSWD\Rule\Grammar\Grammar; +use nicoSWD\Rule\TokenStream\Token\Token; use nicoSWD\Rule\TokenStream\Token\TokenFactory; -use SplPriorityQueue; -final class Tokenizer implements TokenizerInterface +final class Tokenizer extends TokenizerInterface { - private array $tokens = []; - private string $compiledRegex = ''; - public function __construct( - private Grammar $grammar, - private TokenFactory $tokenFactory + public Grammar $grammar, + private readonly TokenFactory $tokenFactory, ) { - foreach ($grammar->getDefinition() as [$class, $regex, $priority]) { - $this->registerToken($class, $regex, $priority); - } } - public function tokenize(string $string): ArrayIterator + public function tokenize(string $string): Iterator { - $regex = $this->getRegex(); + $regex = $this->grammar->buildRegex(); $stack = []; $offset = 0; - while (preg_match($regex, $string, $matches, 0, $offset)) { + while (preg_match($regex, $string, $matches, offset: $offset)) { $token = $this->getMatchedToken($matches); - $className = $this->tokenFactory->createFromTokenName($token); - - $stack[] = new $className($matches[$token], $offset); + $stack[] = $this->tokenFactory->createFromToken($token, $matches, $offset); $offset += strlen($matches[0]); } return new ArrayIterator($stack); } - public function getGrammar(): Grammar - { - return $this->grammar; - } - - private function registerToken(string $class, string $regex, int $priority): void - { - $this->tokens[$class] = new class($class, $regex, $priority) { - public function __construct( - public string $class, - public string $regex, - public int $priority - ) { - } - }; - } - - private function getMatchedToken(array $matches): string + private function getMatchedToken(array $matches): Token { foreach ($matches as $key => $value) { if ($value !== '' && !is_int($key)) { - return $key; - } - } - - return 'Unknown'; - } - - private function getRegex(): string - { - if (!$this->compiledRegex) { - $regex = []; - - foreach ($this->getQueue() as $token) { - $regex[] = "(?<$token->class>$token->regex)"; + return Token::from($key); } - - $this->compiledRegex = '~(' . implode('|', $regex) . ')~As'; - } - - return $this->compiledRegex; - } - - private function getQueue(): SplPriorityQueue - { - $queue = new SplPriorityQueue(); - - foreach ($this->tokens as $class) { - $queue->insert($class, $class->priority); } - return $queue; + return Token::UNKNOWN; } } diff --git a/src/Tokenizer/TokenizerInterface.php b/src/Tokenizer/TokenizerInterface.php index e49b05d..c53f5da 100644 --- a/src/Tokenizer/TokenizerInterface.php +++ b/src/Tokenizer/TokenizerInterface.php @@ -1,20 +1,23 @@ - + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\Tokenizer; -use ArrayIterator; +use Iterator; use nicoSWD\Rule\Grammar\Grammar; use nicoSWD\Rule\TokenStream\Token\BaseToken; -interface TokenizerInterface +abstract class TokenizerInterface { - /** @return ArrayIterator */ - public function tokenize(string $string): ArrayIterator; + public Grammar $grammar; - public function getGrammar(): Grammar; + /** + * @param string $string + * @return Iterator + */ + abstract public function tokenize(string $string): Iterator; } diff --git a/src/container.php b/src/container.php index c356af5..7bf78c7 100644 --- a/src/container.php +++ b/src/container.php @@ -3,27 +3,27 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule; use nicoSWD\Rule\Grammar\JavaScript\JavaScript; -use nicoSWD\Rule\TokenStream\AST; +use nicoSWD\Rule\Parser\EvaluatableExpressionFactory; +use nicoSWD\Rule\TokenStream\TokenStream; use nicoSWD\Rule\Compiler\CompilerFactory; use nicoSWD\Rule\Evaluator\Evaluator; use nicoSWD\Rule\Evaluator\EvaluatorInterface; -use nicoSWD\Rule\Expression\ExpressionFactory; use nicoSWD\Rule\Tokenizer\Tokenizer; use nicoSWD\Rule\TokenStream\Token\TokenFactory; -use nicoSWD\Rule\TokenStream\TokenStreamFactory; +use nicoSWD\Rule\TokenStream\TokenIteratorFactory; use nicoSWD\Rule\TokenStream\CallableUserMethodFactory; return new class { - private static TokenStreamFactory $tokenStreamFactory; + private static TokenIteratorFactory $tokenStreamFactory; private static TokenFactory $tokenFactory; private static CompilerFactory $compiler; private static JavaScript $javaScript; - private static ExpressionFactory $expressionFactory; + private static EvaluatableExpressionFactory $expressionFactory; private static CallableUserMethodFactory $userMethodFactory; private static Tokenizer $tokenizer; private static Evaluator $evaluator; @@ -64,12 +64,12 @@ private static function compiler(): CompilerFactory return self::$compiler; } - private static function ast(array $variables): AST + private static function ast(array $variables): TokenStream { - $ast = new AST(self::tokenizer(), self::tokenFactory(), self::tokenStreamFactory(), self::userMethodFactory()); - $ast->setVariables($variables); + $tokenStream = new TokenStream(self::tokenizer(), self::tokenFactory(), self::tokenStreamFactory(), self::userMethodFactory()); + $tokenStream->setVariables($variables); - return $ast; + return $tokenStream; } private static function tokenizer(): Tokenizer @@ -90,19 +90,19 @@ private static function javascript(): JavaScript return self::$javaScript; } - private static function tokenStreamFactory(): TokenStreamFactory + private static function tokenStreamFactory(): TokenIteratorFactory { if (!isset(self::$tokenStreamFactory)) { - self::$tokenStreamFactory = new TokenStreamFactory(); + self::$tokenStreamFactory = new TokenIteratorFactory(); } return self::$tokenStreamFactory; } - private static function expressionFactory(): ExpressionFactory + private static function expressionFactory(): EvaluatableExpressionFactory { if (!isset(self::$expressionFactory)) { - self::$expressionFactory = new ExpressionFactory(); + self::$expressionFactory = new EvaluatableExpressionFactory(); } return self::$expressionFactory; diff --git a/tests/integration/AbstractTestBase.php b/tests/integration/AbstractTestBase.php index 30d07a7..74cc63c 100755 --- a/tests/integration/AbstractTestBase.php +++ b/tests/integration/AbstractTestBase.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration; diff --git a/tests/integration/HighlighterTest.php b/tests/integration/HighlighterTest.php index 3f44e1a..c996899 100755 --- a/tests/integration/HighlighterTest.php +++ b/tests/integration/HighlighterTest.php @@ -3,17 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration; -use Exception; use nicoSWD\Rule; use nicoSWD\Rule\Grammar\JavaScript\JavaScript; use nicoSWD\Rule\Highlighter\Highlighter; use nicoSWD\Rule\Tokenizer\Tokenizer; use nicoSWD\Rule\TokenStream\Token\TokenFactory; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; final class HighlighterTest extends TestCase { @@ -24,7 +24,7 @@ protected function setUp(): void $this->highlighter = new Highlighter(new Tokenizer(new JavaScript(), new TokenFactory())); } - /** @test */ + #[Test] public function givenAStyleForATokenGroupItShouldBeUsed(): void { $this->highlighter->setStyle( @@ -36,16 +36,4 @@ public function givenAStyleForATokenGroupItShouldBeUsed(): void $this->assertStringContainsString('[', $code); } - - /** @test */ - public function invalidGroupThrowsException(): void - { - $this->expectException(Exception::class); - $this->expectExceptionMessage('Invalid group'); - - $this->highlighter->setStyle( - 99, - 'color: test-color;' - ); - } } diff --git a/tests/integration/ObjectTest.php b/tests/integration/ObjectTest.php index f4c7cb1..0fef937 100755 --- a/tests/integration/ObjectTest.php +++ b/tests/integration/ObjectTest.php @@ -3,16 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration; use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\Rule; +use PHPUnit\Framework\Attributes\Test; final class ObjectTest extends AbstractTestBase { - /** @test */ + #[Test] public function givenAnObjectHasMethodsWhenPublicTheyShouldBeAccessible(): void { $myObj = new class { @@ -40,7 +41,7 @@ public function cat() $this->assertTrue($this->evaluate('my_obj.test2().cat() === "meow"', $variables)); } - /** @test */ + #[Test] public function givenAnObjectHasPropertiesWhenPublicTheyShouldBeAccessible(): void { $myObj = new class { @@ -54,7 +55,7 @@ public function givenAnObjectHasPropertiesWhenPublicTheyShouldBeAccessible(): vo $this->assertTrue($this->evaluate('my_obj.test() === "my string"', $variables)); } - /** @test */ + #[Test] public function publicMethodsShouldBeAccessibleMagicallyViaGet(): void { $myObj = new class { @@ -71,7 +72,7 @@ public function getString() $this->assertTrue($this->evaluate('my_obj.string() === "some string"', $variables)); } - /** @test */ + #[Test] public function publicMethodsShouldBeAccessibleMagicallyViaIs(): void { $myObj = new class { @@ -93,7 +94,7 @@ public function yes() $this->assertTrue($this->evaluate('my_obj.string(my_obj.yes()) === "yes"', $variables)); } - /** @test */ + #[Test] public function givenAnObjectWhenMagicMethodCallIsAvailableItShouldBeAccessible(): void { $myObj = new class { @@ -129,7 +130,7 @@ public function givenAnObjectWhenMagicMethodsAreCalledDirectlyItShouldThrowAnExc $this->evaluate("my_obj.{$magicMethod}()", $variables); } - /** @test */ + #[Test] public function undefinedMethodsShouldThrowAnError(): void { $myObj = new class() { @@ -145,7 +146,7 @@ public function undefinedMethodsShouldThrowAnError(): void $this->assertSame('Undefined method "nope" at position 6', $rule->getError()); } - /** @test */ + #[Test] public function givenAnObjectWithMagicMethodGetWhenPropertyDoesNotExistItShouldNotBeCalled(): void { $myObj = new class { diff --git a/tests/integration/ParserTest.php b/tests/integration/ParserTest.php index 609678e..f6eca61 100755 --- a/tests/integration/ParserTest.php +++ b/tests/integration/ParserTest.php @@ -3,13 +3,15 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration; +use PHPUnit\Framework\Attributes\Test; + final class ParserTest extends AbstractTestBase { - /** @test */ + #[Test] public function multipleAnds(): void { $rule = 'foo=="MA" && bar=="EGP" && baz>50000'; @@ -37,7 +39,7 @@ public function multipleAnds(): void ])); } - /** @test */ + #[Test] public function mixedOrsAndAnds(): void { $rule = ' @@ -53,14 +55,14 @@ public function mixedOrsAndAnds(): void ])); } - /** @test */ + #[Test] public function emptyOrIncompleteRuleReturnsFalse(): void { $rule = ''; $this->assertFalse($this->evaluate($rule)); } - /** @test */ + #[Test] public function freakingLongRule(): void { $rule = ' @@ -87,7 +89,7 @@ public function freakingLongRule(): void ])); } - /** @test */ + #[Test] public function negativeComparison(): void { $rule = ' @@ -107,7 +109,7 @@ public function negativeComparison(): void ])); } - /** @test */ + #[Test] public function spacesBetweenStuff(): void { $rule = 'foo != 3 @@ -120,7 +122,7 @@ public function spacesBetweenStuff(): void $this->assertTrue($this->evaluate($rule, ['foo' => '-1'])); } - /** @test */ + #[Test] public function singleLineCommentDoesNotKillTheRest(): void { $rule = ' 2 > 3 diff --git a/tests/integration/RuleTest.php b/tests/integration/RuleTest.php index 29f741d..156d1ba 100755 --- a/tests/integration/RuleTest.php +++ b/tests/integration/RuleTest.php @@ -3,16 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration; use nicoSWD\Rule; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; final class RuleTest extends TestCase { - /** @test */ + #[Test] public function basicRuleWithCommentsEvaluatesCorrectly(): void { $string = ' @@ -39,7 +40,7 @@ public function basicRuleWithCommentsEvaluatesCorrectly(): void $this->assertTrue(!$rule->isFalse()); } - /** @test */ + #[Test] public function isValidReturnsFalseOnInvalidSyntax(): void { $ruleStr = '(2 == 2) && (1 < 3 && 3 > 2 (1 == 1))'; @@ -50,7 +51,7 @@ public function isValidReturnsFalseOnInvalidSyntax(): void $this->assertSame('Unexpected "(" at position 28', $rule->getError()); } - /** @test */ + #[Test] public function isValidReturnsTrueOnValidSyntax(): void { $ruleStr = '(2 == 2) && (1 < 3 && 3 > 2 || (1 == 1))'; @@ -61,7 +62,7 @@ public function isValidReturnsTrueOnValidSyntax(): void $this->assertEmpty($rule->getError()); $this->assertTrue($rule->isTrue()); } - /** @test */ + #[Test] public function basicInRule(): void { $ruleStr = '4 in [4, 6, 7]'; @@ -81,7 +82,7 @@ public function basicInRule(): void $this->assertFalse($rule->isTrue()); } - /** @test */ + #[Test] public function basicNotInRule(): void { $ruleStr = '5 not diff --git a/tests/integration/SyntaxErrorTest.php b/tests/integration/SyntaxErrorTest.php index 62b8572..f4fae9f 100755 --- a/tests/integration/SyntaxErrorTest.php +++ b/tests/integration/SyntaxErrorTest.php @@ -3,18 +3,19 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration; use nicoSWD\Rule\Rule; +use PHPUnit\Framework\Attributes\Test; /** * @SuppressWarnings(PHPMD.TooManyMethods) */ final class SyntaxErrorTest extends AbstractTestBase { - /** @test */ + #[Test] public function emptyParenthesisThrowException(): void { $rule = new Rule('(totalamount != 3) ()', [ @@ -25,7 +26,7 @@ public function emptyParenthesisThrowException(): void $this->assertSame('Unexpected "(" at position 19', $rule->getError()); } - /** @test */ + #[Test] public function doubleOperatorThrowsException(): void { $rule = new Rule('country == == "venezuela"', ['country' => 'spain']); @@ -34,7 +35,7 @@ public function doubleOperatorThrowsException(): void $this->assertSame('Unexpected "==" at position 11', $rule->getError()); } - /** @test */ + #[Test] public function missingLeftValueThrowsException(): void { $rule = new Rule('== "venezuela"'); @@ -43,7 +44,7 @@ public function missingLeftValueThrowsException(): void $this->assertSame('Incomplete expression for token "=="', $rule->getError()); } - /** @test */ + #[Test] public function missingOperatorThrowsException(): void { $rule = new Rule('total == -1 total > 10', ['total' => 12]); @@ -52,7 +53,7 @@ public function missingOperatorThrowsException(): void $this->assertSame('Missing operator', $rule->getError()); } - /** @test */ + #[Test] public function missingOpeningParenthesisThrowsException(): void { $rule = new Rule('1 == 1)'); @@ -61,7 +62,7 @@ public function missingOpeningParenthesisThrowsException(): void $this->assertSame('Missing opening parenthesis', $rule->getError()); } - /** @test */ + #[Test] public function missingClosingParenthesisThrowsException(): void { $rule = new Rule('(1 == 1'); @@ -70,7 +71,7 @@ public function missingClosingParenthesisThrowsException(): void $this->assertSame('Missing closing parenthesis', $rule->getError()); } - /** @test */ + #[Test] public function misplacedMinusThrowsException(): void { $rule = new Rule('1 == 1 && -foo == 1', ['foo' => 1]); @@ -79,7 +80,7 @@ public function misplacedMinusThrowsException(): void $this->assertSame('Unknown token "-" at position 10', $rule->getError()); } - /** @test */ + #[Test] public function undefinedVariableThrowsException(): void { $rule = new Rule(' // new line on purpose @@ -89,7 +90,7 @@ public function undefinedVariableThrowsException(): void $this->assertSame('Undefined variable "foo" at position 36', $rule->getError()); } - /** @test */ + #[Test] public function incompleteExpressionExceptionIsThrownCorrectly(): void { $rule = new Rule('1 == 1 && country', ['country' => 'es']); @@ -98,7 +99,7 @@ public function incompleteExpressionExceptionIsThrownCorrectly(): void $this->assertSame('Incomplete condition', $rule->getError()); } - /** @test */ + #[Test] public function rulesEvaluatesTrueThrowsExceptionsForUndefinedVars(): void { $rule = new Rule('nonono=="MA"'); @@ -107,7 +108,7 @@ public function rulesEvaluatesTrueThrowsExceptionsForUndefinedVars(): void $this->assertSame('Undefined variable "nonono" at position 0', $rule->getError()); } - /** @test */ + #[Test] public function rulesEvaluatesTrueThrowsExceptionsOnSyntaxErrors(): void { $rule = new Rule('country == "MA" &&', ['country' => 'es']); @@ -116,7 +117,7 @@ public function rulesEvaluatesTrueThrowsExceptionsOnSyntaxErrors(): void $this->assertSame('Incomplete condition', $rule->getError()); } - /** @test */ + #[Test] public function multipleLogicalTokensThrowException(): void { $rule = new Rule('country == "MA" && &&', ['country' => 'es']); @@ -125,7 +126,7 @@ public function multipleLogicalTokensThrowException(): void $this->assertSame('Unexpected "&&" at position 19', $rule->getError()); } - /** @test */ + #[Test] public function unknownTokenExceptionIsThrown(): void { $rule = new Rule('country == "MA" ^', ['country' => 'es']); diff --git a/tests/integration/TokenizerTest.php b/tests/integration/TokenizerTest.php index 614e031..0643342 100755 --- a/tests/integration/TokenizerTest.php +++ b/tests/integration/TokenizerTest.php @@ -3,14 +3,16 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration; use nicoSWD\Rule\Grammar\JavaScript\JavaScript; use nicoSWD\Rule\Tokenizer\Tokenizer; +use nicoSWD\Rule\TokenStream\Token\Token; use nicoSWD\Rule\TokenStream\Token\TokenFactory; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; use ReflectionMethod; final class TokenizerTest extends TestCase @@ -22,17 +24,16 @@ protected function setUp(): void $this->tokenizer = new Tokenizer(new JavaScript(), new TokenFactory()); } - /** @test */ + #[Test] public function getMatchedTokenReturnsFalseOnFailure(): void { $reflection = new ReflectionMethod($this->tokenizer, 'getMatchedToken'); - $reflection->setAccessible(true); $result = $reflection->invoke($this->tokenizer, []); - $this->assertSame('Unknown', $result); + $this->assertSame(Token::UNKNOWN, $result); } - /** @test */ + #[Test] public function tokenPositionAndLineAreCorrect(): void { $tokens = $this->tokenizer->tokenize('1'); diff --git a/tests/integration/arrays/ArraysTest.php b/tests/integration/arrays/ArraysTest.php index 4409d10..e7c8a23 100755 --- a/tests/integration/arrays/ArraysTest.php +++ b/tests/integration/arrays/ArraysTest.php @@ -3,16 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\arrays; use nicoSWD\Rule\Rule; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class ArraysTest extends AbstractTestBase { - /** @test */ + #[Test] public function arraysEqualUserSuppliedArrays(): void { $this->assertTrue($this->evaluate( @@ -26,20 +27,20 @@ public function arraysEqualUserSuppliedArrays(): void ])); } - /** @test */ + #[Test] public function emptyArrayDoesParseCorrectly(): void { $this->assertTrue($this->evaluate('[] === []')); } - /** @test */ + #[Test] public function literalArrayComparison(): void { $this->assertTrue($this->evaluate('[123, 12] === [123, 12]')); $this->assertFalse($this->evaluate('[123, 12] === [123, 12, 1]')); } - /** @test */ + #[Test] public function commentsAreIgnoredInArray(): void { $this->assertTrue($this->evaluate( @@ -51,7 +52,7 @@ public function commentsAreIgnoredInArray(): void )); } - /** @test */ + #[Test] public function trailingCommaThrowsException(): void { $rule = new Rule('["foo", "bar", ] === ["foo", "bar"]'); @@ -60,7 +61,7 @@ public function trailingCommaThrowsException(): void $this->assertSame('Unexpected "," at position 15', $rule->getError()); } - /** @test */ + #[Test] public function lineIsReportedCorrectlyOnSyntaxError2(): void { $rule = new Rule('["foo", "bar", ,] === ["foo", "bar"]'); @@ -69,7 +70,7 @@ public function lineIsReportedCorrectlyOnSyntaxError2(): void $this->assertSame('Unexpected "," at position 15', $rule->getError()); } - /** @test */ + #[Test] public function missingCommaThrowsException(): void { $rule = new Rule('["foo" "bar"] === ["foo", "bar"]'); @@ -78,7 +79,7 @@ public function missingCommaThrowsException(): void $this->assertSame('Unexpected "bar" at position 8', $rule->getError()); } - /** @test */ + #[Test] public function unexpectedTokenThrowsException(): void { $rule = new Rule('["foo", ===] === ["foo", "bar"]'); @@ -87,7 +88,7 @@ public function unexpectedTokenThrowsException(): void $this->assertSame('Unexpected "===" at position 8', $rule->getError()); } - /** @test */ + #[Test] public function unexpectedEndOfStringThrowsException(): void { $rule = new Rule('["foo", "bar"'); diff --git a/tests/integration/functions/ParseFloatTest.php b/tests/integration/functions/ParseFloatTest.php index eae6c70..4a9c396 100755 --- a/tests/integration/functions/ParseFloatTest.php +++ b/tests/integration/functions/ParseFloatTest.php @@ -3,40 +3,41 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\functions; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class ParseFloatTest extends AbstractTestBase { - /** @test */ + #[Test] public function onStringLiteral(): void { $this->assertTrue($this->evaluate('parseFloat("3.1337") === 3.1337')); } - /** @test */ + #[Test] public function onStringLiteralWithSpaces(): void { $this->assertTrue($this->evaluate('parseFloat(" 3.1 ") === 3.1')); } - /** @test */ + #[Test] public function onStringLiteralWithNonNumericChars(): void { $this->assertTrue($this->evaluate('parseFloat("3.12aaa") === 3.12')); } - /** @test */ + #[Test] public function onUserDefinedVariable(): void { $this->assertTrue($this->evaluate('parseFloat(foo) === 3.4', ['foo' => '3.4'])); $this->assertFalse($this->evaluate('parseFloat(foo) === "3.5"', ['foo' => 3.5])); } - /** @test */ + #[Test] public function callWithoutArgsShouldReturnNaN(): void { $this->assertFalse($this->evaluate('parseFloat() === 1')); diff --git a/tests/integration/functions/ParseIntTest.php b/tests/integration/functions/ParseIntTest.php index 0c47cf4..9833ddb 100755 --- a/tests/integration/functions/ParseIntTest.php +++ b/tests/integration/functions/ParseIntTest.php @@ -3,40 +3,41 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\functions; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class ParseIntTest extends AbstractTestBase { - /** @test */ + #[Test] public function onStringLiteral(): void { $this->assertTrue($this->evaluate('parseInt("3") === 3')); } - /** @test */ + #[Test] public function onStringLiteralWithSpaces(): void { $this->assertTrue($this->evaluate('parseInt(" 3 ") === 3')); } - /** @test */ + #[Test] public function onStringLiteralWithNonNumericChars(): void { $this->assertTrue($this->evaluate('parseInt("3aaa") === 3')); } - /** @test */ + #[Test] public function onUserDefinedVariable(): void { $this->assertTrue($this->evaluate('parseInt(foo) === 3', ['foo' => '3'])); $this->assertFalse($this->evaluate('parseInt(foo) === "3"', ['foo' => 3])); } - /** @test */ + #[Test] public function callWithoutArgsShouldReturnNan(): void { $this->assertFalse($this->evaluate('parseInt() === 1')); diff --git a/tests/integration/functions/SyntaxErrorTest.php b/tests/integration/functions/SyntaxErrorTest.php index 95fb7c0..6649b15 100755 --- a/tests/integration/functions/SyntaxErrorTest.php +++ b/tests/integration/functions/SyntaxErrorTest.php @@ -3,16 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\functions; use nicoSWD\Rule\Rule; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class SyntaxErrorTest extends AbstractTestBase { - /** @test */ + #[Test] public function undefinedFunctionThrowsException(): void { $rule = new Rule('nope() === true'); @@ -21,7 +22,7 @@ public function undefinedFunctionThrowsException(): void $this->assertSame('nope is not defined at position 0', $rule->getError()); } - /** @test */ + #[Test] public function incorrectSpellingThrowsException(): void { $rule = new Rule('/* fail */ paRSeInt("2") === 2'); diff --git a/tests/integration/methods/CharAtTest.php b/tests/integration/methods/CharAtTest.php index 95e6a69..e8b3738 100755 --- a/tests/integration/methods/CharAtTest.php +++ b/tests/integration/methods/CharAtTest.php @@ -3,36 +3,37 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class CharAtTest extends AbstractTestBase { - /** @test */ + #[Test] public function ifOmittedPositionFallsBackToZero(): void { $this->assertTrue($this->evaluate('foo.charAt() === "b"', ['foo' => 'bar'])); $this->assertTrue($this->evaluate('"bar".charAt() === "b"')); } - /** @test */ + #[Test] public function callWithValidPosition(): void { $this->assertTrue($this->evaluate('foo.charAt(1) === "a"', ['foo' => 'bar'])); $this->assertTrue($this->evaluate('"bar".charAt(2) === "r"')); } - /** @test */ + #[Test] public function invalidOffsetReturnsEmptyString(): void { $this->assertTrue($this->evaluate('foo.charAt(99) === ""', ['foo' => 'bar'])); $this->assertTrue($this->evaluate('"foo".charAt(99) === ""')); } - /** @test */ + #[Test] public function ifBooleansAndNullAreCastedToOneAndZero(): void { $this->assertTrue($this->evaluate('"foo".charAt(true) === "o"')); diff --git a/tests/integration/methods/CombinedTest.php b/tests/integration/methods/CombinedTest.php index f7aadd0..9ecafec 100755 --- a/tests/integration/methods/CombinedTest.php +++ b/tests/integration/methods/CombinedTest.php @@ -3,15 +3,16 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class CombinedTest extends AbstractTestBase { - /** @test */ + #[Test] public function mixedMethodCalls(): void { $this->assertTrue($this->evaluate( @@ -41,7 +42,7 @@ public function mixedMethodCalls(): void )); } - /** @test */ + #[Test] public function chainedMethodCalls(): void { $this->assertTrue($this->evaluate( @@ -69,7 +70,7 @@ public function chainedMethodCalls(): void )); } - /** @test */ + #[Test] public function functionCallInsideMethod(): void { $this->assertTrue($this->evaluate('"abc".substr(parseInt(" 2 ")) === "c"')); diff --git a/tests/integration/methods/ConcatTest.php b/tests/integration/methods/ConcatTest.php index 1e0c4ac..028520b 100755 --- a/tests/integration/methods/ConcatTest.php +++ b/tests/integration/methods/ConcatTest.php @@ -3,15 +3,16 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class ConcatTest extends AbstractTestBase { - /** @test */ + #[Test] public function allParametersAreConcatenated(): void { $this->assertTrue($this->evaluate('foo.concat("bar", "baz") === "foobarbaz"', ['foo' => 'foo'])); @@ -20,7 +21,7 @@ public function allParametersAreConcatenated(): void $this->assertTrue($this->evaluate('"foo".concat("bar", 1) === "foobar1"')); } - /** @test */ + #[Test] public function arraysAreImplodedByCommaBeforeConcatenating(): void { $this->assertTrue($this->evaluate('"foo".concat("bar", [1, 2]) === "foobar1,2"')); diff --git a/tests/integration/methods/EndsWithTest.php b/tests/integration/methods/EndsWithTest.php index 1e98b21..48075de 100755 --- a/tests/integration/methods/EndsWithTest.php +++ b/tests/integration/methods/EndsWithTest.php @@ -3,35 +3,36 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class EndsWithTest extends AbstractTestBase { - /** @test */ + #[Test] public function givenAStringWhenEndsWithNeedleItShouldReturnTrue(): void { $this->assertTrue($this->evaluate('foo.endsWith("llo") === true', ['foo' => 'hello'])); $this->assertTrue($this->evaluate('"hello".endsWith("llo") === true')); } - /** @test */ + #[Test] public function givenAStringWhenNotEndsWithNeedleItShouldReturnFalse(): void { $this->assertTrue($this->evaluate('"hello".endsWith("ell") === false')); } - /** @test */ + #[Test] public function givenAStringWhenTestedWithEndsWithWithoutArgsItShouldReturnFalse(): void { $this->assertTrue($this->evaluate('"hello".endsWith() === false')); } - /** @test */ + #[Test] public function givenAStringWhenTestedOnNonStringValuesItShouldThrowAnException(): void { $this->expectException(ParserException::class); diff --git a/tests/integration/methods/IndexOfTest.php b/tests/integration/methods/IndexOfTest.php index 37ffe85..cad5e12 100755 --- a/tests/integration/methods/IndexOfTest.php +++ b/tests/integration/methods/IndexOfTest.php @@ -3,28 +3,29 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class IndexOfTest extends AbstractTestBase { - /** @test */ + #[Test] public function validNeedleReturnsCorrectPosition(): void { $this->assertTrue($this->evaluate('foo.indexOf("a") === 1', ['foo' => 'bar'])); $this->assertTrue($this->evaluate('"bar".indexOf("b") === 0')); } - /** @test */ + #[Test] public function omittedParameterReturnsNegativeOne(): void { $this->assertTrue($this->evaluate('"bar".indexOf() === -1')); } - /** @test */ + #[Test] public function negativeOneIsReturnedIfNeedleNotFound(): void { $this->assertTrue($this->evaluate('"bar".indexOf("foo") === -1')); diff --git a/tests/integration/methods/JoinTest.php b/tests/integration/methods/JoinTest.php index 8363ccb..898b9e2 100755 --- a/tests/integration/methods/JoinTest.php +++ b/tests/integration/methods/JoinTest.php @@ -3,29 +3,30 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class JoinTest extends AbstractTestBase { protected array $array = ['foo' => ['foo', 'bar']]; - /** @test */ + #[Test] public function ifOmittedDelimiterFallsBackToDefault(): void { $this->assertTrue($this->evaluate('foo.join() === "foo,bar"', $this->array)); } - /** @test */ + #[Test] public function literalStringDelimiter(): void { $this->assertTrue($this->evaluate('foo.join("|") === "foo|bar"', $this->array)); } - /** @test */ + #[Test] public function variableValueAsDelimiter(): void { $this->assertTrue($this->evaluate( @@ -34,20 +35,20 @@ public function variableValueAsDelimiter(): void )); } - /** @test */ + #[Test] public function callOnStringLiteralArray(): void { $this->assertTrue($this->evaluate('[1, 2, 3].join("|") === "1|2|3"')); $this->assertTrue($this->evaluate('[1, 2, 3] . join("|") === "1|2|3"')); } - /** @test */ + #[Test] public function variableInArrayIsJoined(): void { $this->assertTrue($this->evaluate('[1, 2, foo].join("|") === "1|2|3"', ['foo' => 3])); } - /** @test */ + #[Test] public function joinOnEmptyArray(): void { $this->assertTrue($this->evaluate('[].join("|") === ""')); diff --git a/tests/integration/methods/ReplaceTest.php b/tests/integration/methods/ReplaceTest.php index cbe1ee0..6720618 100755 --- a/tests/integration/methods/ReplaceTest.php +++ b/tests/integration/methods/ReplaceTest.php @@ -3,40 +3,41 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class ReplaceTest extends AbstractTestBase { - /** @test */ + #[Test] public function validNeedleReturnsCorrectPosition(): void { $this->assertTrue($this->evaluate('foo.replace("a", "A") === "bAr"', ['foo' => 'bar'])); $this->assertTrue($this->evaluate('"bar".replace("r", "R") === "baR"')); } - /** @test */ + #[Test] public function omittedParametersDoNotReplaceAnything(): void { $this->assertTrue($this->evaluate('"bar".replace() === "bar"')); } - /** @test */ + #[Test] public function omittedSecondParameterReplacesWithUndefined(): void { $this->assertTrue($this->evaluate('"bar".replace("r") === "baundefined"')); } - /** @test */ + #[Test] public function replaceWithRegularExpression(): void { $this->assertTrue($this->evaluate('"arbar".replace(/ar$/, "") === "arb"')); } - /** @test */ + #[Test] public function regularExpressionWithGModifier(): void { $this->assertTrue($this->evaluate('"foofoo".replace(/foo/, "") === "foo"')); diff --git a/tests/integration/methods/SplitTest.php b/tests/integration/methods/SplitTest.php index c3bf4cd..92c7915 100755 --- a/tests/integration/methods/SplitTest.php +++ b/tests/integration/methods/SplitTest.php @@ -3,31 +3,32 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class SplitTest extends AbstractTestBase { protected array $var = ['foo' => 'bar,baz,foo']; - /** @test */ + #[Test] public function ifOmittedSeparatorFallsBackToDefault(): void { $this->assertTrue($this->evaluate('foo.split() === ["bar,baz,foo"]', $this->var)); $this->assertTrue($this->evaluate('["bar,baz,foo"] === foo.split()', $this->var)); } - /** @test */ + #[Test] public function splittingLiteralStringAndVariableString(): void { $this->assertTrue($this->evaluate('foo.split(",") === ["bar", "baz", "foo"]', $this->var)); $this->assertTrue($this->evaluate('"bar,baz,foo".split(",") === ["bar", "baz", "foo"]')); } - /** @test */ + #[Test] public function booleansAndNullDoNotSplitAnywhere(): void { $this->assertTrue($this->evaluate('"foo".split(true) === ["foo"]')); @@ -35,7 +36,7 @@ public function booleansAndNullDoNotSplitAnywhere(): void $this->assertTrue($this->evaluate('"foo".split(null) === ["foo"]')); } - /** @test */ + #[Test] public function splitDelimiterAsVariable(): void { $this->assertTrue($this->evaluate( @@ -44,7 +45,7 @@ public function splitDelimiterAsVariable(): void )); } - /** @test */ + #[Test] public function splitDelimiterAsVariableWithMethodCall(): void { $this->assertTrue($this->evaluate( @@ -56,19 +57,19 @@ public function splitDelimiterAsVariableWithMethodCall(): void )); } - /** @test */ + #[Test] public function splitWithRegularExpression(): void { $this->assertTrue($this->evaluate('"foo bar".split(/\s+/) === ["foo", "bar"]')); } - /** @test */ + #[Test] public function splitWithRegexAndLimit(): void { $this->assertTrue($this->evaluate('"foo bar baz".split(/\s+/, 2) === ["foo", "bar baz"]')); } - /** @test */ + #[Test] public function splitWithLimit(): void { $this->assertTrue($this->evaluate('"foo bar baz".split(" ", 2) === ["foo", "bar baz"]')); diff --git a/tests/integration/methods/StartsWithTest.php b/tests/integration/methods/StartsWithTest.php index 793eb52..e7651eb 100755 --- a/tests/integration/methods/StartsWithTest.php +++ b/tests/integration/methods/StartsWithTest.php @@ -3,30 +3,31 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\Parser\Exception\ParserException; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class StartsWithTest extends AbstractTestBase { - /** @test */ + #[Test] public function givenAStringWhenStartsWithNeedleItShouldReturnTrue(): void { $this->assertTrue($this->evaluate('"bar".startsWith("ba") === true')); $this->assertTrue($this->evaluate('foo.startsWith("ar", 1) === true', ['foo' => 'bar'])); } - /** @test */ + #[Test] public function givenAStringWhenNotStartsWithNeedleItShouldReturnTrue(): void { $this->assertTrue($this->evaluate('"bar".startsWith("a") === false')); $this->assertTrue($this->evaluate('foo.startsWith("x") === false', ['foo' => 'bar'])); } - /** @test */ + #[Test] public function givenAStringWhenTestedOnNonStringValuesItShouldThrowAnException(): void { $this->expectException(ParserException::class); diff --git a/tests/integration/methods/SubstrTest.php b/tests/integration/methods/SubstrTest.php index 053dc6d..2dfcc0c 100755 --- a/tests/integration/methods/SubstrTest.php +++ b/tests/integration/methods/SubstrTest.php @@ -3,34 +3,35 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class SubstrTest extends AbstractTestBase { - /** @test */ + #[Test] public function substrReturnsCorrectPartOfString(): void { $this->assertTrue($this->evaluate('foo.substr(1, 2) === "ar"', ['foo' => 'bar'])); $this->assertTrue($this->evaluate('"bar".substr(0, 1) === "b"')); } - /** @test */ + #[Test] public function outOfBoundsOffsetReturnsEmptyString(): void { $this->assertTrue($this->evaluate('"bar".substr(100) === ""')); } - /** @test */ + #[Test] public function omittedParametersReturnsSameString(): void { $this->assertTrue($this->evaluate('"bar".substr() === "bar"')); } - /** @test */ + #[Test] public function negativeOffsetReturnsEndOfString(): void { $this->assertTrue($this->evaluate('"bar".substr(-1) === "r"')); diff --git a/tests/integration/methods/SyntaxErrorTest.php b/tests/integration/methods/SyntaxErrorTest.php index 6af16a9..ce9cf0a 100755 --- a/tests/integration/methods/SyntaxErrorTest.php +++ b/tests/integration/methods/SyntaxErrorTest.php @@ -3,16 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\Rule; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class SyntaxErrorTest extends AbstractTestBase { - /** @test */ + #[Test] public function missingCommaInArgumentsThrowsException(): void { $rule = new Rule('"foo".charAt(1 2 ) === "b"'); @@ -21,7 +22,7 @@ public function missingCommaInArgumentsThrowsException(): void $this->assertSame('Unexpected "2" at position 15', $rule->getError()); } - /** @test */ + #[Test] public function missingValueInArgumentsThrowsException(): void { $rule = new Rule('"foo".charAt(1 , ) === "b"'); @@ -30,7 +31,7 @@ public function missingValueInArgumentsThrowsException(): void $this->assertSame('Unexpected "," at position 17', $rule->getError()); } - /** @test */ + #[Test] public function missingValueBetweenCommasInArgumentsThrowsException(): void { $rule = new Rule('"foo".charAt(1 , , ) === "b"'); @@ -39,7 +40,7 @@ public function missingValueBetweenCommasInArgumentsThrowsException(): void $this->assertSame('Unexpected "," at position 17', $rule->getError()); } - /** @test */ + #[Test] public function unexpectedTokenInArgumentsThrowsException(): void { $rule = new Rule('"foo".charAt(1 , < , ) === "b"'); @@ -48,7 +49,7 @@ public function unexpectedTokenInArgumentsThrowsException(): void $this->assertSame('Unexpected "<" at position 17', $rule->getError()); } - /** @test */ + #[Test] public function unexpectedEndOfStringThrowsException(): void { $rule = new Rule('"foo".charAt(1 , '); @@ -57,7 +58,7 @@ public function unexpectedEndOfStringThrowsException(): void $this->assertSame('Unexpected end of string', $rule->getError()); } - /** @test */ + #[Test] public function undefinedMethodThrowsException(): void { $rule = new Rule('/^foo$/.teddst("foo") === true'); @@ -66,7 +67,7 @@ public function undefinedMethodThrowsException(): void $this->assertSame('Undefined method "teddst" at position 7', $rule->getError()); } - /** @test */ + #[Test] public function incorrectSpellingThrowsException(): void { $rule = new Rule('"foo".ChARat(1) === "o"'); @@ -75,7 +76,7 @@ public function incorrectSpellingThrowsException(): void $this->assertSame('Undefined method "ChARat" at position 5', $rule->getError()); } - /** @test */ + #[Test] public function callOnNonArray(): void { $rule = new Rule('"foo".join("|") === ""'); @@ -84,12 +85,12 @@ public function callOnNonArray(): void $this->assertSame('foo.join is not a function', $rule->getError()); } - /** @test */ + #[Test] public function exceptionIsThrownOnTypeError(): void { $rule = new Rule('"foo".test("foo") === false'); $this->assertFalse($rule->isValid()); - $this->assertSame('undefined is not a function', $rule->getError()); + $this->assertSame('test() is not a function', $rule->getError()); } } diff --git a/tests/integration/methods/TestTest.php b/tests/integration/methods/TestTest.php index dd905c2..8466863 100755 --- a/tests/integration/methods/TestTest.php +++ b/tests/integration/methods/TestTest.php @@ -3,29 +3,30 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class TestTest extends AbstractTestBase { - /** @test */ + #[Test] public function basicRegularExpression(): void { $this->assertTrue($this->evaluate('/^foo$/.test("foo") === true')); $this->assertTrue($this->evaluate('/^foo$/.test(foo) === true', ['foo' => 'foo'])); } - /** @test */ + #[Test] public function arrayIsConvertedToString(): void { $this->assertTrue($this->evaluate('/^foo$/.test(["foo"]) === true')); $this->assertTrue($this->evaluate('/1/.test([[[1]]]) === true')); } - /** @test */ + #[Test] public function modifiers(): void { $this->assertTrue($this->evaluate('/^foo$/i.test("FOO") === true')); @@ -34,7 +35,7 @@ public function modifiers(): void $this->assertFalse($this->evaluate('/^foo$/.test("' . "\n\n" .'foo") === true')); } - /** @test */ + #[Test] public function gModifierIsIgnored(): void { $this->assertTrue($this->evaluate('/^foo$/gi.test("foo") === true'), 'gi'); @@ -42,7 +43,7 @@ public function gModifierIsIgnored(): void $this->assertTrue($this->evaluate('/^foo$/g.test("foo") === true'), '"g" modifier alone'); } - /** @test */ + #[Test] public function booleansAndNullsAsSubject(): void { $this->assertTrue($this->evaluate('/^foo$/.test(true) === false')); @@ -51,7 +52,7 @@ public function booleansAndNullsAsSubject(): void $this->assertTrue($this->evaluate('/^true/.test(true) === false')); } - /** @test */ + #[Test] public function withOmittedParameters(): void { $this->assertTrue($this->evaluate('/^foo$/.test() === false')); diff --git a/tests/integration/methods/ToUpperCaseTest.php b/tests/integration/methods/ToUpperCaseTest.php index f17cf2c..ef17cf7 100755 --- a/tests/integration/methods/ToUpperCaseTest.php +++ b/tests/integration/methods/ToUpperCaseTest.php @@ -3,16 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\methods; use nicoSWD\Rule\Rule; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class ToUpperCaseTest extends AbstractTestBase { - /** @test */ + #[Test] public function spacesBetweenVariableAndMethodWork(): void { $this->assertTrue($this->evaluate('foo . toUpperCase() === "BAR"', ['foo' => 'bar'])); @@ -24,20 +25,20 @@ public function spacesBetweenVariableAndMethodWork(): void )); } - /** @test */ + #[Test] public function ifCallOnStringLiteralsWorks(): void { $this->assertTrue($this->evaluate('"bar".toUpperCase() === "BAR"')); $this->assertTrue($this->evaluate('"bar" . toUpperCase() === "BAR"')); } - /** @test */ + #[Test] public function ifMethodCanBeCalledOnVariablesHoldingIntegers(): void { $this->assertTrue($this->evaluate('foo.toUpperCase() === "1"', ['foo' => 1])); } - /** @test */ + #[Test] public function callOnIntegersThrowsException(): void { $rule = new Rule('1.toUpperCase() === "1"', ['foo' => 1]); diff --git a/tests/integration/operators/OperatorsTest.php b/tests/integration/operators/OperatorsTest.php index 226bc2a..18cb9a7 100755 --- a/tests/integration/operators/OperatorsTest.php +++ b/tests/integration/operators/OperatorsTest.php @@ -3,16 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\operators; use nicoSWD\Rule\Rule; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class OperatorsTest extends AbstractTestBase { - /** @test */ + #[Test] public function allAvailableOperators(): void { $this->assertTrue($this->evaluate('3 == 3'), 'Equal operator failed on two integers'); @@ -30,7 +31,7 @@ public function allAvailableOperators(): void $this->assertFalse($this->evaluate('2 !== 2')); } - /** @test */ + #[Test] public function strictOperators(): void { $this->assertFalse($this->evaluate('"4" === 4')); @@ -40,7 +41,7 @@ public function strictOperators(): void $this->assertFalse($this->evaluate('4 !== 4')); } - /** @test */ + #[Test] public function inOperator(): void { $this->assertTrue($this->evaluate('123 in foo', ['foo' => [123, 12]])); @@ -49,13 +50,13 @@ public function inOperator(): void $this->assertTrue($this->evaluate('123 in [123, 12]')); } - /** @test */ + #[Test] public function inOperatorOnReturnedValueByMethodCall(): void { $this->assertTrue($this->evaluate('"123" in "321,123".split(",")')); } - /** @test */ + #[Test] public function inOperatorWithNonArrayRightValueThrowsException(): void { $rule = new Rule('"123" in "foo"'); @@ -64,7 +65,7 @@ public function inOperatorWithNonArrayRightValueThrowsException(): void $this->assertSame('Expected array, got "string"', $rule->getError()); } - /** @test */ + #[Test] public function commentsAreIgnoredCorrectly(): void { $this->assertFalse($this->evaluate('1 == 2 // || 1 == 1')); @@ -76,7 +77,7 @@ public function commentsAreIgnoredCorrectly(): void )); } - /** @test */ + #[Test] public function equalOperator(): void { $this->assertTrue($this->evaluate('foo == -1', ['foo' => -1])); diff --git a/tests/integration/scalars/ScalarTest.php b/tests/integration/scalars/ScalarTest.php index 8efad67..2a1de12 100755 --- a/tests/integration/scalars/ScalarTest.php +++ b/tests/integration/scalars/ScalarTest.php @@ -3,15 +3,16 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\integration\scalars; use nicoSWD\Rule\tests\integration\AbstractTestBase; +use PHPUnit\Framework\Attributes\Test; final class ScalarTest extends AbstractTestBase { - /** @test */ + #[Test] public function booleans(): void { $this->assertTrue($this->evaluate('"0" == false')); @@ -25,7 +26,7 @@ public function booleans(): void $this->assertFalse($this->evaluate('foo !== true', ['foo' => true])); } - /** @test */ + #[Test] public function nullValues(): void { $this->assertTrue($this->evaluate('foo === null', ['foo' => null])); @@ -36,7 +37,7 @@ public function nullValues(): void $this->assertFalse($this->evaluate('"" === null', ['foo' => null])); } - /** @test */ + #[Test] public function floatPrecision(): void { $this->assertFalse($this->evaluate('foo === "1.0000034"', ['foo' => 1.0000034])); @@ -47,7 +48,7 @@ public function floatPrecision(): void $this->assertTrue($this->evaluate('2 > 1.0000034')); } - /** @test */ + #[Test] public function negativeNumbers(): void { $rule = 'foo > -1 && foo < 1'; diff --git a/tests/unit/Evaluator/EvaluatorTest.php b/tests/unit/Evaluator/EvaluatorTest.php index 8ce9d36..f5d6511 100755 --- a/tests/unit/Evaluator/EvaluatorTest.php +++ b/tests/unit/Evaluator/EvaluatorTest.php @@ -3,13 +3,14 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\Evaluator; use nicoSWD\Rule\Evaluator\Evaluator; use nicoSWD\Rule\Evaluator\Exception\UnknownSymbolException; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; final class EvaluatorTest extends TestCase { @@ -20,7 +21,7 @@ protected function setUp(): void $this->evaluator = new Evaluator(); } - /** @test */ + #[Test] public function givenACompiledRuleWithAnLogicalAndItShouldEvaluateBothOperandsAndReturnTheResult(): void { $this->assertTrue($this->evaluator->evaluate('1&1')); @@ -29,7 +30,7 @@ public function givenACompiledRuleWithAnLogicalAndItShouldEvaluateBothOperandsAn $this->assertFalse($this->evaluator->evaluate('0&0')); } - /** @test */ + #[Test] public function givenACompiledRuleWithAnLogicalOrItShouldEvaluateBothOperandsAndReturnTheResult(): void { $this->assertTrue($this->evaluator->evaluate('1|1')); @@ -38,7 +39,7 @@ public function givenACompiledRuleWithAnLogicalOrItShouldEvaluateBothOperandsAnd $this->assertFalse($this->evaluator->evaluate('0|0')); } - /** @test */ + #[Test] public function givenACompiledRuleWithGroupsTheyShouldBeEvaluatedFirst(): void { $this->assertTrue($this->evaluator->evaluate('0|(1|0)')); @@ -48,7 +49,7 @@ public function givenACompiledRuleWithGroupsTheyShouldBeEvaluatedFirst(): void $this->assertFalse($this->evaluator->evaluate('0|(0|(1&0))')); } - /** @test */ + #[Test] public function givenACharacterWhenUnknownItShouldThrowAnException(): void { $this->expectException(UnknownSymbolException::class); diff --git a/tests/unit/Expression/ExpressionFactoryTest.php b/tests/unit/Expression/ExpressionFactoryTest.php index 98758c8..4568966 100755 --- a/tests/unit/Expression/ExpressionFactoryTest.php +++ b/tests/unit/Expression/ExpressionFactoryTest.php @@ -3,14 +3,16 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\Expression; use nicoSWD\Rule\Expression; use nicoSWD\Rule\Expression\ExpressionFactory; use nicoSWD\Rule\TokenStream\Token; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; final class ExpressionFactoryTest extends TestCase { @@ -21,10 +23,8 @@ protected function setUp(): void $this->factory = new ExpressionFactory(); } - /** - * @test - * @dataProvider expressionProvider - */ + #[Test] + #[DataProvider('expressionProvider')] public function givenAnEqualOperatorItShouldCreateAnEqualExpression( string $expressionClass, Token\BaseToken $token @@ -35,7 +35,7 @@ public function givenAnEqualOperatorItShouldCreateAnEqualExpression( ); } - public function expressionProvider(): array + public static function expressionProvider(): array { return [ [Expression\EqualExpression::class, new Token\TokenEqual('==')], diff --git a/tests/unit/Grammar/GrammarTest.php b/tests/unit/Grammar/GrammarTest.php index f0d1b13..08fbda4 100755 --- a/tests/unit/Grammar/GrammarTest.php +++ b/tests/unit/Grammar/GrammarTest.php @@ -3,9 +3,9 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ -namespace nicoSWD\Rule\tests\unit\Parser; +namespace nicoSWD\Rule\tests\unit\Grammar; use nicoSWD\Rule\Grammar\Grammar; use PHPUnit\Framework\TestCase; @@ -19,6 +19,16 @@ public function getDefinition(): array { return []; } + + public function getInternalFunctions(): array + { + return []; + } + + public function getInternalMethods(): array + { + return []; + } }; $this->assertSame([], $grammar->getDefinition()); diff --git a/tests/unit/Parser/ParserTest.php b/tests/unit/Parser/ParserTest.php index 95546c2..8629c32 100755 --- a/tests/unit/Parser/ParserTest.php +++ b/tests/unit/Parser/ParserTest.php @@ -3,41 +3,41 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\Parser; +use ArrayIterator; use Mockery as m; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use nicoSWD\Rule\TokenStream\AST; +use nicoSWD\Rule\Compiler\StandardCompiler; +use nicoSWD\Rule\Parser\EvaluatableExpressionFactory; +use nicoSWD\Rule\TokenStream\TokenStream; use nicoSWD\Rule\Compiler\CompilerFactoryInterface; -use nicoSWD\Rule\Compiler\CompilerInterface; -use nicoSWD\Rule\Expression\BaseExpression; -use nicoSWD\Rule\Expression\ExpressionFactoryInterface; use nicoSWD\Rule\Parser\Parser; use nicoSWD\Rule\TokenStream\Token; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\TokenIterator; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; final class ParserTest extends TestCase { use MockeryPHPUnitIntegration; - private AST|m\Mock $ast; - private ExpressionFactoryInterface|m\Mock $expressionFactory; + private TokenStream|m\Mock $tokenStream; + private EvaluatableExpressionFactory $expressionFactory; private CompilerFactoryInterface|m\Mock $compilerFactory; private Parser $parser; protected function setUp(): void { - $this->ast = m::mock(AST::class); - $this->expressionFactory = m::mock(ExpressionFactoryInterface::class); + $this->tokenStream = m::mock(TokenStream::class); $this->compilerFactory = m::mock(CompilerFactoryInterface::class); - $this->parser = new Parser($this->ast, $this->expressionFactory, $this->compilerFactory); + $this->parser = new Parser($this->tokenStream, new EvaluatableExpressionFactory(), $this->compilerFactory); } - /** @test */ + #[Test] public function givenARuleStringWhenValidItShouldReturnTheCompiledRule(): void { $tokens = [ @@ -54,38 +54,12 @@ public function givenARuleStringWhenValidItShouldReturnTheCompiledRule(): void new Token\TokenComment('// true dat!') ]; - $compiler = m::mock(CompilerInterface::class); - $compiler->shouldReceive('addLogical')->once(); - $compiler->shouldReceive('addParentheses')->twice(); - $compiler->shouldReceive('addBoolean')->twice(); - $compiler->shouldReceive('getCompiledRule')->once()->andReturn('(1)&1'); - - /** @var m\MockInterface $tokenStream */ - $tokenStream = \Mockery::mock(TokenStream::class); - $tokenStream->shouldReceive('rewind')->once(); - $tokenStream->shouldReceive('next'); - $tokenStream->shouldReceive('current')->andReturn(...$tokens); - $tokenStream->shouldReceive('valid')->andReturnUsing(function () use (&$tokens) { - return !!next($tokens); - }); + $compiler = new StandardCompiler(); + $arrayIterator = new ArrayIterator($tokens); + $tokenIterator = new TokenIterator($arrayIterator, $this->tokenStream); $this->compilerFactory->shouldReceive('create')->once()->andReturn($compiler); - $this->ast->shouldReceive('getStream')->once()->andReturn($tokenStream); - - $equalExpression = m::mock(BaseExpression::class); - $equalExpression->shouldReceive('evaluate')->once()->with(1, '1'); - - $greaterExpression = m::mock(BaseExpression::class); - $greaterExpression->shouldReceive('evaluate')->once()->with(2, 1); - - $this->expressionFactory - ->shouldReceive('createFromOperator') - ->twice() - ->with(m::type(Token\BaseToken::class)) - ->andReturn( - $equalExpression, - $greaterExpression - ); + $this->tokenStream->shouldReceive('getStream')->once()->andReturn($tokenIterator); $this->assertSame('(1)&1', $this->parser->parse('(1=="1")&&2>1 // true dat!')); } diff --git a/tests/unit/Token/TokenFactoryTest.php b/tests/unit/Token/TokenFactoryTest.php index fcf59f3..688dca3 100755 --- a/tests/unit/Token/TokenFactoryTest.php +++ b/tests/unit/Token/TokenFactoryTest.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\Token; @@ -11,17 +11,18 @@ use nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\TokenStream\Token\TokenEqualStrict; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; final class TokenFactoryTest extends TestCase { - private Token\TokenFactory $tokenFactory; + private readonly Token\TokenFactory $tokenFactory; protected function setUp(): void { $this->tokenFactory = new Token\TokenFactory(); } - /** @test */ + #[Test] public function simpleTypeReturnsCorrectInstance(): void { $this->assertInstanceOf(Token\TokenNull::class, $this->tokenFactory->createFromPHPType(null)); @@ -32,7 +33,7 @@ public function simpleTypeReturnsCorrectInstance(): void $this->assertInstanceOf(Token\TokenArray::class, $this->tokenFactory->createFromPHPType([1, 2])); } - /** @test */ + #[Test] public function unsupportedTypeThrowsException(): void { $this->expectException(ParserException::class); @@ -41,17 +42,9 @@ public function unsupportedTypeThrowsException(): void $this->tokenFactory->createFromPHPType(tmpfile()); } - /** @test */ - public function givenAnInvalidTokenNameItShouldThrowAnException(): void - { - $this->expectException(ParserException::class); - - $this->tokenFactory->createFromTokenName('betrunken'); - } - - /** @test */ + #[Test] public function givenAValidTokenNameItShouldReturnItsCorrespondingClassName(): void { - $this->assertSame(TokenEqualStrict::class, $this->tokenFactory->createFromTokenName(Token\Token::EQUAL_STRICT)); + $this->assertInstanceOf(TokenEqualStrict::class, $this->tokenFactory->createFromToken(Token\Token::EQUAL_STRICT, ['EqualStrict' => '==='], 0)); } } diff --git a/tests/unit/TokenStream/ASTTest.php b/tests/unit/TokenStream/ASTTest.php deleted file mode 100755 index 04f2281..0000000 --- a/tests/unit/TokenStream/ASTTest.php +++ /dev/null @@ -1,89 +0,0 @@ - - */ -namespace nicoSWD\Rule\tests\unit\TokenStream; - -use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use Mockery\MockInterface; -use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; -use nicoSWD\Rule\Grammar\Grammar; -use nicoSWD\Rule\Tokenizer\TokenizerInterface; -use nicoSWD\Rule\TokenStream\AST; -use nicoSWD\Rule\TokenStream\Exception\UndefinedFunctionException; -use nicoSWD\Rule\TokenStream\Node\BaseNode; -use nicoSWD\Rule\TokenStream\Token\BaseToken; -use nicoSWD\Rule\TokenStream\Token\TokenFactory; -use nicoSWD\Rule\TokenStream\TokenStreamFactory; -use nicoSWD\Rule\TokenStream\CallableUserMethodFactory; -use PHPUnit\Framework\TestCase; - -final class ASTTest extends TestCase -{ - use MockeryPHPUnitIntegration; - - private TokenizerInterface|MockInterface $tokenizer; - private TokenFactory|MockInterface $tokenFactory; - private TokenStreamFactory|MockInterface $tokenStreamFactory; - private AST $ast; - private CallableUserMethodFactory $userMethodFactory; - - protected function setUp(): void - { - $this->tokenizer = \Mockery::mock(TokenizerInterface::class); - $this->tokenFactory = \Mockery::mock(TokenFactory::class); - $this->tokenStreamFactory = \Mockery::mock(TokenStreamFactory::class); - $this->userMethodFactory = new CallableUserMethodFactory(); - - $this->ast = new AST( - $this->tokenizer, - $this->tokenFactory, - $this->tokenStreamFactory, - $this->userMethodFactory - ); - } - - /** @test */ - public function givenAFunctionNameWhenValidItShouldReturnTheCorrespondingFunction(): void - { - $grammar = \Mockery::mock(Grammar::class); - $grammar->shouldReceive('getInternalFunctions')->once()->andReturn(['test' => TestFunc::class]); - $this->tokenizer->shouldReceive('getGrammar')->once()->andReturn($grammar); - - /** @var BaseToken $result */ - $result = $this->ast->getFunction('test')->call(\Mockery::mock(BaseNode::class)); - - $this->assertSame(234, $result->getValue()); - } - - /** @test */ - public function givenAFunctionNameWhenItDoesNotImplementTheInterfaceItShouldThrowAnException(): void - { - $this->expectExceptionMessage(sprintf( - 'stdClass must be an instance of %s', - CallableUserFunctionInterface::class - )); - - $grammar = \Mockery::mock(Grammar::class); - $grammar->shouldReceive('getInternalFunctions')->once()->andReturn(['test' => \stdClass::class]); - $this->tokenizer->shouldReceive('getGrammar')->once()->andReturn($grammar); - - $this->ast->getFunction('test')->call(\Mockery::mock(BaseNode::class)); - } - - /** @test */ - public function givenAFunctionNameNotDefinedItShouldThrowAnException(): void - { - $this->expectException(UndefinedFunctionException::class); - $this->expectExceptionMessage('pineapple_pizza'); - - $grammar = \Mockery::mock(Grammar::class); - $grammar->shouldReceive('getInternalFunctions')->once()->andReturn([]); - $this->tokenizer->shouldReceive('getGrammar')->once()->andReturn($grammar); - - $this->ast->getFunction('pineapple_pizza')->call(\Mockery::mock(BaseNode::class)); - } -} diff --git a/tests/unit/TokenStream/CallableUserMethodTest.php b/tests/unit/TokenStream/CallableUserMethodTest.php index fe18bdd..349c218 100755 --- a/tests/unit/TokenStream/CallableUserMethodTest.php +++ b/tests/unit/TokenStream/CallableUserMethodTest.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\TokenStream; @@ -12,11 +12,12 @@ use nicoSWD\Rule\TokenStream\Token\TokenFactory; use nicoSWD\Rule\TokenStream\Token\TokenObject; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; use stdClass; final class CallableUserMethodTest extends TestCase { - /** @test */ + #[Test] public function givenAnObjectWithAPublicPropertyItShouldBeAccessible(): void { $object = new stdClass(); @@ -25,7 +26,7 @@ public function givenAnObjectWithAPublicPropertyItShouldBeAccessible(): void $this->assertSame(123, $this->callMethod($object, 'my_test')->getValue()); } - /** @test */ + #[Test] public function givenAnObjectWithAPublicWhenMethodMatchingItShouldBeUsed(): void { $object = new class { @@ -38,7 +39,7 @@ public function my_test() $this->assertSame(123, $this->callMethod($object, 'my_test')->getValue()); } - /** @test */ + #[Test] public function givenAnObjectWithAPublicWhenMethodNameWithIsPrefixMatchesItShouldBeUsed(): void { $object = new class { @@ -57,7 +58,7 @@ public function isMyTest() $this->assertSame(456, $this->callMethod($object, 'myTest')->getValue()); } - /** @test */ + #[Test] public function givenAnObjectWithAPublicWhenMethodNameWithGetPrefixMatchesItShouldBeUsed(): void { $object = new class { diff --git a/tests/unit/TokenStream/TestFunc.php b/tests/unit/TokenStream/TestFunc.php index 966ab13..925e891 100755 --- a/tests/unit/TokenStream/TestFunc.php +++ b/tests/unit/TokenStream/TestFunc.php @@ -3,7 +3,7 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\TokenStream; diff --git a/tests/unit/TokenStream/Token/BaseTokenTest.php b/tests/unit/TokenStream/Token/BaseTokenTest.php index 0033d3d..95f9ef6 100755 --- a/tests/unit/TokenStream/Token/BaseTokenTest.php +++ b/tests/unit/TokenStream/Token/BaseTokenTest.php @@ -3,14 +3,17 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ -namespace nicoSWD\Rule\tests\unit\TokenStream; +namespace nicoSWD\Rule\tests\unit\TokenStream\Token; +use ArrayIterator; use Mockery\MockInterface; use nicoSWD\Rule\TokenStream\Token\BaseToken; use nicoSWD\Rule\TokenStream\Token\TokenType; +use nicoSWD\Rule\TokenStream\TokenIterator; use nicoSWD\Rule\TokenStream\TokenStream; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; final class BaseTokenTest extends TestCase @@ -21,134 +24,45 @@ final class BaseTokenTest extends TestCase protected function setUp(): void { $this->token = new class('&&', 1337) extends BaseToken { - public function getType(): int + public function getType(): TokenType { return TokenType::LOGICAL; } }; } - /** @test */ - public function offset(): void + #[Test] + public function givenATokenWhenGettingOffsetItShouldReturnTheExpectedOffset(): void { $this->assertSame(1337, $this->token->getOffset()); } - /** @test */ - public function getValue(): void + #[Test] + public function givenATokenWhenGettingValueItShouldReturnTheExpectedValue(): void { $this->assertSame('&&', $this->token->getValue()); } - /** @test */ - public function getOriginalValue(): void + #[Test] + public function givenATokenWhenGettingOriginalValueItShouldReturnTheExpectedOriginalValue(): void { $this->assertSame('&&', $this->token->getOriginalValue()); } - /** @test */ - public function createNode(): void + #[Test] + public function givenATokenIteratorWhenCreatingNodeItShouldReturnTheSameToken(): void { /** @var TokenStream|MockInterface $tokenStream */ $tokenStream = \Mockery::mock(TokenStream::class); - $this->assertSame($this->token, $this->token->createNode($tokenStream)); + $iterator = new TokenIterator(new ArrayIterator([]), $tokenStream); + + $this->assertSame($this->token, $this->token->createNode($iterator)); } - /** @test */ - public function isOfType(): void + #[Test] + public function givenALogicalTokenWhenCheckingTypeItShouldReturnTrueForLogicalAndFalseForComma(): void { $this->assertTrue($this->token->isOfType(TokenType::LOGICAL)); $this->assertFalse($this->token->isOfType(TokenType::COMMA)); } - - /** @test */ - public function isValue(): void - { - $token = new class('123', 1337) extends BaseToken { - public function getType(): int - { - return TokenType::VALUE; - } - }; - - $this->assertTrue($token->isValue()); - } - - /** @test */ - public function isWhitespace(): void - { - $token = new class(' ', 1337) extends BaseToken { - public function getType(): int - { - return TokenType::SPACE; - } - }; - - $this->assertTrue($token->isWhitespace()); - } - - /** @test */ - public function isMethod(): void - { - $token = new class('.derp(', 1337) extends BaseToken { - public function getType(): int - { - return TokenType::METHOD; - } - }; - - $this->assertTrue($token->isMethod()); - } - - /** @test */ - public function isComma(): void - { - $token = new class(',', 1337) extends BaseToken { - public function getType(): int - { - return TokenType::COMMA; - } - }; - - $this->assertTrue($token->isComma()); - } - - /** @test */ - public function isOperator(): void - { - $token = new class('>', 1337) extends BaseToken { - public function getType(): int - { - return TokenType::OPERATOR; - } - }; - - $this->assertTrue($token->isOperator()); - } - - /** @test */ - public function isLogical(): void - { - $token = new class('&&', 1337) extends BaseToken { - public function getType(): int - { - return TokenType::LOGICAL; - } - }; - - $this->assertTrue($token->isLogical()); - } - - /** @test */ - public function isParenthesis(): void - { - $token = new class('(', 1337) extends BaseToken { - public function getType(): int - { - return TokenType::PARENTHESIS; - } - }; - - $this->assertTrue($token->isParenthesis()); - } } diff --git a/tests/unit/TokenStream/TokenIteratorTest.php b/tests/unit/TokenStream/TokenIteratorTest.php new file mode 100755 index 0000000..cd3421d --- /dev/null +++ b/tests/unit/TokenStream/TokenIteratorTest.php @@ -0,0 +1,133 @@ + + */ +namespace nicoSWD\Rule\tests\unit\TokenStream; + +use ArrayIterator; +use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; +use Mockery\MockInterface; +use nicoSWD\Rule\Grammar\CallableFunction; +use nicoSWD\Rule\Parser\Exception\ParserException; +use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\Exception\UndefinedFunctionException; +use nicoSWD\Rule\TokenStream\Exception\UndefinedMethodException; +use nicoSWD\Rule\TokenStream\Exception\UndefinedVariableException; +use nicoSWD\Rule\TokenStream\Token\BaseToken; +use nicoSWD\Rule\TokenStream\Token\TokenFunction; +use nicoSWD\Rule\TokenStream\Token\TokenMethod; +use nicoSWD\Rule\TokenStream\Token\TokenString; +use nicoSWD\Rule\TokenStream\Token\TokenVariable; +use nicoSWD\Rule\TokenStream\TokenIterator; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; + +final class TokenIteratorTest extends TestCase +{ + use MockeryPHPUnitIntegration; + + private ArrayIterator|MockInterface $stack; + private TokenStream|MockInterface $tokenStream; + private TokenIterator $tokenIterator; + + protected function setUp(): void + { + $this->stack = \Mockery::mock(ArrayIterator::class); + $this->tokenStream = \Mockery::mock(TokenStream::class); + + $this->tokenIterator = new TokenIterator($this->stack, $this->tokenStream); + } + + #[Test] + public function givenAStackWhenNotEmptyItShouldBeIterable() + { + $this->stack->shouldReceive('rewind'); + $this->stack->shouldReceive('valid')->andReturn(true, true, true, false); + $this->stack->shouldReceive('key')->andReturn(1, 2, 3); + $this->stack->shouldReceive('next'); + $this->stack->shouldReceive('seek'); + $this->stack->shouldReceive('current')->times(5)->andReturn( + new TokenString('a'), + new TokenMethod('.foo('), + new TokenString('b') + ); + + foreach ($this->tokenIterator as $value) { + $this->assertInstanceOf(BaseToken::class, $value); + } + } + + #[Test] + public function givenATokenStackItShouldBeAccessibleViaGetter() + { + $this->assertInstanceOf(ArrayIterator::class, $this->tokenIterator->getStack()); + } + + #[Test] + public function givenAVariableNameWhenFoundItShouldReturnItsValue() + { + $this->tokenStream->shouldReceive('getVariable')->once()->with('foo')->andReturn(new TokenVariable('bar')); + + $token = $this->tokenIterator->getVariable('foo'); + $this->assertInstanceOf(TokenVariable::class, $token); + } + + #[Test] + public function givenAVariableNameWhenNotFoundItShouldThrowAnException() + { + $this->expectException(ParserException::class); + + $this->tokenStream->shouldReceive('getVariable')->once()->with('foo')->andThrow(new UndefinedVariableException()); + $this->stack->shouldReceive('current')->once()->andReturn(new TokenVariable('nope')); + + $this->tokenIterator->getVariable('foo'); + } + + #[Test] + public function givenAFunctionNameWhenFoundItShouldACallableClosure() + { + $this->tokenStream->shouldReceive('getFunction')->once()->with('foo')->andReturn(fn () => 42); + + $function = $this->tokenIterator->getFunction('foo'); + $this->assertSame(42, $function()); + } + + #[Test] + public function givenAFunctionNameWhenNotFoundItShouldThrowAnException() + { + $this->expectException(ParserException::class); + + $this->tokenStream->shouldReceive('getFunction')->once()->with('foo')->andThrow(new UndefinedFunctionException()); + $this->stack->shouldReceive('current')->once()->andReturn(new TokenFunction('nope(')); + + $this->tokenIterator->getFunction('foo'); + } + + #[Test] + public function givenAMethodNameWhenFoundItShouldReturnAnInstanceOfCallableFunction() + { + $token = new TokenString('bar'); + $callableFunction = \Mockery::mock(CallableFunction::class); + + $this->tokenStream->shouldReceive('getMethod')->once()->with('foo', $token)->andReturn($callableFunction); + + $method = $this->tokenIterator->getMethod('foo', $token); + + $this->assertInstanceOf(CallableFunction::class, $method); + } + + #[Test] + public function givenAMethodNameWhenNotFoundItShouldThrowAnException() + { + $this->expectException(ParserException::class); + + $token = new TokenString('bar'); + $this->tokenStream->shouldReceive('getMethod')->once()->with('foo', $token)->andThrow(new UndefinedMethodException()); + $this->stack->shouldReceive('current')->once()->andReturn(new TokenFunction('bar')); + + $this->tokenIterator->getMethod('foo', $token); + } +} diff --git a/tests/unit/TokenStream/TokenStreamTest.php b/tests/unit/TokenStream/TokenStreamTest.php index 757a634..7b2248c 100755 --- a/tests/unit/TokenStream/TokenStreamTest.php +++ b/tests/unit/TokenStream/TokenStreamTest.php @@ -3,130 +3,162 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\TokenStream; use ArrayIterator; +use Iterator; +use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use Mockery\MockInterface; -use nicoSWD\Rule\Grammar\CallableFunction; -use nicoSWD\Rule\Parser\Exception\ParserException; -use nicoSWD\Rule\TokenStream\AST; +use nicoSWD\Rule\Grammar\CallableUserFunctionInterface; +use nicoSWD\Rule\Grammar\Grammar; +use nicoSWD\Rule\Grammar\InternalFunction; +use nicoSWD\Rule\Tokenizer\Tokenizer; +use nicoSWD\Rule\Tokenizer\TokenizerInterface; +use nicoSWD\Rule\TokenStream\CallableUserMethodFactoryInterface; +use nicoSWD\Rule\TokenStream\TokenStream; use nicoSWD\Rule\TokenStream\Exception\UndefinedFunctionException; -use nicoSWD\Rule\TokenStream\Exception\UndefinedMethodException; -use nicoSWD\Rule\TokenStream\Exception\UndefinedVariableException; +use nicoSWD\Rule\TokenStream\Node\BaseNode; use nicoSWD\Rule\TokenStream\Token\BaseToken; -use nicoSWD\Rule\TokenStream\Token\TokenFunction; -use nicoSWD\Rule\TokenStream\Token\TokenMethod; -use nicoSWD\Rule\TokenStream\Token\TokenString; -use nicoSWD\Rule\TokenStream\Token\TokenVariable; -use nicoSWD\Rule\TokenStream\TokenStream; +use nicoSWD\Rule\TokenStream\Token\TokenFactory; +use nicoSWD\Rule\TokenStream\TokenIteratorFactory; +use nicoSWD\Rule\TokenStream\CallableUserMethodFactory; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\Test; +use stdClass; final class TokenStreamTest extends TestCase { use MockeryPHPUnitIntegration; - - private ArrayIterator|MockInterface $stack; - private AST|MockInterface $ast; - private TokenStream $tokenStream; + + private readonly TokenStream $tokenStream; + private readonly TokenFactory|MockInterface $tokenFactory; + private readonly CallableUserMethodFactory $userMethodFactory; + private readonly TokenIteratorFactory $tokenStreamFactory; protected function setUp(): void { - $this->stack = \Mockery::mock(ArrayIterator::class); - $this->ast = \Mockery::mock(AST::class); - - $this->tokenStream = new TokenStream($this->stack, $this->ast); + $this->tokenFactory = Mockery::mock(TokenFactory::class); + $this->userMethodFactory = new CallableUserMethodFactory(); + $this->tokenStreamFactory = new TokenIteratorFactory(); } - /** @test */ - public function givenAStackWhenNotEmptyItShouldBeIterable() + #[Test] + public function givenAFunctionNameWhenValidItShouldReturnTheCorrespondingFunction(): void { - $this->stack->shouldReceive('rewind'); - $this->stack->shouldReceive('valid')->andReturn(true, true, true, false); - $this->stack->shouldReceive('key')->andReturn(1, 2, 3); - $this->stack->shouldReceive('next'); - $this->stack->shouldReceive('seek'); - $this->stack->shouldReceive('current')->times(5)->andReturn( - new TokenString('a'), - new TokenMethod('.foo('), - new TokenString('b') + $grammar = $this->createGrammarWithInternalFunctions([new InternalFunction('test', TestFunc::class)]); + $tokenizer = new Tokenizer($grammar, $this->tokenFactory); + + $tokenStream = new TokenStream( + $tokenizer, + $this->tokenFactory, + $this->tokenStreamFactory, + $this->userMethodFactory ); - foreach ($this->tokenStream as $key => $value) { - $this->assertInstanceOf(BaseToken::class, $value); - } - } + /** @var BaseToken $result */ + $result = $tokenStream->getFunction('test')->call(Mockery::mock(BaseNode::class)); - /** @test */ - public function givenATokenStackItShouldBeAccessibleViaGetter() - { - $this->assertInstanceOf(ArrayIterator::class, $this->tokenStream->getStack()); + $this->assertSame(234, $result->getValue()); } - /** @test */ - public function givenAVariableNameWhenFoundItShouldReturnItsValue() + #[Test] + public function givenAFunctionNameWhenItDoesNotImplementTheInterfaceItShouldThrowAnException(): void { - $this->ast->shouldReceive('getVariable')->once()->with('foo')->andReturn(new TokenVariable('bar')); + $this->expectExceptionMessage(sprintf( + 'stdClass must be an instance of %s', + CallableUserFunctionInterface::class + )); + + $grammar = $this->createGrammarWithInternalFunctions([new InternalFunction('test', stdClass::class)]); + $tokenizer = new Tokenizer($grammar, $this->tokenFactory); + + $tokenStream = new TokenStream( + $tokenizer, + $this->tokenFactory, + $this->tokenStreamFactory, + $this->userMethodFactory + ); - $token = $this->tokenStream->getVariable('foo'); - $this->assertInstanceOf(TokenVariable::class, $token); + $tokenStream->getFunction('test')->call(Mockery::mock(BaseNode::class)); } - /** @test */ - public function givenAVariableNameWhenNotFoundItShouldThrowAnException() + #[Test] + public function givenAFunctionNameNotDefinedItShouldThrowAnException(): void { - $this->expectException(ParserException::class); + $this->expectException(UndefinedFunctionException::class); + $this->expectExceptionMessage('pineapple_pizza'); - $this->ast->shouldReceive('getVariable')->once()->with('foo')->andThrow(new UndefinedVariableException()); - $this->stack->shouldReceive('current')->once()->andReturn(new TokenVariable('nope')); - - $this->tokenStream->getVariable('foo'); - } + $tokenizer = $this->createDummyTokenizer(); + $userMethodFactory = $this->createCallableUserMethodFactory(); - /** @test */ - public function givenAFunctionNameWhenFoundItShouldACallableClosure() - { - $this->ast->shouldReceive('getFunction')->once()->with('foo')->andReturn(fn () => 42); + $tokenStream = new TokenStream( + $tokenizer, + new TokenFactory(), + $this->tokenStreamFactory, + $userMethodFactory, + ); - $function = $this->tokenStream->getFunction('foo'); - $this->assertSame(42, $function()); + $tokenStream->getFunction('pineapple_pizza'); } - /** @test */ - public function givenAFunctionNameWhenNotFoundItShouldThrowAnException() + private function createDummyTokenizer(): TokenizerInterface { - $this->expectException(ParserException::class); - - $this->ast->shouldReceive('getFunction')->once()->with('foo')->andThrow(new UndefinedFunctionException()); - $this->stack->shouldReceive('current')->once()->andReturn(new TokenFunction('nope(')); - - $this->tokenStream->getFunction('foo'); + return new class($this->createGrammarWithInternalFunctions()) extends TokenizerInterface { + public function __construct( + public Grammar $grammar, + ) { + } + + public function tokenize(string $string): Iterator + { + return new ArrayIterator([]); + } + }; } - /** @test */ - public function givenAMethodNameWhenFoundItShouldReturnAnInstanceOfCallableFunction() + private function createGrammarWithInternalFunctions(array $internalFunctions = []): Grammar { - $token = new TokenString('bar'); - $callableFunction = \Mockery::mock(CallableFunction::class); - - $this->ast->shouldReceive('getMethod')->once()->with('foo', $token)->andReturn($callableFunction); - - $method = $this->tokenStream->getMethod('foo', $token); - - $this->assertInstanceOf(CallableFunction::class, $method); + return new class($internalFunctions) extends Grammar { + public function __construct( + private readonly array $internalFunctions, + ) { + } + + public function getDefinition(): array + { + return []; + } + + public function getInternalFunctions(): array + { + return $this->internalFunctions; + } + + public function getInternalMethods(): array + { + return []; + } + }; } - /** @test */ - public function givenAMethodNameWhenNotFoundItShouldThrowAnException() + private function createCallableUserMethodFactory(): CallableUserMethodFactoryInterface { - $this->expectException(ParserException::class); - - $token = new TokenString('bar'); - $this->ast->shouldReceive('getMethod')->once()->with('foo', $token)->andThrow(new UndefinedMethodException()); - $this->stack->shouldReceive('current')->once()->andReturn(new TokenFunction('bar')); - - $this->tokenStream->getMethod('foo', $token); + return new class implements CallableUserMethodFactoryInterface { + public function create( + BaseToken $token, + TokenFactory $tokenFactory, + string $methodName, + ): CallableUserFunctionInterface { + return new class implements CallableUserFunctionInterface { + public function call(?BaseToken ...$param): BaseToken + { + return Mockery::mock(BaseToken::class); + } + }; + } + }; } } diff --git a/tests/unit/Tokenizer/TokenizerTest.php b/tests/unit/Tokenizer/TokenizerTest.php index 6d93527..89b0cfe 100755 --- a/tests/unit/Tokenizer/TokenizerTest.php +++ b/tests/unit/Tokenizer/TokenizerTest.php @@ -3,24 +3,26 @@ /** * @license http://opensource.org/licenses/mit-license.php MIT * @link https://github.com/nicoSWD - * @author Nicolas Oelgart + * @author Nicolas Oelgart */ namespace nicoSWD\Rule\tests\unit\Tokenizer; +use nicoSWD\Rule\Grammar\Definition; use nicoSWD\Rule\Grammar\Grammar; use nicoSWD\Rule\TokenStream\Token; use nicoSWD\Rule\Tokenizer\Tokenizer; +use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; final class TokenizerTest extends TestCase { - /** @test */ + #[Test] public function givenAGrammarWithCollidingRegexItShouldTakeThePriorityIntoAccount(): void { $tokens = $this->tokenizeWithGrammar('yes somevar', [ - [Token\Token::BOOL_TRUE, '\byes\b', 20], - [Token\Token::VARIABLE, '\b[a-z]+\b', 10], - [Token\Token::SPACE, '\s+', 5], + new Definition(Token\Token::BOOL_TRUE, '\byes\b', 20), + new Definition(Token\Token::VARIABLE, '\b[a-z]+\b', 10), + new Definition(Token\Token::SPACE, '\s+', 5), ]); $this->assertCount(3, $tokens); @@ -38,13 +40,13 @@ public function givenAGrammarWithCollidingRegexItShouldTakeThePriorityIntoAccoun $this->assertInstanceOf(Token\TokenVariable::class, $tokens[2]); } - /** @test */ + #[Test] public function givenAGrammarWithCollidingRegexWhenPriorityIsWrongItShouldNeverMatchTheOneWithLowerPriority(): void { $tokens = $this->tokenizeWithGrammar('somevar yes', [ - [Token\Token::VARIABLE, '\b[a-z]+\b', 20], - [Token\Token::BOOL_TRUE, '\byes\b', 10], - [Token\Token::SPACE, '\s+', 5], + new Definition(Token\Token::VARIABLE, '\b[a-z]+\b', 20), + new Definition(Token\Token::BOOL_TRUE, '\byes\b', 10), + new Definition(Token\Token::SPACE, '\s+', 5), ]); $this->assertCount(3, $tokens); @@ -62,16 +64,6 @@ public function givenAGrammarWithCollidingRegexWhenPriorityIsWrongItShouldNeverM $this->assertInstanceOf(Token\TokenVariable::class, $tokens[2]); } - /** @test */ - public function givenAGrammarItShouldBeAvailableThroughGetter(): void - { - $grammar = $this->getTokenizer([[Token\Token::BOOL_TRUE, '\byes\b', 10]])->getGrammar(); - - $this->assertInstanceOf(Grammar::class, $grammar); - $this->assertIsArray($grammar->getDefinition()); - $this->assertCount(1, $grammar->getDefinition()); - } - /** @return Token\BaseToken[] */ private function tokenizeWithGrammar(string $rule, array $definition): array { @@ -89,17 +81,24 @@ private function tokenizeWithGrammar(string $rule, array $definition): array private function getTokenizer(array $definition): Tokenizer { $grammar = new class($definition) extends Grammar { - private array $definition; - - public function __construct(array $definition) + public function __construct(private readonly array $definition) { - $this->definition = $definition; } public function getDefinition(): array { return $this->definition; } + + public function getInternalFunctions(): array + { + return []; + } + + public function getInternalMethods(): array + { + return []; + } }; return new Tokenizer($grammar, new Token\TokenFactory());