Passed
Push — master ( 4344d4...81d6c9 )
by Martijn
02:43
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 Exception;
6
use RuntimeException;
7
use UnexpectedValueException;
8
use Vanderlee\Comprehend\Core\ArgumentsTrait;
9
use Vanderlee\Comprehend\Core\Context;
10
use Vanderlee\Comprehend\Match\Success;
11
use Vanderlee\Comprehend\Parser\Parser;
12
13
/**
14
 * Description of abstract Ruleset
15
 *
16
 * @author Martijn
17
 */
18
abstract class AbstractRuleset extends Parser
19
{
20
    use ArgumentsTrait,
21
        RuleToParserTrait;
22
23
    /**
24
     * Constant to specify default parser for this ruleset, which is called when Ruleset is used as a Parser.
25
     */
26
    const ROOT = 'ROOT';
27
28
29
    /**
30
     * List of reserved parser names. These are used as methods instead.
31
     *
32
     * @var string[]
33
     */
34
    private static $reserved = [
35
        'define',
36
        'defined',
37
        'undefine'
38
    ];
39
40
    /**
41
     * Name of this ruleset. Intended for use with the standard library rulesets
42
     *
43
     * @var string
44
     */
45
    protected static $name = null;
46
47
    /**
48
     * Parser to use as the default parser used when calling this Ruleset as a Parser.
49
     *
50
     * @var null|Parser
51
     */
52
    private $parser = null;
53
54
    /**
55
     * @var Definition[]|Parser[]|callable[]
56
     */
57
    private static $staticRules = [];
58
59
    /**
60
     * @var Definition[]|Parser[]|callable[]
61
     */
62
    protected $instanceRules = [];
63
64
    /**
65
     * Ruleset constructor, defining initial instance rules.
66
     *
67
     * @param string|array $key Either a key of an initial rule to define or a [ key : definition ] array
68
     * @param null|string $definition Definition of the initial rule or `null` if `$key` is an array
69
     * @param null|string $name optional identifier for this ruleset
70
     * @throws \Exception
71
     */
72 35
    public function __construct($key = null, $definition = null, $name = null)
73
    {
74 35
        if ($key !== null) {
75 14
            self::defineRule($this->instanceRules, $key, $definition);
76
        }
77
78 35
        self::$name = $name;
79 35
    }
80
81
    /**
82
     * Instantiate the default parser (if available)
83
     */
84 14
    private function initDefaultParser()
85
    {
86 14
        if ($this->parser === null) {
87 6
            if (!isset($this->instanceRules[self::ROOT])) {
88 1
                throw new UnexpectedValueException('Missing default parser');
89
            }
90
91 5
            $this->parser = static::call($this->instanceRules, self::ROOT);
92
        }
93 12
    }
94
95 12
    protected function parse(&$input, $offset, Context $context)
96
    {
97 12
        $this->initDefaultParser();
98
99 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

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