Completed
Push — master ( 082cdd...2ef79b )
by Martijn
03:19
created

AbstractRuleset::parse()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 3
dl 0
loc 9
ccs 6
cts 6
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
     * List of static methods (in order) to attempt instantiating a rule
26
     *
27
     * @var string[]
28
     */
29
    private static $ruleMethods = [
30
        'ruleToDefinition',
31
        'ruleToParser',
32
        'ruleToCallable',
33
        'ruleToClassname',
34
        'ruleToReference',
35
        'ruleToArgument',
36
    ];
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
     * Name of this ruleset. Intended for use with the standard library rulesets
51
     *
52
     * @var string
53
     */
54
    protected static $name = null;
55
56
    /**
57
     * Parser to use as the default parser used when calling this Ruleset as a Parser.
58
     *
59
     * @var null|Parser
60
     */
61
    private $parser = null;
62
63
    /**
64
     * @var Definition[]|Parser[]|callable[]
65
     */
66
    private static $staticRules = [];
67
68
    /**
69
     * @var Definition[]|Parser[]|callable[]
70
     */
71
    protected $instanceRules = [];
72
73
    /**
74
     * Ruleset constructor, defining initial instance rules.
75
     *
76
     * @param string|array $key Either a key of an initial rule to define or a [ key : definition ] array
77
     * @param null|string $definition Definition of the initial rule or `null` if `$key` is an array
78
     * @param null|string $name optional identifier for this ruleset
79
     * @throws \Exception
80
     */
81 34
    public function __construct($key = null, $definition = null, $name = null)
82
    {
83 34
        if ($key !== null) {
84 14
            self::defineRule($this->instanceRules, $key, $definition);
85
        }
86
87 34
        self::$name = $name;
88 34
    }
89
90
    /**
91
     * Instantiate the default parser (if available)
92
     */
93 14
    private function initDefaultParser()
94
    {
95 14
        if ($this->parser === null) {
96 6
            if (!isset($this->instanceRules[self::ROOT])) {
97 1
                throw new \UnexpectedValueException('Missing default parser');
98
            }
99
100 5
            $this->parser = static::call($this->instanceRules, self::ROOT);
101
        }
102 12
    }
103
104 12
    protected function parse(&$input, $offset, Context $context)
105
    {
106 12
        $this->initDefaultParser();
107
108 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

108
        /** @scrutinizer ignore-call */ 
109
        $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...
109 11
        if ($match instanceof Success) {
110 10
            return $this->success($input, $offset, $match->length, $match);
111
        }
112 1
        return $this->failure($input, $offset, $match->length);
113
    }
114
115
    /**
116
     * Set a definition
117
     *
118
     * @param Definition[]|Parser[]|callable[] $rules
119
     * @param string $key
120
     * @param Definition|Parser|callable $definition
121
     * @throws \Exception
122
     */
123 36
    protected static function setRule(&$rules, $key, $definition)
124
    {
125 36
        if (isset($rules[$key])
126 36
            && $rules[$key] instanceof Definition) {
127
128 15
            if ($definition instanceof Definition) {
129 1
                $rules[$key]->generator  = $definition->generator;
130 1
                $rules[$key]->validators = $definition->validators;
131 1
                return;
132
            }
133
134 14
            if ($definition instanceof Parser) {
135 10
                $rules[$key]->generator = $definition;
136 10
                return;
137
            }
138
139 12
            if (is_callable($definition)) {
140 2
                $rules[$key]->generator = $definition;
141 2
                return;
142
            }
143
144 10
            if (is_array($definition)
145 2
                || is_string($definition)
146 10
                || is_int($definition)) {
147 9
                $rules[$key]->generator = self::getArgument($definition);
148 9
                return;
149
            }
150
151 1
            throw new \RuntimeException(sprintf('Cannot redefine `%2$s` using definition type `%1$s`',
152 1
                is_object($definition)
153
                    ? get_class($definition)
154 1
                    : gettype($definition), $key));
155
        }
156
157 29
        $rules[$key] = $definition;
158 29
    }
159
160
    /**
161
     * Define a rule
162
     *
163
     * @param array $rules
164
     * @param string|array $name
165
     * @param Mixed|null $definition
166
     * @throws \Exception
167
     */
168 20
    protected static function defineRule(&$rules, $name, $definition = null)
169
    {
170 20
        if (is_array($name)) {
171 9
            foreach ($name as $key => $value) {
172 9
                self::defineRule($rules, $key, $value);
173
            }
174 9
            return;
175
        }
176
177 20
        if (in_array($name, self::$reserved)) {
178 1
            throw new \RuntimeException("Cannot define reserved name `{$name}`");
179
        }
180
181 19
        self::setRule($rules, $name, $definition);
182 19
    }
183
184
    /**
185
     * Check if a specified name is defined in the rules map provided
186
     *
187
     * @param $rules
188
     * @param $names
189
     * @return bool
190
     */
191 3
    protected static function isRuleDefined(&$rules, $names)
192
    {
193 3
        foreach ((array)$names as $key) {
194 3
            if (!isset($rules[$key])) {
195 3
                return false;
196
            }
197
        }
198
199 3
        return true;
200
    }
201
202 2
    protected static function undefineRule(&$rules, $names)
203
    {
204 2
        foreach ((array)$names as $key) {
205 2
            unset($rules[$key]);
206
        }
207 2
    }
208
209 35
    private static function applyToken($key, Parser $parser)
210
    {
211 35
        if (!$parser->hasToken()) {
212 35
            $parser->token($key, static::$name);
213
        }
214
215 35
        return $parser;
216
    }
217
218
    /**
219
     * @param array $rules
220
     * @param string $key
221
     * @param array $arguments
222
     * @return Implementation
223
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
224
     */
225 37
    private static function ruleToDefinition(&$rules, $key, $arguments)
226
    {
227 37
        if ($rules[$key] instanceof Definition) {
228 23
            return new Implementation($rules[$key], $arguments);
229
        }
230
231 19
        return null;
232
    }
233
234
    /**
235
     * @param array $rules
236
     * @param string $key
237
     * @param array $arguments
238
     * @return Parser
239
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
240
     */
241 19
    private static function ruleToParser(&$rules, $key)
242
    {
243 19
        if ($rules[$key] instanceof Parser) {
244 7
            return clone $rules[$key];
245
        }
246
247 14
        return null;
248
    }
249
250
    /**
251
     * @param array $rules
252
     * @param string $key
253
     * @param array $arguments
254
     * @return Parser
255
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
256
     */
257 14
    private static function ruleToCallable(&$rules, $key, $arguments)
258
    {
259 14
        if (is_callable($rules[$key])) {
260 6
            $instance = $rules[$key](...$arguments);
261 6
            if ($instance instanceof Parser) {
262 5
                return $instance;
263
            }
264
265 1
            throw new \InvalidArgumentException("Generator function for `{$key}` does not return Parser");
266
        }
267
268 8
        return null;
269
    }
270
271
    /**
272
     * @param array $rules
273
     * @param string $key
274
     * @param array $arguments
275
     * @return Parser
276
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
277
     */
278 8
    private static function ruleToClassname(&$rules, $key, $arguments)
279
    {
280 8
        if (is_string($rules[$key])
281 8
            && class_exists($rules[$key])
282 8
            && is_subclass_of($rules[$key], Parser::class)) {
283 1
            return new $rules[$key](...$arguments);
284
        }
285
286 7
        return null;
287
    }
288
289
    /**
290
     * @param array $rules
291
     * @param string $key
292
     * @param array $arguments
293
     * @return Parser
294
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
295
     */
296 7
    private static function ruleToReference(&$rules, $key, $arguments)
297
    {
298 7
        if (is_string($rules[$key])
299 7
            && isset($rules[$rules[$key]])) {
300 1
            return static::call($rules, $rules[$key], $arguments);
301
        }
302
303 6
        return null;
304
    }
305
306
    /**
307
     * @param array $rules
308
     * @param string $key
309
     * @param array $arguments
310
     * @return Parser
311
     * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
312
     */
313 6
    private static function ruleToArgument(&$rules, $key)
314
    {
315 6
        if (is_array($rules[$key])
316 5
            || is_string($rules[$key])
317 6
            || is_int($rules[$key])) {
318 5
            return self::getArgument($rules[$key]);
319
        }
320
321 1
        return null;
322
    }
323
324
    /**
325
     * Create a new instance of a definition
326
     *
327
     * @param $rules
328
     * @param $key
329
     * @param array $arguments
330
     * @return Implementation|Parser
331
     */
332 37
    protected static function call(&$rules, $key, $arguments = [])
333
    {
334 37
        if (!isset($rules[$key])) {
335 17
            $rules[$key] = new Definition();
336 17
            self::applyToken($key, self::ruleToDefinition($rules, $key, $arguments));
337
        }
338
339 37
        foreach (self::$ruleMethods as $ruleMethod) {
340 37
            $instance = self::$ruleMethod($rules, $key, $arguments);
341 37
            if ($instance) {
342 37
                return self::applyToken($key, $instance);
343
            }
344
        }
345
346 1
        throw new \RuntimeException(sprintf('Cannot instantiate `%2$s` using definition type `%1$s`',
347 1
            is_object($rules[$key])
348
                ? get_class($rules[$key])
349 1
                : gettype($rules[$key]), $key));
350
    }
351
352
    /**
353
     * Handle instance/object definitions
354
     *
355
     * @param string $name
356
     * @param array $arguments
357
     * @return Parser
358
     * @throws \Exception
359
     */
360 14
    public function __call($name, $arguments = [])
361
    {
362 14
        return static::call($this->instanceRules, $name, $arguments);
363
    }
364
365
    /**
366
     * Handle static/class definitions
367
     *
368
     * @param string $name
369
     * @param array $arguments
370
     * @return Parser
371
     * @throws \Exception
372
     */
373 4
    public static function __callStatic($name, $arguments = [])
374
    {
375 4
        return static::call(self::$staticRules, $name, $arguments);
376
    }
377
378 15
    public function __get($name)
379
    {
380 15
        return static::call($this->instanceRules, $name);
381
    }
382
383 1
    public function __isset($name)
384
    {
385 1
        return isset($this->instanceRules[$name]);
386
    }
387
388 2
    public function __toString()
389
    {
390
        try {
391 2
            $this->initDefaultParser();
392 1
        } catch (\Exception $e) {
393
            // ignore
394
        }
395
396 2
        return (string)$this->parser;
397
    }
398
}
399