AbstractRuleset::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 3
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
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
 * @author Martijn
15
 */
16
abstract class AbstractRuleset extends Parser
17
{
18
    use ArgumentsTrait,
19
        RuleToParserTrait;
20
21
    /**
22
     * Constant to specify default parser for this ruleset, which is called when Ruleset is used as a Parser.
23
     */
24
    const ROOT = 'ROOT';
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 $defaultParserCache = 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             $definition Definition of the initial rule or `null` if `$key` is an array
66
     * @param string|null $name optional identifier for this ruleset
67
     *
68
     * @throws Exception
69
     */
70 35
    public function __construct($key = null, $definition = null, string $name = null)
71
    {
72 35
        if ($key !== null) {
73 14
            self::defineRule($this->instanceRules, $key, $definition);
74
        }
75
76 35
        self::$name = $name;
77 35
    }
78
79
    /**
80
     * Instantiate the default parser (if available).
81
     *
82
     * @return null|Implementation|Parser
83
     */
84 14
    private function getRootParser()
85
    {
86 14
        if ($this->defaultParserCache === null) {
87 6
            if (!isset($this->instanceRules[self::ROOT])) {
88 1
                throw new UnexpectedValueException('Missing default parser');
89
            }
90
91 5
            $this->defaultParserCache = static::call($this->instanceRules, self::ROOT);
92
        }
93
94 12
        return $this->defaultParserCache;
95
    }
96
97
    /**
98
     * @throws Exception
99
     */
100 12
    protected function parse(&$input, $offset, Context $context)
101
    {
102 12
        $match = $this->getRootParser()->parse($input, $offset, $context);
103 11
        if ($match instanceof Success) {
104 10
            return $this->success($input, $offset, $match->length, $match);
105
        }
106
107 1
        return $this->failure($input, $offset, $match->length);
108
    }
109
110
    /**
111
     * Set a definition.
112
     *
113
     * @param callable[]|Definition[]|Parser[] $rules
114
     * @param string $key
115
     * @param Definition|Parser|callable $definition
116
     *
117
     * @throws Exception
118
     */
119 37
    protected static function setRule(array &$rules, string $key, $definition)
120
    {
121 37
        if (isset($rules[$key])
122 37
            && $rules[$key] instanceof Definition) {
123
            try {
124 16
                $rules[$key]->define($definition);
125
126 14
                return;
127 2
            } catch (Exception $exception) {
128 2
                throw new RuntimeException(sprintf('Cannot redefine `%2$s` using definition type `%1$s`',
129 2
                    self::getArgumentType($definition), $key));
130
            }
131
        }
132
133 29
        $rules[$key] = $definition;
134 29
    }
135
136
    /**
137
     * Define a rule.
138
     *
139
     * @param array $rules
140
     * @param string|array $name
141
     * @param mixed|null $definition
142
     *
143
     * @throws Exception
144
     */
145 20
    protected static function defineRule(array &$rules, $name, $definition = null)
146
    {
147 20
        if (is_array($name)) {
148 9
            foreach ($name as $key => $value) {
149 9
                self::defineRule($rules, $key, $value);
150
            }
151
152 9
            return;
153
        }
154
155 20
        if (in_array($name, self::$reserved)) {
156 1
            throw new RuntimeException('Cannot define reserved name `' . $name . '`');
157
        }
158
159 19
        self::setRule($rules, $name, $definition);
160 19
    }
161
162
    /**
163
     * Check if a specified name is defined in the rules map provided.
164
     *
165
     * @param $rules
166
     * @param $names
167
     *
168
     * @return bool
169
     */
170 3
    protected static function isRuleDefined($rules, $names)
171
    {
172 3
        foreach ((array)$names as $key) {
173 3
            if (!isset($rules[$key])) {
174 3
                return false;
175
            }
176
        }
177
178 3
        return true;
179
    }
180
181 2
    protected static function undefineRule(&$rules, $names)
182
    {
183 2
        foreach ((array)$names as $key) {
184 2
            unset($rules[$key]);
185
        }
186 2
    }
187
188 36
    private static function applyToken($key, Parser $parser)
189
    {
190 36
        if (!$parser->hasToken()) {
191 36
            $parser->token($key, static::$name);
192
        }
193
194 36
        return $parser;
195
    }
196
197
    /**
198
     * Create a new instance of a definition.
199
     *
200
     * @param       $rules
201
     * @param       $key
202
     * @param array $arguments
203
     *
204
     * @return Implementation|Parser
205
     */
206 38
    protected static function call(&$rules, $key, array $arguments = [])
207
    {
208
        // Undefined rule; return empty definition implementation
209 38
        if (!isset($rules[$key])) {
210 18
            $rules[$key] = new Definition();
211
212 18
            return self::applyToken($key, new Implementation($rules[$key], $arguments));
213
        }
214
215
        // Rule reference
216 29
        if (is_string($rules[$key])
217 29
            && isset($rules[$rules[$key]])) {
218 1
            return self::applyToken($key, static::call($rules, $rules[$key], $arguments));
219
        }
220
221
        // Generic rule interpreters
222 29
        $instance = self::ruleToParser($rules[$key], $arguments);
223 29
        if ($instance) {
224 27
            return self::applyToken($key, $instance);
225
        }
226
227 2
        throw new RuntimeException(sprintf('Cannot instantiate `%2$s` using definition type `%1$s`',
228 2
            self::getArgumentType($rules[$key]), $key));
229
    }
230
231
    /**
232
     * Handle instance/object definitions.
233
     *
234
     * @param string $name
235
     * @param array $arguments
236
     *
237
     * @return Parser
238
     * @throws Exception
239
     *
240
     */
241 14
    public function __call($name, $arguments = [])
242
    {
243 14
        return static::call($this->instanceRules, $name, $arguments);
244
    }
245
246
    /**
247
     * Handle static/class definitions.
248
     *
249
     * @param string $name
250
     * @param array $arguments
251
     *
252
     * @return Parser
253
     * @throws Exception
254
     *
255
     */
256 4
    public static function __callStatic($name, $arguments = [])
257
    {
258 4
        return static::call(self::$staticRules, $name, $arguments);
259
    }
260
261 16
    public function __get($name)
262
    {
263 16
        return static::call($this->instanceRules, $name);
264
    }
265
266 1
    public function __isset($name)
267
    {
268 1
        return isset($this->instanceRules[$name]);
269
    }
270
271 2
    public function __toString()
272
    {
273
        try {
274 2
            return (string)$this->getRootParser();
275 1
        } catch (Exception $e) {
276
            // ignore
277
        }
278
279 1
        return '';
280
    }
281
}
282