Passed
Push — master ( 25daee...6810a7 )
by Martijn
02:35
created

AbstractRuleset::typeToString()   A

Complexity

Conditions 2
Paths 2

Size

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