Passed
Push — master ( 2ef79b...70e25c )
by Martijn
03:03
created

AbstractRuleset::__call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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
        RuleToParserTrait;
19
20
    /**
21
     * Constant to specify default parser for this ruleset, which is called when Ruleset is used as a Parser.
22
     */
23
    const ROOT = 'ROOT';
24
25
26
    /**
27
     * List of reserved parser names. These are used as methods instead.
28
     *
29
     * @var string[]
30
     */
31
    private static $reserved = [
32
        'define',
33
        'defined',
34
        'undefine'
35
    ];
36
37
    /**
38
     * Name of this ruleset. Intended for use with the standard library rulesets
39
     *
40
     * @var string
41
     */
42
    protected static $name = null;
43
44
    /**
45
     * Parser to use as the default parser used when calling this Ruleset as a Parser.
46
     *
47
     * @var null|Parser
48
     */
49
    private $parser = null;
50
51
    /**
52
     * @var Definition[]|Parser[]|callable[]
53
     */
54
    private static $staticRules = [];
55
56
    /**
57
     * @var Definition[]|Parser[]|callable[]
58
     */
59
    protected $instanceRules = [];
60
61
    /**
62
     * Ruleset constructor, defining initial instance rules.
63
     *
64
     * @param string|array $key Either a key of an initial rule to define or a [ key : definition ] array
65
     * @param null|string $definition Definition of the initial rule or `null` if `$key` is an array
66
     * @param null|string $name optional identifier for this ruleset
67
     * @throws \Exception
68
     */
69 34
    public function __construct($key = null, $definition = null, $name = null)
70
    {
71 34
        if ($key !== null) {
72 14
            self::defineRule($this->instanceRules, $key, $definition);
73
        }
74
75 34
        self::$name = $name;
76 34
    }
77
78
    /**
79
     * Instantiate the default parser (if available)
80
     */
81 14
    private function initDefaultParser()
82
    {
83 14
        if ($this->parser === null) {
84 6
            if (!isset($this->instanceRules[self::ROOT])) {
85 1
                throw new \UnexpectedValueException('Missing default parser');
86
            }
87
88 5
            $this->parser = static::call($this->instanceRules, self::ROOT);
89
        }
90 12
    }
91
92 12
    protected function parse(&$input, $offset, Context $context)
93
    {
94 12
        $this->initDefaultParser();
95
96 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

96
        /** @scrutinizer ignore-call */ 
97
        $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...
97 11
        if ($match instanceof Success) {
98 10
            return $this->success($input, $offset, $match->length, $match);
99
        }
100 1
        return $this->failure($input, $offset, $match->length);
101
    }
102
103
    /**
104
     * Set a definition
105
     *
106
     * @param Definition[]|Parser[]|callable[] $rules
107
     * @param string $key
108
     * @param Definition|Parser|callable $definition
109
     * @throws \Exception
110
     */
111 36
    protected static function setRule(&$rules, $key, $definition)
112
    {
113 36
        if (isset($rules[$key])
114 36
            && $rules[$key] instanceof Definition) {
115
116 15
            if ($definition instanceof Definition) {
117 1
                $rules[$key]->generator  = $definition->generator;
118 1
                $rules[$key]->validators = $definition->validators;
119 1
                return;
120
            }
121
122 14
            if ($definition instanceof Parser) {
123 10
                $rules[$key]->generator = $definition;
124 10
                return;
125
            }
126
127 12
            if (is_callable($definition)) {
128 2
                $rules[$key]->generator = $definition;
129 2
                return;
130
            }
131
132 10
            if (is_array($definition)
133 2
                || is_string($definition)
134 10
                || is_int($definition)) {
135 9
                $rules[$key]->generator = self::getArgument($definition);
136 9
                return;
137
            }
138
139 1
            throw new \RuntimeException(sprintf('Cannot redefine `%2$s` using definition type `%1$s`',
140 1
                is_object($definition)
141
                    ? get_class($definition)
142 1
                    : gettype($definition), $key));
143
        }
144
145 29
        $rules[$key] = $definition;
146 29
    }
147
148
    /**
149
     * Define a rule
150
     *
151
     * @param array $rules
152
     * @param string|array $name
153
     * @param Mixed|null $definition
154
     * @throws \Exception
155
     */
156 20
    protected static function defineRule(&$rules, $name, $definition = null)
157
    {
158 20
        if (is_array($name)) {
159 9
            foreach ($name as $key => $value) {
160 9
                self::defineRule($rules, $key, $value);
161
            }
162 9
            return;
163
        }
164
165 20
        if (in_array($name, self::$reserved)) {
166 1
            throw new \RuntimeException("Cannot define reserved name `{$name}`");
167
        }
168
169 19
        self::setRule($rules, $name, $definition);
170 19
    }
171
172
    /**
173
     * Check if a specified name is defined in the rules map provided
174
     *
175
     * @param $rules
176
     * @param $names
177
     * @return bool
178
     */
179 3
    protected static function isRuleDefined(&$rules, $names)
180
    {
181 3
        foreach ((array)$names as $key) {
182 3
            if (!isset($rules[$key])) {
183 3
                return false;
184
            }
185
        }
186
187 3
        return true;
188
    }
189
190 2
    protected static function undefineRule(&$rules, $names)
191
    {
192 2
        foreach ((array)$names as $key) {
193 2
            unset($rules[$key]);
194
        }
195 2
    }
196
197 35
    private static function applyToken($key, Parser $parser)
198
    {
199 35
        if (!$parser->hasToken()) {
200 35
            $parser->token($key, static::$name);
201
        }
202
203 35
        return $parser;
204
    }
205
206
    /**
207
     * Create a new instance of a definition
208
     *
209
     * @param $rules
210
     * @param $key
211
     * @param array $arguments
212
     * @return Implementation|Parser
213
     */
214 37
    protected static function call(&$rules, $key, $arguments = [])
215
    {
216
        // Undefined rule; return empty definition implementation
217 37
        if (!isset($rules[$key])) {
218 17
            $rules[$key] = new Definition();
219 17
            return self::applyToken($key, new Implementation($rules[$key], $arguments));
220
        }
221
222
        // Rule reference
223 29
        if (is_string($rules[$key])
224 29
            && isset($rules[$rules[$key]])) {
225 1
            return self::applyToken($key, static::call($rules, $rules[$key], $arguments));
226
        }
227
228
        // Generic rule interpreters
229 29
        $instance = self::ruleToParser($rules[$key], $arguments);
230 29
        if ($instance) {
231 27
            return self::applyToken($key, $instance);
232
        }
233
234 2
        throw new \RuntimeException(sprintf('Cannot instantiate `%2$s` using definition type `%1$s`',
235 2
            is_object($rules[$key])
236 1
                ? get_class($rules[$key])
237 2
                : gettype($rules[$key]), $key));
238
    }
239
240
    /**
241
     * Handle instance/object definitions
242
     *
243
     * @param string $name
244
     * @param array $arguments
245
     * @return Parser
246
     * @throws \Exception
247
     */
248 14
    public function __call($name, $arguments = [])
249
    {
250 14
        return static::call($this->instanceRules, $name, $arguments);
251
    }
252
253
    /**
254
     * Handle static/class definitions
255
     *
256
     * @param string $name
257
     * @param array $arguments
258
     * @return Parser
259
     * @throws \Exception
260
     */
261 4
    public static function __callStatic($name, $arguments = [])
262
    {
263 4
        return static::call(self::$staticRules, $name, $arguments);
264
    }
265
266 15
    public function __get($name)
267
    {
268 15
        return static::call($this->instanceRules, $name);
269
    }
270
271 1
    public function __isset($name)
272
    {
273 1
        return isset($this->instanceRules[$name]);
274
    }
275
276 2
    public function __toString()
277
    {
278
        try {
279 2
            $this->initDefaultParser();
280 1
        } catch (\Exception $e) {
281
            // ignore
282
        }
283
284 2
        return (string)$this->parser;
285
    }
286
}
287