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\Regex\Visitor;
38
39use Hoa\Math;
40use Hoa\Regex;
41use Hoa\Ustring;
42use Hoa\Visitor;
43
44/**
45 * Class \Hoa\Regex\Visitor\Isotropic.
46 *
47 * Isotropic walk on the AST to generate a data.
48 *
49 * @copyright  Copyright © 2007-2017 Hoa community
50 * @license    New BSD License
51 */
52class Isotropic implements Visitor\Visit
53{
54    /**
55     * Numeric-sampler.
56     *
57     * @var \Hoa\Math\Sampler
58     */
59    protected $_sampler = null;
60
61
62
63    /**
64     * Initialize numeric-sampler.
65     *
66     * @param   \Hoa\Math\Sampler  $sampler    Numeric-sampler.
67     */
68    public function __construct(Math\Sampler $sampler)
69    {
70        $this->_sampler = $sampler;
71
72        return;
73    }
74
75    /**
76     * Visit an element.
77     *
78     * @param   \Hoa\Visitor\Element  $element    Element to visit.
79     * @param   mixed                 &$handle    Handle (reference).
80     * @param   mixed                 $eldnah     Handle (not reference).
81     * @return  mixed
82     * @throws  \Hoa\Regex\Exception
83     */
84    public function visit(
85        Visitor\Element $element,
86        &$handle = null,
87        $eldnah = null
88    ) {
89        switch ($element->getId()) {
90            case '#expression':
91            case '#capturing':
92            case '#noncapturing':
93            case '#namedcapturing':
94                return $element->getChild(0)->accept($this, $handle, $eldnah);
95
96            case '#alternation':
97            case '#class':
98                return $element->getChild($this->_sampler->getInteger(
99                    0,
100                    $element->getChildrenNumber() - 1
101                ))->accept($this, $handle, $eldnah);
102
103            case '#concatenation':
104                $out = null;
105
106                foreach ($element->getChildren() as $child) {
107                    $out .= $child->accept($this, $handle, $eldnah);
108                }
109
110                return $out;
111
112            case '#quantification':
113                $out = null;
114                $xy  = $element->getChild(1)->getValueValue();
115                $x   = 0;
116                $y   = 0;
117
118                switch ($element->getChild(1)->getValueToken()) {
119                    case 'zero_or_one':
120                        $y = 1;
121
122                        break;
123
124                    case 'zero_or_more':
125                        $y = mt_rand(5, 8); // why not?
126
127                        break;
128
129                    case 'one_or_more':
130                        $x = 1;
131                        $y = mt_rand(5, 8); // why not?
132
133                        break;
134
135                    case 'exactly_n':
136                        $x = $y = (int) substr($xy, 1, -1);
137
138                        break;
139
140                    case 'n_to_m':
141                        $xy = explode(',', substr($xy, 1, -1));
142                        $x  = (int) trim($xy[0]);
143                        $y  = (int) trim($xy[1]);
144
145                        break;
146
147                    case 'n_or_more':
148                        $xy = explode(',', substr($xy, 1, -1));
149                        $x  = (int) trim($xy[0]);
150                        $y  = mt_rand($x + 5, $x + 8); // why not?
151
152                        break;
153                }
154
155                for (
156                    $i = 0, $max = $this->_sampler->getInteger($x, $y);
157                    $i < $max;
158                    ++$i
159                ) {
160                    $out .= $element->getChild(0)->accept(
161                        $this,
162                        $handle,
163                        $eldnah
164                    );
165                }
166
167                return $out;
168
169            case '#negativeclass':
170                $c = [];
171
172                foreach ($element->getChildren() as $child) {
173                    $c[Ustring::toCode(
174                        $child->accept($this, $handle, $eldnah)
175                    )] = true;
176                }
177
178                do {
179                    // all printable ASCII.
180                    $i = $this->_sampler->getInteger(32, 126);
181                } while (isset($c[$i]));
182
183                return Ustring::fromCode($i);
184
185            case '#range':
186                $out   = null;
187                $left  = $element->getChild(0)->accept($this, $handle, $eldnah);
188                $right = $element->getChild(1)->accept($this, $handle, $eldnah);
189
190                return
191                    Ustring::fromCode(
192                        $this->_sampler->getInteger(
193                            Ustring::toCode($left),
194                            Ustring::toCode($right)
195                        )
196                    );
197
198            case 'token':
199                $value = $element->getValueValue();
200
201                switch ($element->getValueToken()) {
202                    case 'character':
203                        $value = ltrim($value, '\\');
204
205                        switch ($value) {
206                            case 'a':
207                                return "\a";
208
209                            case 'e':
210                                return "\e";
211
212                            case 'f':
213                                return "\f";
214
215                            case 'n':
216                                return "\n";
217
218                            case 'r':
219                                return "\r";
220
221                            case 't':
222                                return "\t";
223
224                            default:
225                                return
226                                    Ustring::fromCode(
227                                        intval(
228                                            substr($value, 1)
229                                        )
230                                    );
231                        }
232
233                        break;
234
235                    case 'dynamic_character':
236                        $value = ltrim($value, '\\');
237
238                        switch ($value[0]) {
239                            case 'x':
240                                $value = trim($value, 'x{}');
241
242                                return Ustring::fromCode(
243                                    hexdec($value)
244                                );
245
246                            default:
247                                return Ustring::fromCode(octdec($value));
248                        }
249
250                        break;
251
252                    case 'character_type':
253                        $value = ltrim($value, '\\');
254
255                        if ('s' === $value) {
256                            $value = $this->_sampler->getInteger(0, 1) ? 'h' : 'v';
257                        }
258
259                        switch ($value) {
260                            case 'C':
261                                return $this->_sampler->getInteger(0, 127);
262
263                            case 'd':
264                                return $this->_sampler->getInteger(0, 9);
265
266                            case 'h':
267                                $h = [
268                                    Ustring::fromCode(0x0009),
269                                    Ustring::fromCode(0x0020),
270                                    Ustring::fromCode(0x00a0)
271                                ];
272
273                                return $h[$this->_sampler->getInteger(0, count($h) -1)];
274
275                            case 'v':
276                                $v = [
277                                    Ustring::fromCode(0x000a),
278                                    Ustring::fromCode(0x000b),
279                                    Ustring::fromCode(0x000c),
280                                    Ustring::fromCode(0x000d)
281                                ];
282
283                                return $v[$this->_sampler->getInteger(0, count($v) -1)];
284
285                            case 'w':
286                                $w  = array_merge(
287                                    range(0x41, 0x5a),
288                                    range(0x61, 0x7a),
289                                    [0x5f]
290                                );
291
292                                return Ustring::fromCode($w[$this->_sampler->getInteger(0, count($w) - 1)]);
293
294                            default:
295                                return '?';
296                        }
297
298                        break;
299
300                    case 'literal':
301                        if ('.' === $value) {
302                            $w  = array_merge(
303                                range(0x41, 0x5a),
304                                range(0x61, 0x7a),
305                                [0x5f]
306                            );
307
308                            return Ustring::fromCode($w[$this->_sampler->getInteger(0, count($w) - 1)]);
309                        }
310
311                        return
312                            str_replace(
313                                '\\\\',
314                                '\\',
315                                preg_replace(
316                                    '#\\\(?!\\\)#',
317                                    '',
318                                    $value
319                                )
320                            );
321                }
322
323                break;
324
325            case '#internal_options':
326                break;
327
328            default:
329                throw new Regex\Exception(
330                    'Unsupported node: %s.',
331                    0,
332                    $element->getId()
333                );
334        }
335
336        return;
337    }
338}
339