Completed
Push — master ( b124c6...25daee )
by Martijn
02:35
created

AbstractRuleset::call()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 4
nop 3
dl 0
loc 22
ccs 12
cts 12
cp 1
crap 5
rs 9.6111
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
    /**
28
     * List of reserved parser names. These are used as methods instead.
29
     *
30
     * @var string[]
31
     */
32
    private static $reserved = [
33
        'define',
34
        'defined',
35
        'undefine',
36
    ];
37
38
    /**
39
     * Name of this ruleset. Intended for use with the standard library rulesets.
40
     *
41
     * @var string
42
     */
43
    protected static $name = null;
44
45
    /**
46
     * Parser to use as the default parser used when calling this Ruleset as a Parser.
47
     *
48
     * @var null|Parser
49
     */
50
    private $defaultParserCache = null;
51
52
    /**
53
     * @var Definition[]|Parser[]|callable[]
54
     */
55
    private static $staticRules = [];
56
57
    /**
58
     * @var Definition[]|Parser[]|callable[]
59
     */
60
    protected $instanceRules = [];
61
62
    /**
63
     * Ruleset constructor, defining initial instance rules.
64
     *
65
     * @param string|array $key        Either a key of an initial rule to define or a [ key : definition ] array
66
     * @param null|string  $definition Definition of the initial rule or `null` if `$key` is an array
67
     * @param null|string  $name       optional identifier for this ruleset
68
     *
69
     * @throws \Exception
70
     */
71 35
    public function __construct($key = null, $definition = null, $name = null)
72
    {
73 35
        if ($key !== null) {
74 14
            self::defineRule($this->instanceRules, $key, $definition);
75
        }
76
77 35
        self::$name = $name;
78 35
    }
79
80
    /**
81
     * Instantiate the default parser (if available).
82
     *
83
     * @return null|Implementation|Parser
84
     */
85 14
    private function getRootParser()
86
    {
87 14
        if ($this->defaultParserCache === null) {
88 6
            if (!isset($this->instanceRules[self::ROOT])) {
89 1
                throw new UnexpectedValueException('Missing default parser');
90
            }
91
92 5
            $this->defaultParserCache = static::call($this->instanceRules, self::ROOT);
93
        }
94
95 12
        return $this->defaultParserCache;
96
    }
97
98 12
    protected function parse(&$input, $offset, Context $context)
99
    {
100 12
        $match = $this->getRootParser()->parse($input, $offset, $context);
101 11
        if ($match instanceof Success) {
102 10
            return $this->success($input, $offset, $match->length, $match);
103
        }
104 1
        return $this->failure($input, $offset, $match->length);
105
    }
106
107
    /**
108
     * Set a definition.
109
     *
110
     * @param Definition[]|Parser[]|callable[] $rules
111
     * @param string                           $key
112
     * @param Definition|Parser|callable       $definition
113
     *
114
     * @throws \Exception
115
     */
116 37
    protected static function setRule(&$rules, $key, $definition)
117
    {
118 37
        if (isset($rules[$key])
119 37
            && $rules[$key] instanceof Definition) {
120
121 16
            if ($definition instanceof Definition) {
122 1
                $rules[$key]->generator = $definition->generator;
123 1
                $rules[$key]->validators = $definition->validators;
124 1
                return;
125
            }
126
127 15
            if ($definition instanceof Parser
128 15
                || is_callable($definition)) {
129
130 12
                $rules[$key]->generator = $definition;
131 12
                return;
132
            }
133
134
            try {
135 11
                $rules[$key]->generator = self::getArgument($definition);
136 9
                return;
137 2
            } catch (Exception $exception) {
138 2
                throw new RuntimeException(sprintf('Cannot redefine `%2$s` using definition type `%1$s`',
139 2
                    self::typeToString($definition), $key));
140
            }
141
        }
142
143 29
        $rules[$key] = $definition;
144 29
    }
145
146
    /**
147
     * Convert a variable type to a string
148
     *
149
     * @param mixed $variable
150
     *
151
     * @return string
152
     */
153 4
    private static function typeToString($variable)
154
    {
155 4
        return is_object($variable)
156 2
            ? get_class($variable)
157 4
            : gettype($variable);
158
    }
159
160
    /**
161
     * Define a rule.
162
     *
163
     * @param array        $rules
164
     * @param string|array $name
165
     * @param Mixed|null   $definition
166
     *
167
     * @throws \Exception
168
     */
169 20
    protected static function defineRule(&$rules, $name, $definition = null)
170
    {
171 20
        if (is_array($name)) {
172 9
            foreach ($name as $key => $value) {
173 9
                self::defineRule($rules, $key, $value);
174
            }
175 9
            return;
176
        }
177
178 20
        if (in_array($name, self::$reserved)) {
179 1
            throw new RuntimeException("Cannot define reserved name `{$name}`");
180
        }
181
182 19
        self::setRule($rules, $name, $definition);
183 19
    }
184
185
    /**
186
     * Check if a specified name is defined in the rules map provided.
187
     *
188
     * @param $rules
189
     * @param $names
190
     *
191
     * @return bool
192
     */
193 3
    protected static function isRuleDefined(&$rules, $names)
194
    {
195 3
        foreach ((array) $names as $key) {
196 3
            if (!isset($rules[$key])) {
197 3
                return false;
198
            }
199
        }
200
201 3
        return true;
202
    }
203
204 2
    protected static function undefineRule(&$rules, $names)
205
    {
206 2
        foreach ((array) $names as $key) {
207 2
            unset($rules[$key]);
208
        }
209 2
    }
210
211 36
    private static function applyToken($key, Parser $parser)
212
    {
213 36
        if (!$parser->hasToken()) {
214 36
            $parser->token($key, static::$name);
215
        }
216
217 36
        return $parser;
218
    }
219
220
    /**
221
     * Create a new instance of a definition
222
     *
223
     * @param       $rules
224
     * @param       $key
225
     * @param array $arguments
226
     *
227
     * @return Implementation|Parser
228
     */
229 38
    protected static function call(&$rules, $key, $arguments = [])
230
    {
231
        // Undefined rule; return empty definition implementation
232 38
        if (!isset($rules[$key])) {
233 18
            $rules[$key] = new Definition();
234 18
            return self::applyToken($key, new Implementation($rules[$key], $arguments));
235
        }
236
237
        // Rule reference
238 29
        if (is_string($rules[$key])
239 29
            && isset($rules[$rules[$key]])) {
240 1
            return self::applyToken($key, static::call($rules, $rules[$key], $arguments));
241
        }
242
243
        // Generic rule interpreters
244 29
        $instance = self::ruleToParser($rules[$key], $arguments);
245 29
        if ($instance) {
246 27
            return self::applyToken($key, $instance);
247
        }
248
249 2
        throw new \RuntimeException(sprintf('Cannot instantiate `%2$s` using definition type `%1$s`',
250 2
            self::typeToString($rules[$key]), $key));
251
    }
252
253
    /**
254
     * Handle instance/object definitions
255
     *
256
     * @param string $name
257
     * @param array  $arguments
258
     *
259
     * @return Parser
260
     * @throws \Exception
261
     */
262 14
    public function __call($name, $arguments = [])
263
    {
264 14
        return static::call($this->instanceRules, $name, $arguments);
265
    }
266
267
    /**
268
     * Handle static/class definitions
269
     *
270
     * @param string $name
271
     * @param array  $arguments
272
     *
273
     * @return Parser
274
     * @throws \Exception
275
     */
276 4
    public static function __callStatic($name, $arguments = [])
277
    {
278 4
        return static::call(self::$staticRules, $name, $arguments);
279
    }
280
281 16
    public function __get($name)
282
    {
283 16
        return static::call($this->instanceRules, $name);
284
    }
285
286 1
    public function __isset($name)
287
    {
288 1
        return isset($this->instanceRules[$name]);
289
    }
290
291 2
    public function __toString()
292
    {
293
        try {
294 2
            return (string) $this->getRootParser();
295 1
        } catch (Exception $e) {
296
            // ignore
297
        }
298
299 1
        return '';
300
    }
301
}
302