Passed
Push — master ( c0588a...082cdd )
by Martijn
02:55
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
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]) && $rules[$key] instanceof Definition) {
86 15
            if ($definition instanceof Definition) {
87 1
                $rules[$key]->generator  = $definition->generator;
88 1
                $rules[$key]->validators = $definition->validators;
89 1
                return;
90
            }
91
92 14
            if ($definition instanceof Parser) {
93 10
                $rules[$key]->generator = $definition;
94 10
                return;
95
            }
96
97 12
            if (is_callable($definition)) {
98 2
                $rules[$key]->generator = $definition;
99 2
                return;
100
            }
101
102 10
            if (is_array($definition) || is_string($definition) || is_int($definition)) {
103 9
                $rules[$key]->generator = self::getArgument($definition);
104 9
                return;
105
            }
106
107 1
            throw new \RuntimeException(sprintf('Cannot redefine `%2$s` using definition type `%1$s`',
108 1
                is_object($definition)
109
                    ? get_class($definition)
110 1
                    : gettype($definition), $key));
111
        }
112
113 29
        $rules[$key] = $definition;
114 29
    }
115
116 20
    protected static function defineRule(&$rules, $names, $definition = null)
117
    {
118 20
        if (is_array($names)) {
119 9
            foreach ($names as $key => $value) {
120 9
                self::defineRule($rules, $key, $value);
121
            }
122 9
            return;
123
        }
124
125 20
        if (in_array($names, self::$reserved)) {
126 1
            throw new \RuntimeException("Cannot define reserved name `{$names}`");
127
        }
128
129 19
        self::setRule($rules, $names, $definition);
130 19
    }
131
132
    /**
133
     * Check if a specified name is defined in the rules map provided
134
     *
135
     * @param $rules
136
     * @param $names
137
     * @return bool
138
     */
139 3
    protected static function isRuleDefined(&$rules, $names)
140
    {
141 3
        foreach ((array)$names as $key) {
142 3
            if (!isset($rules[$key])) {
143 3
                return false;
144
            }
145
        }
146
147 3
        return true;
148
    }
149
150 2
    protected static function undefineRule(&$rules, $names)
151
    {
152 2
        foreach ((array)$names as $key) {
153 2
            unset($rules[$key]);
154
        }
155 2
    }
156
157 35
    private static function applyToken($key, Parser $parser)
158
    {
159 35
        if (!$parser->hasToken()) {
160 35
            $parser->token($key, static::$name);
161
        }
162
163 35
        return $parser;
164
    }
165
166
    /**
167
     * Create a new instance of a definition
168
     *
169
     * @param $rules
170
     * @param $key
171
     * @param array $arguments
172
     * @return Implementation|Parser
173
     */
174 37
    protected static function call(&$rules, $key, $arguments = [])
175
    {
176 37
        if (!isset($rules[$key])) {
177 17
            $rules[$key] = new Definition();
178
        }
179
180 37
        $rule = $rules[$key];
181
182
        // Parser Definition
183 37
        if ($rule instanceof Definition) {
184 23
            return self::applyToken($key, new Implementation($rule, $arguments));
185
        }
186
187
        // Parser
188 19
        if ($rule instanceof Parser) {
189 7
            return self::applyToken($key, clone $rule);
190
        }
191
192
        // Generator function (should return Parser)
193 14
        if (is_callable($rule)) {
194 6
            $instance = $rule(...$arguments);
195 6
            if (!$instance instanceof Parser) {
196 1
                throw new \InvalidArgumentException("Generator function for rule `{$key}` does not return Parser");
197
            }
198 5
            return self::applyToken($key, $instance);
199
        }
200
201
        // Class name of a Parser class
202 8
        if (is_string($rule) && class_exists($rule) && is_subclass_of($rule, Parser::class)) {
203 1
            return self::applyToken($key, new $rule(...$arguments));
204
        }
205
206
        // Self-referential call
207 7
        if (is_string($rule) && isset($rules[$rule])) {
208 1
            return self::applyToken($key, static::call($rules, $rule, $arguments));
209
        }
210
211
        // S-C-S Array syntax
212 6
        if (is_array($rule) || is_string($rule) || is_int($rule)) {
213 5
            return self::applyToken($key, self::getArgument($rule));
214
        }
215
216 1
        throw new \RuntimeException(sprintf('Cannot define `%2$s` using definition type `%1$s`',
217 1
            is_object($rule)
218
                ? get_class($rule)
219 1
                : gettype($rule), $key));
220
    }
221
222
    /**
223
     * Handle instance/object definitions
224
     *
225
     * @param string $name
226
     * @param array $arguments
227
     * @return Parser
228
     * @throws \Exception
229
     */
230 14
    public function __call($name, $arguments = [])
231
    {
232 14
        return static::call($this->instanceRules, $name, $arguments);
233
    }
234
235
    /**
236
     * Handle static/class definitions
237
     *
238
     * @param string $name
239
     * @param array $arguments
240
     * @return Parser
241
     * @throws \Exception
242
     */
243 4
    public static function __callStatic($name, $arguments = [])
244
    {
245 4
        return static::call(self::$staticRules, $name, $arguments);
246
    }
247
248 15
    public function __get($name)
249
    {
250 15
        return static::call($this->instanceRules, $name);
251
    }
252
253 1
    public function __isset($name)
254
    {
255 1
        return isset($this->instanceRules[$name]);
256
    }
257
258
    // Default parser
259
260 14
    private function initDefaultParser()
261
    {
262 14
        if ($this->parser === null) {
263 6
            if (!isset($this->instanceRules[self::ROOT])) {
264 1
                throw new \UnexpectedValueException('Missing default parser');
265
            }
266
267 5
            $this->parser = static::call($this->instanceRules, self::ROOT);
268
        }
269 12
    }
270
271 12
    protected function parse(&$input, $offset, Context $context)
272
    {
273 12
        $this->initDefaultParser();
274
275 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

275
        /** @scrutinizer ignore-call */ 
276
        $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...
276 11
        if ($match instanceof Success) {
277 10
            return $this->success($input, $offset, $match->length, $match);
278
        }
279 1
        return $this->failure($input, $offset, $match->length);
280
    }
281
282 2
    public function __toString()
283
    {
284
        try {
285 2
            $this->initDefaultParser();
286 1
        } catch (\Exception $e) {
287
            // ignore
288
        }
289
290 2
        return (string)$this->parser;
291
    }
292
}
293