Completed
Push — master ( c80620...0cd8d9 )
by Martijn
03:25
created

AbstractRuleset::setRule()   B

Complexity

Conditions 10
Paths 7

Size

Total Lines 31
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 20
nc 7
nop 3
dl 0
loc 31
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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