Completed
Push — master ( aee4a1...066343 )
by Nico
01:30
created

Parser   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 24
lcom 3
cbo 8
dl 0
loc 151
ccs 79
cts 79
cp 1
rs 10
c 0
b 0
f 0

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 1
C parse() 0 32 8
A assignVariables() 0 4 1
A assignVariableValueFromToken() 0 10 2
A assignOperator() 0 11 3
A parseExpression() 0 11 3
A registerFunctionClass() 0 19 2
A registerToken() 0 4 1
A getFunction() 0 11 2
A registerFunction() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @license     http://opensource.org/licenses/mit-license.php MIT
7
 * @link        https://github.com/nicoSWD
8
 * @author      Nicolas Oelgart <[email protected]>
9
 */
10
namespace nicoSWD\Rules;
11
12
use Closure;
13
use InvalidArgumentException;
14
use nicoSWD\Rules\Core\CallableUserFunction;
15
use nicoSWD\Rules\Exceptions\ParserException;
16
use nicoSWD\Rules\Grammar\JavaScript\Functions\ParseFloat;
17
use nicoSWD\Rules\Grammar\JavaScript\Functions\ParseInt;
18
use nicoSWD\Rules\Tokens\BaseToken;
19
20
class Parser
21
{
22
    /** @var array */
23
    public $variables = [];
24
25
    /** @var null|mixed[] */
26
    private $values = null;
27
28
    /** @var null|BaseToken */
29
    private $operator =  null;
30
31
    /** @var TokenizerInterface */
32
    private $tokenizer;
33
34
    /** @var Expressions\Factory */
35
    private $expressionFactory;
36
37
    /** @var Callable[] */
38
    private $userDefinedFunctions = [];
39
40
    /** @var RuleGenerator */
41
    private $ruleGenerator;
42
43 230
    public function __construct(
44
        TokenizerInterface $tokenizer,
45
        Expressions\Factory $expressionFactory,
46
        RuleGenerator $ruleGenerator
47
    ) {
48 230
        $this->tokenizer = $tokenizer;
49 230
        $this->expressionFactory = $expressionFactory;
50 230
        $this->ruleGenerator = $ruleGenerator;
51
52 230
        $this->registerFunctionClass(ParseInt::class);
53 230
        $this->registerFunctionClass(ParseFloat::class);
54 230
    }
55
56 224
    public function parse(string $rule): string
57
    {
58 224
        $this->ruleGenerator->clear();
59 224
        $this->operator = null;
60 224
        $this->values = null;
61
62 224
        foreach (new AST($this->tokenizer->tokenize($rule), $this) as $token) {
63 186
            switch ($token->getType()) {
64 186
                case TokenType::VALUE:
65 182
                    $this->assignVariableValueFromToken($token);
66 182
                    break;
67 186
                case TokenType::LOGICAL:
68 36
                    $this->ruleGenerator->addLogical($token);
69 36
                    continue 2;
70 186
                case TokenType::PARENTHESES:
71 22
                    $this->ruleGenerator->addParentheses($token);
72 20
                    continue 2;
73 186
                case TokenType::OPERATOR:
74 182
                    $this->assignOperator($token);
75 180
                    continue 2;
76 184
                case TokenType::COMMENT:
77 184
                case TokenType::SPACE:
78 182
                    continue 2;
79
                default:
80 6
                    throw Exceptions\ParserException::unknownToken($token);
81
            }
82
83 182
            $this->parseExpression();
84
        }
85
86 164
        return $this->ruleGenerator->get();
87
    }
88
89 224
    public function assignVariables(array $variables)
90
    {
91 224
        $this->variables = $variables;
92 224
    }
93
94 182
    protected function assignVariableValueFromToken(BaseToken $token)
95
    {
96 182
        $this->ruleGenerator->flipOperatorRequired($token);
97
98 182
        if (!isset($this->values)) {
99 182
            $this->values = [$token->getValue()];
100
        } else {
101 178
            $this->values[] = $token->getValue();
102
        }
103 182
    }
104
105 182
    protected function assignOperator(BaseToken $token)
106
    {
107 182
        if (isset($this->operator)) {
108 2
            throw Exceptions\ParserException::unexpectedToken($token);
109 182
        } elseif (!isset($this->values)) {
110 2
            throw Exceptions\ParserException::incompleteExpression($token);
111
        }
112
113 180
        $this->ruleGenerator->operatorRequired(false);
114 180
        $this->operator = $token;
115 180
    }
116
117 182
    protected function parseExpression()
118
    {
119 182
        if (!isset($this->operator) || count($this->values) <> 2) {
120 182
            return;
121
        }
122
123 178
        $expression = $this->expressionFactory->createFromOperator($this->operator);
124 178
        $this->ruleGenerator->addBoolean($expression->evaluate(...$this->values));
0 ignored issues
show
Bug introduced by
The call to evaluate() misses a required argument $rightValue.

This check looks for function calls that miss required arguments.

Loading history...
125
126 176
        unset($this->operator, $this->values);
127 176
    }
128
129 230
    public function registerFunctionClass(string $className)
130
    {
131
        /** @var CallableUserFunction $function */
132 230
        $function = new $className();
133
134 230
        if (!$function instanceof CallableUserFunction) {
135 2
            throw new InvalidArgumentException(
136 2
                sprintf(
137 2
                    "%s must be an instance of %s",
138 2
                    $className,
139 2
                    CallableUserFunction::class
140
                )
141
            );
142
        }
143
144 230
        $this->registerFunction($function->getName(), function () use ($function): BaseToken {
145 24
            return $function->call(...func_get_args());
0 ignored issues
show
Documentation introduced by
func_get_args() is of type array, but the function expects a object<nicoSWD\Rules\Tokens\BaseToken>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
146 230
        });
147 230
    }
148
149 2
    public function registerToken(string $token, string $regex, int $priority = 10)
150
    {
151 2
        $this->tokenizer->registerToken($token, $regex, $priority);
152 2
    }
153
154 28
    public function getFunction(string $name): Closure
155
    {
156 28
        if (!isset($this->userDefinedFunctions[$name])) {
157 4
            throw new Exceptions\ParserException(sprintf(
158 4
                '%s is not defined',
159 4
                $name
160
            ));
161
        }
162
163 24
        return $this->userDefinedFunctions[$name];
164
    }
165
166 230
    private function registerFunction(string $name, Closure $callback)
167
    {
168 230
        $this->userDefinedFunctions[$name] = $callback;
169 230
    }
170
}
171