1<?php
2
3/**
4 * Hoa
5 *
6 *
7 * @license
8 *
9 * New BSD License
10 *
11 * Copyright © 2007-2017, Hoa community. All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *     * Redistributions of source code must retain the above copyright
16 *       notice, this list of conditions and the following disclaimer.
17 *     * Redistributions in binary form must reproduce the above copyright
18 *       notice, this list of conditions and the following disclaimer in the
19 *       documentation and/or other materials provided with the distribution.
20 *     * Neither the name of the Hoa nor the names of its contributors may be
21 *       used to endorse or promote products derived from this software without
22 *       specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37namespace Hoa\Compiler\Test\Integration\Llk\Rule;
38
39use Hoa\Compiler as LUT;
40use Hoa\Compiler\Llk\Rule;
41use Hoa\Compiler\Llk\Rule\Analyzer as SUT;
42use Hoa\Test;
43
44/**
45 * Class \Hoa\Compiler\Test\Integration\Llk\Rule\Analyzer.
46 *
47 * Test suite of the rule analyzer.
48 *
49 * @copyright  Copyright © 2007-2017 Hoa community
50 * @license    New BSD License
51 */
52class Analyzer extends Test\Integration\Suite
53{
54    public function case_simple_kept_token()
55    {
56        return $this->_case_simple_token(true);
57    }
58
59    public function case_simple_skipped_token()
60    {
61        return $this->_case_simple_token(false);
62    }
63
64    protected function _case_simple_token($kept)
65    {
66        $this
67            ->given(
68                $tokens   = ['default' => ['foo' => 'bar']],
69                $ruleA    = $kept ? '<foo>' : '::foo::',
70                $analyzer = new SUT($tokens),
71
72                $_ruleA = new Rule\Token('ruleA', 'foo', null, -1, $kept),
73                $_ruleA->setPPRepresentation($ruleA)
74            )
75            ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA]))
76            ->then
77                ->array($result)
78                    ->isEqualTo([
79                        'ruleA' => $_ruleA
80                    ]);
81    }
82
83    public function case_simple_named()
84    {
85        $this
86            ->given(
87                $tokens = ['default' => ['foo' => 'bar']],
88                $rules  = [
89                    'ruleA' => 'ruleB()',
90                    'ruleB' => '<foo>'
91                ],
92                $analyzer = new SUT($tokens),
93
94                $_ruleA = new Rule\Concatenation('ruleA', ['ruleB']),
95                $_ruleA->setPPRepresentation($rules['ruleA']),
96                $_ruleB = new Rule\Token('ruleB', 'foo', null, -1, true),
97                $_ruleB->setPPRepresentation($rules['ruleB'])
98            )
99            ->when($result = $analyzer->analyzeRules($rules))
100            ->then
101                ->array($result)
102                    ->isEqualTo([
103                        'ruleA' => $_ruleA,
104                        'ruleB' => $_ruleB
105                    ]);
106    }
107
108    public function case_simple_kept_unified_token()
109    {
110        return $this->_case_simple_unified_token(true);
111    }
112
113    public function case_simple_skipped_unified_token()
114    {
115        return $this->_case_simple_unified_token(false);
116    }
117
118    protected function _case_simple_unified_token($kept)
119    {
120        $this
121            ->given(
122                $tokens   = ['default' => ['foo' => 'bar']],
123                $ruleA    = $kept ? '<foo[42]>' : '::foo[42]::',
124                $analyzer = new SUT($tokens),
125
126                $_ruleA = new Rule\Token('ruleA', 'foo', null, 42, $kept),
127                $_ruleA->setPPRepresentation($ruleA)
128            )
129            ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA]))
130            ->then
131                ->array($result)
132                    ->isEqualTo([
133                        'ruleA' => $_ruleA
134                    ]);
135    }
136
137    public function case_repetition_zero_or_one()
138    {
139        return $this->_case_repetition('?', 0, 1);
140    }
141
142    public function case_repetition_one_or_more()
143    {
144        return $this->_case_repetition('+', 1, -1);
145    }
146
147    public function case_repetition_zero_or_more()
148    {
149        return $this->_case_repetition('*', 0, -1);
150    }
151
152    public function case_repetition_n_to_m()
153    {
154        return $this->_case_repetition('{7,42}', 7, 42);
155    }
156
157    public function case_repetition_n_or_more()
158    {
159        return $this->_case_repetition('{7,}', 7, -1);
160    }
161
162    public function case_repetition_exactly_n()
163    {
164        return $this->_case_repetition('{7}', 7, 7);
165    }
166
167    protected function _case_repetition($quantifier, $min, $max)
168    {
169        $this
170            ->given(
171                $tokens   = ['default' => ['foo' => 'bar']],
172                $ruleA    = '<foo>' . $quantifier,
173                $analyzer = new SUT($tokens),
174
175                $_ruleA = new Rule\Repetition('ruleA', $min, $max, 0, null),
176                $_ruleA->setPPRepresentation($ruleA),
177                $_rule0 = new Rule\Token(0, 'foo', null, -1, true)
178            )
179            ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA]))
180            ->then
181                ->array($result)
182                    ->isEqualTo([
183                        '0'     => $_rule0,
184                        'ruleA' => $_ruleA
185                    ]);
186    }
187
188    public function case_concatenation()
189    {
190        $this
191            ->given(
192                $tokens   = ['default' => ['foo' => 'bar', 'baz' => 'qux']],
193                $ruleA    = '<foo> <baz>',
194                $analyzer = new SUT($tokens),
195
196                $_ruleA = new Rule\Concatenation('ruleA', [0, 1], null),
197                $_ruleA->setPPRepresentation($ruleA),
198                $_rule0 = new Rule\Token(0, 'foo', null, -1, true),
199                $_rule1 = new Rule\Token(1, 'baz', null, -1, true)
200            )
201            ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA]))
202            ->then
203                ->array($result)
204                    ->isEqualTo([
205                        0       => $_rule0,
206                        1       => $_rule1,
207                        'ruleA' => $_ruleA
208                    ]);
209    }
210
211    public function case_choice()
212    {
213        $this
214            ->given(
215                $tokens   = ['default' => ['foo' => 'bar', 'baz' => 'qux']],
216                $ruleA    = '<foo> | <baz>',
217                $analyzer = new SUT($tokens),
218
219                $_ruleA = new Rule\Choice('ruleA', [0, 1], null),
220                $_ruleA->setPPRepresentation($ruleA),
221                $_rule0 = new Rule\Token(0, 'foo', null, -1, true),
222                $_rule1 = new Rule\Token(1, 'baz', null, -1, true)
223            )
224            ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA]))
225            ->then
226                ->array($result)
227                    ->isEqualTo([
228                        0       => $_rule0,
229                        1       => $_rule1,
230                        'ruleA' => $_ruleA
231                    ]);
232    }
233
234    public function case_rule()
235    {
236        $this
237            ->given(
238                $tokens   = ['default' => ['foo' => 'bar', 'baz' => 'qux']],
239                $ruleA    = '<foo> | <baz>',
240                $analyzer = new SUT($tokens),
241
242                $_ruleA = new Rule\Choice('ruleA', [0, 1], null),
243                $_ruleA->setPPRepresentation($ruleA),
244                $_rule0 = new Rule\Token(0, 'foo', null, -1, true),
245                $_rule1 = new Rule\Token(1, 'baz', null, -1, true)
246            )
247            ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA]))
248            ->then
249                ->array($result)
250                    ->isEqualTo([
251                        0       => $_rule0,
252                        1       => $_rule1,
253                        'ruleA' => $_ruleA
254                    ]);
255    }
256
257    public function case_full()
258    {
259        $this
260            ->given(
261                $tokens   = ['default' => ['foo' => 'bar', 'baz' => 'qux']],
262                $ruleA    = '<foo>+ | ruleB() ::foo::',
263                $ruleB    = '<baz> ruleA()',
264                $analyzer = new SUT($tokens),
265
266                $_rule0 = new Rule\Token(0, 'foo', null, -1, true),
267                $_rule1 = new Rule\Repetition('1', 1, -1, '0', null),
268                $_rule2 = new Rule\Token(2, 'foo', null, -1, false),
269                $_rule3 = new Rule\Concatenation('3', ['ruleB', 2]),
270                $_ruleA = new Rule\Choice('ruleA', [1, 3], null),
271                $_ruleA->setPPRepresentation($ruleA),
272                $_rule5 = new Rule\Token(5, 'baz', null, -1, true),
273                $_ruleB = new Rule\Concatenation('ruleB', [5, 'ruleA']),
274                $_ruleB->setPPRepresentation($ruleB)
275            )
276            ->when($result = $analyzer->analyzeRules(['ruleA' => $ruleA, 'ruleB' => $ruleB]))
277            ->then
278                ->array($result)
279                    ->isEqualTo([
280                        0       => $_rule0,
281                        1       => $_rule1,
282                        2       => $_rule2,
283                        3       => $_rule3,
284                        'ruleA' => $_ruleA,
285                        5       => $_rule5,
286                        'ruleB' => $_ruleB
287                    ]);
288    }
289
290    public function case_no_rule()
291    {
292        $this
293            ->given($analyzer = new SUT([]))
294            ->exception(function () use ($analyzer) {
295                $analyzer->analyzeRules([]);
296            })
297                ->isInstanceOf(LUT\Exception::class)
298                ->hasMessage('No rules specified!');
299    }
300
301    public function case_error_while_parsing_rule()
302    {
303        $this
304            ->given(
305                $tokens   = ['default' => ['foo' => 'bar']],
306                $ruleA    = '<foo>{2,1}',
307                $analyzer = new SUT($tokens)
308            )
309            ->exception(function () use ($analyzer, $ruleA) {
310                $analyzer->analyzeRules(['ruleA' => $ruleA]);
311            })
312                ->isInstanceOf(LUT\Exception::class)
313                ->hasMessage(
314                    'Upper bound 1 must be greater or equal to lower bound ' .
315                    '2 in rule ruleA.'
316                );
317    }
318
319    public function case_kept_token_does_not_exist()
320    {
321        $this
322            ->given(
323                $tokens   = [],
324                $ruleA    = '<foo>',
325                $analyzer = new SUT($tokens)
326            )
327            ->exception(function () use ($analyzer, $ruleA) {
328                $analyzer->analyzeRules(['ruleA' => $ruleA]);
329            })
330                ->isInstanceOf(LUT\Exception::class)
331                ->hasMessage('Token <foo> does not exist in rule ruleA.');
332    }
333
334    public function case_skipped_token_does_not_exist()
335    {
336        $this
337            ->given(
338                $tokens   = [],
339                $ruleA    = '::foo::',
340                $analyzer = new SUT($tokens)
341            )
342            ->exception(function () use ($analyzer, $ruleA) {
343                $analyzer->analyzeRules(['ruleA' => $ruleA]);
344            })
345                ->isInstanceOf(LUT\Exception::class)
346                ->hasMessage('Token ::foo:: does not exist in rule ruleA.');
347    }
348
349    public function case_rule_does_not_exist()
350    {
351        $this
352            ->given(
353                $tokens   = [],
354                $ruleA    = 'ruleB()',
355                $analyzer = new SUT($tokens)
356            )
357            ->exception(function () use ($analyzer, $ruleA) {
358                $analyzer->analyzeRules(['ruleA' => $ruleA]);
359            })
360                ->isInstanceOf(LUT\Exception::class)
361                ->hasMessage(
362                    'Cannot call rule ruleB() in rule ruleA because it does not exist.'
363                );
364    }
365}
366