Completed
Push — master ( 075373...9cb16b )
by Martijn
02:56
created

AbstractRuleset::isRuleDefined()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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