Completed
Push — master ( 04e24a...c0588a )
by Martijn
03:54 queued 38s
created

AbstractRuleset::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 3
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Vanderlee\Comprehend\Builder;
4
5
use Vanderlee\Comprehend\Core\ArgumentsTrait;
6
use Vanderlee\Comprehend\Core\Context;
7
use Vanderlee\Comprehend\Match\Success;
8
use Vanderlee\Comprehend\Parser\Parser;
9
10
/**
11
 * Description of abstract Ruleset
12
 *
13
 * @author Martijn
14
 */
15
abstract class AbstractRuleset extends Parser
16
{
17
    use ArgumentsTrait;
18
19
    /**
20
     * Constant to specify default parser for this ruleset, which is called when Ruleset is used as a Parser.
21
     */
22
    const ROOT = 'ROOT';
23
24
    /**
25
     * Name of this ruleset. Intended for use with the standard library rulesets
26
     *
27
     * @var string
28
     */
29
    protected static $name = null;
30
31
    /**
32
     * Parser to use as the default parser used when calling this Ruleset as a Parser.
33
     *
34
     * @var null|Parser
35
     */
36
    private $parser = null;
37
38
    /**
39
     * List of reserved parser names. These are used as methods instead.
40
     *
41
     * @var string[]
42
     */
43
    private static $reserved = [
44
        'define',
45
        'defined',
46
        'undefine'
47
    ];
48
49
    /**
50
     * @var Definition[]|Parser[]|callable[]
51
     */
52
    private static $staticRules = [];
53
54
    /**
55
     * @var Definition[]|Parser[]|callable[]
56
     */
57
    protected $instanceRules = [];
58
59
    /**
60
     * Ruleset constructor, defining initial instance rules.
61
     *
62
     * @param string|array $key Either a key of an initial rule to define or a [ key : definition ] array
63
     * @param null|string $definition Definition of the initial rule or `null` if `$key` is an array
64
     * @param null|string $name optional identifier for this ruleset
65
     */
66 34
    public function __construct($key = null, $definition = null, $name = null)
67
    {
68 34
        if ($key !== null) {
69 14
            self::defineRule($this->instanceRules, $key, $definition);
70
        }
71
72 34
        self::$name = $name;
73 34
    }
74
75
    /**
76
     * Set a definition
77
     *
78
     * @param Definition[]|Parser[]|callable[] $rules
79
     * @param string $key
80
     * @param Definition|Parser|callable $definition
81
     * @throws \Exception
82
     */
83 36
    protected static function setRule(&$rules, $key, $definition)
84
    {
85 36
        if (isset($rules[$key])) {
86 15
            $rule = &$rules[$key];
87 15
            if ($rule instanceof Definition) {
88
                switch (true) {
89 15
                    case $definition instanceof Definition:
90 1
                        $rule->generator  = $definition->generator;
91 1
                        $rule->validators = $definition->validators;
92 1
                        return;
93
94 14
                    case $definition instanceof Parser:
95 10
                        $rule->generator = $definition;
96 10
                        return;
97
98 12
                    case is_callable($definition):
99 2
                        $rule->generator = $definition;
100 2
                        return;
101
102 10
                    case is_array($definition) || is_string($definition) || is_int($definition):
103
                        // S-C-S Array syntax
104 9
                        $rule->generator = self::getArgument($definition);
105 9
                        return;
106
                }
107
108 1
                throw new \RuntimeException(sprintf('Cannot redefine `%2$s` using definition type `%1$s`',
109 1
                    is_object($definition)
110
                        ? get_class($definition)
111 1
                        : gettype($definition), $key));
112
            }
113
        }
114
115 29
        $rules[$key] = $definition;
116 29
    }
117
118 20
    protected static function defineRule(&$rules, $names, $definition = null)
119
    {
120 20
        if (is_array($names)) {
121 9
            foreach ($names as $key => $value) {
122 9
                self::defineRule($rules, $key, $value);
123
            }
124 9
            return;
125
        }
126
127 20
        if (in_array($names, self::$reserved)) {
128 1
            throw new \RuntimeException("Cannot define reserved name `{$names}`");
129
        }
130
131 19
        self::setRule($rules, $names, $definition);
132 19
    }
133
134
    /**
135
     * Check if a specified name is defined in the rules map provided
136
     *
137
     * @param $rules
138
     * @param $names
139
     * @return bool
140
     */
141 3
    protected static function isRuleDefined(&$rules, $names)
142
    {
143 3
        foreach ((array)$names as $key) {
144 3
            if (!isset($rules[$key])) {
145 3
                return false;
146
            }
147
        }
148
149 3
        return true;
150
    }
151
152 2
    protected static function undefineRule(&$rules, $names)
153
    {
154 2
        foreach ((array)$names as $key) {
155 2
            unset($rules[$key]);
156
        }
157 2
    }
158
159
    /**
160
     * Create a new instance of a definition
161
     *
162
     * @param $rules
163
     * @param $key
164
     * @param array $arguments
165
     * @return Implementation|Parser
166
     */
167 37
    protected static function call(&$rules, $key, $arguments = [])
168
    {
169 37
        if (!isset($rules[$key])) {
170 17
            $rules[$key] = new Definition();
171
        }
172
173 37
        $rule = $rules[$key];
174
175
        switch (true) {
176
            // Parser Definition
177 37
            case $rule instanceof Definition:
178 23
                $instance = new Implementation($rule, $arguments);
179 23
                break;
180
181
            // Parser
182 19
            case $rule instanceof Parser:
183 7
                $instance = clone $rule;
184 7
                break;
185
186
            // Generator function (should return Parser)
187 14
            case is_callable($rule):
188 6
                $instance = $rule(...$arguments);
189 6
                if (!$instance instanceof Parser) {
190 1
                    throw new \InvalidArgumentException("Generator function for rule `{$key}` does not return Parser");
191
                }
192 5
                break;
193
194
            // Class name of a Parser class
195 8
            case is_string($rule) && class_exists($rule) && is_subclass_of($rule, Parser::class):
196 1
                $instance = new $rule(...$arguments);
197 1
                break;
198
199
            // Self-referential call
200 7
            case is_string($rule) && isset($rules[$rule]):
201 1
                $instance = static::call($rules, $rule, $arguments);
202 1
                break;
203
204
            // S-C-S Array syntax
205 6
            case is_array($rule) || is_string($rule) || is_int($rule):
206 5
                $instance = self::getArgument($rule);
207 5
                break;
208
209
            default:
210 1
                throw new \RuntimeException(sprintf('Cannot define `%2$s` using definition type `%1$s`',
211 1
                    is_object($rule)
212
                        ? get_class($rule)
213 1
                        : gettype($rule), $key));
214
        }
215
216 35
        if (!$instance->hasToken()) {
217 35
            $instance->token($key, static::$name);
218
        }
219
220 35
        return $instance;
221
    }
222
223
    /**
224
     * Handle instance/object definitions
225
     *
226
     * @param string $name
227
     * @param array $arguments
228
     * @return Parser
229
     * @throws \Exception
230
     */
231 14
    public function __call($name, $arguments = [])
232
    {
233 14
        return static::call($this->instanceRules, $name, $arguments);
234
    }
235
236
    /**
237
     * Handle static/class definitions
238
     *
239
     * @param string $name
240
     * @param array $arguments
241
     * @return Parser
242
     * @throws \Exception
243
     */
244 4
    public static function __callStatic($name, $arguments = [])
245
    {
246 4
        return static::call(self::$staticRules, $name, $arguments);
247
    }
248
249 15
    public function __get($name)
250
    {
251 15
        return static::call($this->instanceRules, $name);
252
    }
253
254 1
    public function __isset($name)
255
    {
256 1
        return isset($this->instanceRules[$name]);
257
    }
258
259
    // Default parser
260
261 14
    private function initDefaultParser()
262
    {
263 14
        if ($this->parser === null) {
264 6
            if (!isset($this->instanceRules[self::ROOT])) {
265 1
                throw new \UnexpectedValueException('Missing default parser');
266
            }
267
268 5
            $this->parser = static::call($this->instanceRules, self::ROOT);
269
        }
270 12
    }
271
272 12
    protected function parse(&$input, $offset, Context $context)
273
    {
274 12
        $this->initDefaultParser();
275
276 11
        $match = $this->parser->parse($input, $offset, $context);
0 ignored issues
show
Bug introduced by
The method parse() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

276
        /** @scrutinizer ignore-call */ 
277
        $match = $this->parser->parse($input, $offset, $context);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
277 11
        if ($match instanceof Success) {
278 10
            return $this->success($input, $offset, $match->length, $match);
279
        }
280 1
        return $this->failure($input, $offset, $match->length);
281
    }
282
283 2
    public function __toString()
284
    {
285
        try {
286 2
            $this->initDefaultParser();
287 1
        } catch (\Exception $e) {
288
            // ignore
289
        }
290
291 2
        return (string)$this->parser;
292
    }
293
}
294