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;
38
39/**
40 * Define the __ constant, so useful in compiler :-).
41 */
42!defined('GO') and define('GO', 'GO');
43!defined('__') and define('__', '__');
44
45/**
46 * Class \Hoa\Compiler\Ll1.
47 *
48 * Provide an abstract LL(1) compiler, based on sub-automata and stacks.
49 *
50 * @copyright  Copyright © 2007-2017 Hoa community
51 * @license    New BSD License
52 */
53abstract class Ll1
54{
55    /**
56     * Initial line.
57     * If we try to compile a code inside another code, the initial line would
58     * not probably be 0.
59     *
60     * @var int
61     */
62    protected $_initialLine         = 0;
63
64    /**
65     * Tokens to skip (will be totally skip, no way to get it).
66     * Tokens' rules could be apply here (i.e. normal and special tokens are
67     * understood).
68     * Example:
69     *     [
70     *         '#\s+',           // white spaces
71     *         '#//.*',          // inline comment
72     *         '#/\*(.|\n)*\*\/' // block comment
73     *     ]
74     *
75     * @var array
76     */
77    protected $_skip                = [];
78
79    /**
80     * Tokens.
81     * A token should be:
82     *     * simple, it means just a single char, e.g. ':';
83     *     * special, strings and/or a regular expressions, and must begin with
84     *       a sharp (#), e.g. '#foobar', '#[a-zA-Z]' or '#foo?bar'.
85     * Note: if we want the token #, we should write '##'.
86     * PCRE expressions are fully-supported.
87     * We got an array of arrays because of sub-automata, one sub-array per
88     * sub-automaton.
89     * Example:
90     *     [
91     *         [
92     *             '{'  // open brack
93     *         ],
94     *         [
95     *             '"',            // quote
96     *             ':',            // semi-colon
97     *             ',',            // comma
98     *             '{',            // open bracket
99     *             '}'             // close bracket
100     *         ],
101     *         [
102     *             '#[a-z_\ \n]+", // id/string
103     *             '"'             // quote
104     *         ]
105     *     ]
106     *
107     * @var array
108     */
109    protected $_tokens              = [];
110
111    /**
112     * States.
113     * We got an array of arrays because of sub-automata, one sub-array per
114     * sub-automaton.
115     * Example:
116     *     [
117     *         [
118     *              __ , // error
119     *             'GO', // start
120     *             'OK'  // terminal
121     *         ],
122     *         [
123     *              __ , // error
124     *             'GO', // start
125     *             'KE', // key
126     *             'CO', // colon
127     *             'VA', // value
128     *             'BL', // block
129     *             'OK'  // terminal
130     *         ],
131     *         [
132     *              __ , // error
133     *             'GO', // start
134     *             'ST', // string
135     *             'OK'  // terminal
136     *         ]
137     *     ]
138     *
139     * Note: the constant GO or the string 'GO' must be used to represent the
140     *       initial state.
141     * Note: the constant __ or the string '__' must be used to represent the
142     *       null/unrecognized/error state.
143     *
144     * @var array
145     */
146    protected $_states              = [];
147
148    /**
149     * Terminal states (defined in the states set).
150     * We got an array of arrays because of sub-automata, one sub-array per
151     * sub-automaton.
152     * Example:
153     *     [
154     *         ['OK'],
155     *         ['OK'],
156     *         ['OK']
157     *     ]
158     *
159     * @var array
160     */
161    protected $_terminal            = [];
162
163    /**
164     * Transitions table.
165     * It's actually a matrix, such as: TT(TOKENS × STATES).
166     * We got an array of arrays because of sub-automata, one sub-array per
167     * sub-automaton.
168     * Example:
169     *     [
170     *        [
171     *                   {
172     *             __  [ __ ],
173     *             GO  ['OK'],
174     *             OK  [ __ ],
175     *         ),
176     *         [
177     *                   "     :     ,     {     }
178     *             __  [ __ ,  __ ,  __ ,  __ ,  __ ],
179     *             GO  ['KE',  __ ,  __ ,  __ , 'OK'],
180     *             KE  [ __ , 'CO',  __ ,  __ ,  __ ],
181     *             CO  ['VA',  __ ,  __ , 'BL',  __ ],
182     *             VA  [ __ ,  __ , 'GO',  __ , 'OK'],
183     *             BL  [ __ ,  __ , 'GO',  __ , 'OK'],
184     *             OK  [ __ ,  __ ,  __ ,  __ ,  __ ]
185     *         ],
186     *         [
187     *                   id    "
188     *             __  [ __ ,  __ ],
189     *             GO  ['ST', 'OK'],
190     *             ST  [ __ , 'OK'],
191     *             OK  [ __ ,  __ ]
192     *         ]
193     *     )
194     *
195     * Note: tokens and states should be declared in the strict same order as
196     *       defined previously.
197     *
198     * @var array
199     */
200    protected $_transitions         = [];
201
202    /**
203     * Actions table.
204     * It's actually a matrix, such as: AT(TOKENS × STATES).
205     * We got an array of arrays because of sub-automata, one sub-array per
206     * sub-automaton.
207     * Example:
208     *     [
209     *         [
210     *                   {
211     *             __  [ 0 ],
212     *             GO  [ 0 ],
213     *             OK  [ 2 ],
214     *         ],
215     *         [
216     *                   "   :    ,    {    }
217     *             __  [ 0,  0 ,  0 ,  0 ,  0 ],
218     *             GO  [ 0,  0 ,  0 ,  0 , 'd'],
219     *             KE  [ 3, 'k',  0 ,  0 ,  0 ],
220     *             CO  [ 0,  0 ,  0 , 'u',  0 ],
221     *             VA  [ 3,  0 , 'v',  0 , 'x'],
222     *             BL  [ 0,  0 ,  0 ,  2 , 'd'],
223     *             OK  [ 0,  0 ,  0 ,  0 ,  0 ]
224     *         ],
225     *         [
226     *                   id  "
227     *             __  [ 0, 0 ],
228     *             GO  [-1, 0 ],
229     *             ST  [ 0, 0 ],
230     *             OK  [ 0, 0 ]
231     *         ]
232     *     ]
233     *
234     * AT is filled with integer or char n.
235     * If n is a char, it means an action.
236     * If n < 0, it means a special action.
237     * If n = 0, it means not action.
238     * If n > 0, it means a link to a sub-automata (sub-automata index + 1).
239     *
240     * When we write our consume() method, it's just a simple switch receiving
241     * an action. It receives only character. It's like a “goto” in our
242     * compiler, and allows us to execute code when skiming through the graph.
243     *
244     * Negative/special actions are used to auto-fill or empty buffers.
245     * E.g: -1 will fill the buffer 0, -2 will empty the buffer 0,
246     *      -3 will fill the buffer 1, -4 will empty the buffer 1,
247     *      -5 will fill the buffer 2, -6 will empty the buffer 2 etc.
248     * A formula appears:
249     *      y = |x|
250     *      fill  buffer (x - 2) / 2 if x & 1 = 1
251     *      empty buffer (x - 1) / 2 if x & 1 = 0
252     *
253     * Positive/link actions are used to make an epsilon-transition (or a link)
254     * between two sub-automata.
255     * Sub-automata are indexed from 0, but our links must be index + 1. Example
256     * given: the sub-automata 0 in our example has a link to the sub-automata 1
257     * through OK[{] = 2. Take attention to this :-).
258     * And another thing which must be carefully studying is the place of the
259     * link. For example, with our sub-automata 1 (the big one), we have an
260     * epsilon-transition to the sub-automata 2 through VA["] = 3. It means:
261     * when we arrived in the state VA from the token ", we go in the
262     * sub-automata 3 (the 2nd one actually). And when the linked sub-automata
263     * has finished, we are back in our state and continue our parsing. Take
264     * care of this :-).
265     *
266     * Finally, it is possible to combine positive and char action, separated by
267     a comma. Thus: 7,f is equivalent to make an epsilon-transition to the
268     * automata 7, then consume the action f.
269     *
270     * @var array
271     */
272    protected $_actions             = [];
273
274    /**
275     * Names of automata.
276     */
277    protected $_names               = [];
278
279    /**
280     * Recursive stack.
281     *
282     * @var array
283     */
284    private $_stack                 = [];
285
286    /**
287     * Buffers.
288     *
289     * @var array
290     */
291    protected $buffers              = [];
292
293    /**
294     * Current token's line.
295     *
296     * @var int
297     */
298    protected $line                 = 0;
299
300    /**
301     * Current token's column.
302     *
303     * @var int
304     */
305    protected $column               = 0;
306
307    /**
308     * Cache compiling result.
309     *
310     * @var array
311     */
312    protected static $_cache        = [];
313
314    /**
315     * Whether cache is enabled or not.
316     *
317     * @var bool
318     */
319    protected static $_cacheEnabled = true;
320
321
322
323    /**
324     * Singleton, and set parameters.
325     *
326     * @param   array   $skip           Skip.
327     * @param   array   $tokens         Tokens.
328     * @param   array   $states         States.
329     * @param   array   $terminal       Terminal states.
330     * @param   array   $transitions    Transitions table.
331     * @param   array   $actions        Actions table.
332     * @param   array   $names          Names of automata.
333     */
334    public function __construct(
335        array $skip,
336        array $tokens,
337        array $states,
338        array $terminal,
339        array $transitions,
340        array $actions,
341        array $names = []
342    ) {
343        $this->setSkip($skip);
344        $this->setTokens($tokens);
345        $this->setStates($states);
346        $this->setTerminal($terminal);
347        $this->setTransitions($transitions);
348        $this->setActions($actions);
349        $this->setNames($names);
350
351        return;
352    }
353
354    /**
355     * Compile a source code.
356     *
357     * @param   string  $in    Source code.
358     * @return  void
359     * @throws  \Hoa\Compiler\Exception\FinalStateHasNotBeenReached
360     * @throws  \Hoa\Compiler\Exception\IllegalToken
361     */
362    public function compile($in)
363    {
364        $cacheId = md5($in);
365
366        if (true === self::$_cacheEnabled &&
367            true === array_key_exists($cacheId, self::$_cache)) {
368            return self::$_cache[$cacheId];
369        }
370
371        $d             = 0;
372        $c             = 0; // current automata.
373        $_skip         = array_flip($this->_skip);
374        $_tokens       = array_flip($this->_tokens[$c]);
375        $_states       = array_flip($this->_states[$c]);
376        $_actions      = [$c => 0];
377
378        $nextChar      = null;
379        $nextToken     = 0;
380        $nextState     = $_states['GO'];
381        $nextAction    = $_states['GO'];
382
383        $this->line    = $this->getInitialLine();
384        $this->column  = 0;
385
386        $this->buffers = [];
387
388        $line          = $this->line;
389        $column        = $this->column;
390
391        $this->pre($in);
392
393        for ($i = 0, $max = strlen($in); $i <= $max; $i++) {
394
395            //echo "\n---\n\n";
396
397            // End of parsing (not automata).
398            if ($i == $max) {
399                while ($c > 0 &&
400                       in_array($this->_states[$c][$nextState], $this->_terminal[$c])) {
401                    list($c, $nextState, ) = array_pop($this->_stack);
402                }
403
404                if (in_array($this->_states[$c][$nextState], $this->_terminal[$c]) &&
405                    0    === $c &&
406                    true === $this->end()) {
407
408                    //echo '*********** END REACHED **********' . "\n";
409
410                    if (true === self::$_cacheEnabled) {
411                        self::$_cache[$cacheId] = $this->getResult();
412                    }
413
414                    return true;
415                }
416
417                throw new Exception\FinalStateHasNotBeenReached(
418                    'End of code has been reached but not correctly; ' .
419                    'maybe your program is not complete?',
420                    0
421                );
422            }
423
424            $nextChar = $in[$i];
425
426            // Skip.
427            if (isset($_skip[$nextChar])) {
428                if ("\n" === $nextChar) {
429                    $line++;
430                    $column = 0;
431                } else {
432                    $column++;
433                }
434
435                continue;
436            } else {
437                $continue = false;
438                $handle   = substr($in, $i);
439
440                foreach ($_skip as $sk => $e) {
441                    if ($sk[0] != '#') {
442                        continue;
443                    }
444
445                    $sk = str_replace('#', '\#', substr($sk, 1));
446
447                    if (0 != preg_match('#^(' . $sk . ')#u', $handle, $match)) {
448                        $strlen = strlen($match[1]);
449
450                        if ($strlen > 0) {
451                            if (false !== $offset = strrpos($match[1], "\n")) {
452                                $column  = $strlen - $offset - 1;
453                            } else {
454                                $column += $strlen;
455                            }
456
457                            $line     += substr_count($match[1], "\n");
458                            $i        += $strlen - 1;
459                            $continue  = true;
460
461                            break;
462                        }
463                    }
464                }
465
466                if (true === $continue) {
467                    continue;
468                }
469            }
470
471            // Epsilon-transition.
472            $epsilon = false;
473            while (array_key_exists($nextToken, $this->_actions[$c][$nextState]) &&
474                     (
475                        (
476                            is_array($this->_actions[$c][$nextState][$nextToken]) &&
477                            0 < $foo = $this->_actions[$c][$nextState][$nextToken][0]
478                        ) ||
479                        (
480                            is_int($this->_actions[$c][$nextState][$nextToken]) &&
481                            0 < $foo = $this->_actions[$c][$nextState][$nextToken]
482                        )
483                     )
484                  ) {
485                $epsilon = true;
486
487                if ($_actions[$c] == 0) {
488
489                    //echo '*** Change automata (up to ' . ($foo - 1) . ')' . "\n";
490
491                    $this->_stack[$d] = [$c, $nextState, $nextToken];
492                    end($this->_stack);
493
494                    $c            = $foo - 1;
495                    $_tokens      = array_flip($this->_tokens[$c]);
496                    $_states      = array_flip($this->_states[$c]);
497
498                    $nextState    = $_states['GO'];
499                    $nextAction   = $_states['GO'];
500                    $nextToken    = 0;
501
502                    $_actions[$c] = 0;
503
504                    $d++;
505                } elseif ($_actions[$c] == 2) {
506                    $_actions[$c] = 0;
507
508                    break;
509                }
510            }
511
512            if (true === $epsilon) {
513                $epsilon   = false;
514                $nextToken = false;
515            }
516
517            // Token.
518            if (isset($_tokens[$nextChar])) {
519                $token      = $nextChar;
520                $nextToken  = $_tokens[$token];
521
522                if ("\n" === $nextChar) {
523                    $line++;
524                    $column = 0;
525                } else {
526                    $column++;
527                }
528            } else {
529                $nextToken = false;
530                $handle    = substr($in, $i);
531
532                foreach ($_tokens as $token => $e) {
533                    if ('#' !== $token[0]) {
534                        continue;
535                    }
536
537                    $ntoken = str_replace('#', '\#', substr($token, 1));
538
539                    if (0 != preg_match('#^(' . $ntoken . ')#u', $handle, $match)) {
540                        $strlen = strlen($match[1]);
541
542                        if ($strlen > 0) {
543                            if (false !== $offset = strrpos($match[1], "\n")) {
544                                $column  = $strlen - $offset - 1;
545                            } else {
546                                $column += $strlen;
547                            }
548
549                            $nextChar   = $match[1];
550                            $nextToken  = $e;
551                            $i         += $strlen - 1;
552                            $line      += substr_count($match[1], "\n");
553
554                            break;
555                        }
556                    }
557                }
558            }
559
560            /*
561            echo '>>> Automata   ' . $c . "\n" .
562                 '>>> Next state ' . $nextState . "\n" .
563                 '>>> Token      ' . $token . "\n" .
564                 '>>> Next char  ' . $nextChar . "\n";
565            */
566
567            // Got it!
568            if (false !== $nextToken) {
569                if (is_array($this->_actions[$c][$nextState][$nextToken])) {
570                    $nextAction = $this->_actions[$c][$nextState][$nextToken][1];
571                } else {
572                    $nextAction = $this->_actions[$c][$nextState][$nextToken];
573                }
574                $nextState      = $_states[$this->_transitions[$c][$nextState][$nextToken]];
575            }
576
577            // Oh :-(.
578            if (false === $nextToken || $nextState === $_states['__']) {
579                $pop = array_pop($this->_stack);
580                $d--;
581
582                // Go back to a parent automata.
583                if ((in_array($this->_states[$c][$nextState], $this->_terminal[$c]) &&
584                     null !== $pop) ||
585                    ($nextState === $_states['__'] &&
586                     null !== $pop)) {
587
588                    //echo '!!! Change automata (down)' . "\n";
589
590                    list($c, $nextState, $nextToken) = $pop;
591
592                    $_actions[$c]  = 2;
593
594                    $i            -= strlen($nextChar);
595                    $_tokens       = array_flip($this->_tokens[$c]);
596                    $_states       = array_flip($this->_states[$c]);
597
598                    /*
599                    echo '!!! Automata   ' . $c . "\n" .
600                         '!!! Next state ' . $nextState . "\n";
601                    */
602
603                    continue;
604                }
605
606                $error = explode("\n", $in);
607                $error = $error[$this->line];
608
609                throw new Exception\IllegalToken(
610                    'Illegal token at line ' . ($this->line + 1) . ' and column ' .
611                    ($this->column + 1) . "\n" . $error . "\n" .
612                    str_repeat(' ', $this->column) . '↑',
613                    1,
614                    [],
615                    $this->line + 1, $this->column + 1
616                );
617            }
618
619            $this->line   = $line;
620            $this->column = $column;
621
622            //echo '<<< Next state ' . $nextState . "\n";
623
624            $this->buffers[-1] = $nextChar;
625
626            // Special actions.
627            if ($nextAction < 0) {
628                $buffer = abs($nextAction);
629
630                if (($buffer & 1) == 0) {
631                    $this->buffers[($buffer - 2) / 2] = null;
632                } else {
633                    $buffer = ($buffer - 1) / 2;
634
635                    if (!(isset($this->buffers[$buffer]))) {
636                        $this->buffers[$buffer] = null;
637                    }
638
639                    $this->buffers[$buffer] .= $nextChar;
640                }
641
642                continue;
643            }
644
645            if (0 !== $nextAction) {
646                $this->consume($nextAction);
647            }
648        }
649
650        return;
651    }
652
653    /**
654     * Consume actions.
655     * Please, see the actions table definition to learn more.
656     *
657     * @param   int  $action    Action.
658     * @return  void
659     */
660    abstract protected function consume($action);
661
662    /**
663     * Compute source code before compiling it.
664     *
665     * @param   string  &$in    Source code.
666     * @return  void
667     */
668    protected function pre(&$in)
669    {
670        return;
671    }
672
673    /**
674     * Verify compiler state when ending the source code.
675     *
676     * @return  bool
677     */
678    protected function end()
679    {
680        return true;
681    }
682
683    /**
684     * Get the result of the compiling.
685     *
686     * @return  mixed
687     */
688    abstract public function getResult();
689
690    /**
691     * Set initial line.
692     *
693     * @param   int     $line    Initial line.
694     * @return  int
695     */
696    public function setInitialLine($line)
697    {
698        $old                = $this->_initialLine;
699        $this->_initialLine = $line;
700
701        return $old;
702    }
703
704    /**
705     * Set tokens to skip.
706     *
707     * @param   array   $skip    Skip.
708     * @return  array
709     */
710    public function setSkip(array $skip)
711    {
712        $old         = $this->_skip;
713        $this->_skip = $skip;
714
715        return $old;
716    }
717
718
719    /**
720     * Set tokens.
721     *
722     * @param   array   $tokens    Tokens.
723     * @return  array
724     */
725    public function setTokens(array $tokens)
726    {
727        $old           = $this->_tokens;
728        $this->_tokens = $tokens;
729
730        return $old;
731    }
732
733    /**
734     * Set states.
735     *
736     * @param   array   $states    States.
737     * @return  array
738     */
739    public function setStates(array $states)
740    {
741        $old           = $this->_states;
742        $this->_states = $states;
743
744        return $old;
745    }
746
747    /**
748     * Set terminal states.
749     *
750     * @param   array   $terminal    Terminal states.
751     * @return  array
752     */
753    public function setTerminal(array $terminal)
754    {
755        $old             = $this->_terminal;
756        $this->_terminal = $terminal;
757
758        return $old;
759    }
760
761    /**
762     * Set transitions table.
763     *
764     * @param   array   $transitions    Transitions table.
765     * @return  array
766     */
767    public function setTransitions(array $transitions)
768    {
769        $old                = $this->_transitions;
770        $this->_transitions = $transitions;
771
772        return $old;
773    }
774
775    /**
776     * Set actions table.
777     *
778     * @param   array   $actions    Actions table.
779     * @return  array
780     */
781    public function setActions(array $actions)
782    {
783        foreach ($actions as $e => $automata) {
784            foreach ($automata as $i => $state) {
785                foreach ($state as $j => $token) {
786                    if (0 != preg_match('#^(\d+),(.*)$#', $token, $matches)) {
787                        $actions[$e][$i][$j] = [(int) $matches[1], $matches[2]];
788                    }
789                }
790            }
791        }
792
793        $old            = $this->_actions;
794        $this->_actions = $actions;
795
796        return $old;
797    }
798
799    /**
800     * Set names of automata.
801     *
802     * @param   array   $names    Names of automata.
803     * @return  array
804     */
805    public function setNames(array $names)
806    {
807        $old          = $this->_names;
808        $this->_names = $names;
809
810        return $old;
811    }
812
813    /**
814     * Get initial line.
815     *
816     * @return  int
817     */
818    public function getInitialLine()
819    {
820        return $this->_initialLine;
821    }
822
823    /**
824     * Get skip tokens.
825     *
826     * @return  array
827     */
828    public function getSkip()
829    {
830        return $this->_skip;
831    }
832
833    /**
834     * Get tokens.
835     *
836     * @return  array
837     */
838    public function getTokens()
839    {
840        return $this->_tokens;
841    }
842
843    /**
844     * Get states.
845     *
846     * @return  array
847     */
848    public function getStates()
849    {
850        return $this->_states;
851    }
852
853    /**
854     * Get terminal states.
855     *
856     * @return  array
857     */
858    public function getTerminal()
859    {
860        return $this->_terminal;
861    }
862
863    /**
864     * Get transitions table.
865     *
866     * @return  array
867     */
868    public function getTransitions()
869    {
870        return $this->_transitions;
871    }
872
873    /**
874     * Get actions table.
875     *
876     * @return  array
877     */
878    public function getActions()
879    {
880        return $this->_actions;
881    }
882
883    /**
884     * Get names of automata.
885     *
886     * @return  array
887     */
888    public function getNames()
889    {
890        return $this->_names;
891    }
892
893    /**
894     * Enable cache
895     *
896     * @return  bool
897     */
898    public static function enableCache()
899    {
900        $old                 = self::$_cacheEnabled;
901        self::$_cacheEnabled = true;
902
903        return $old;
904    }
905
906    /**
907     * Disable cache
908     *
909     * @return  bool
910     */
911    public static function disableCache()
912    {
913        $old                 = self::$_cacheEnabled;
914        self::$_cacheEnabled = false;
915
916        return $old;
917    }
918
919    /**
920     * Transform automatas into DOT language.
921     *
922     * @return  void
923     */
924    public function __toString()
925    {
926        $out =
927            'digraph ' . str_replace('\\', '', get_class($this)) . ' {' .
928            "\n" .
929            '    rankdir=LR;' . "\n" .
930            '    label="Automata of ' .
931            str_replace('\\', '\\\\', get_class($this)) . '";';
932
933        $transitions = array_reverse($this->_transitions, true);
934
935        foreach ($transitions as $e => $automata) {
936            $out .=
937                "\n\n" . '    subgraph cluster_' . $e . ' {' . "\n" .
938                '        label="Automata #' . $e .
939                (isset($this->_names[$e])
940                    ? ' (' . str_replace('"', '\\"', $this->_names[$e]) . ')'
941                    : '') .
942                '";' . "\n";
943
944            if (!empty($this->_terminal[$e])) {
945                $out .=
946                    '        node[shape=doublecircle] "' . $e . '_' .
947                    implode('" "' . $e . '_', $this->_terminal[$e]) . '";' . "\n";
948            }
949
950            $out .= '        node[shape=circle];' . "\n";
951
952            foreach ($this->_states[$e] as $i => $state) {
953                $name  = [];
954                $label = $state;
955
956                if (__ != $state) {
957                    foreach ($this->_transitions[$e][$i] as $j => $foo) {
958                        $ep = $this->_actions[$e][$i][$j];
959
960                        if (is_array($ep)) {
961                            $ep = $ep[0];
962                        }
963
964                        if (is_int($ep)) {
965                            $ep--;
966
967                            if (0 < $ep && !isset($name[$ep])) {
968                                $name[$ep] = $ep;
969                            }
970                        }
971                    }
972
973                    if (!empty($name)) {
974                        $label .= ' (' . implode(', ', $name) . ')';
975                    }
976
977                    $out .=
978                        '        "' . $e . '_' . $state . '" ' .
979                        '[label="' . $label . '"];' . "\n";
980                }
981            }
982
983            foreach ($automata as $i => $transition) {
984                $transition = array_reverse($transition, true);
985
986                foreach ($transition as $j => $state) {
987                    if (__ != $this->_states[$e][$i]
988                       && __ != $state) {
989                        $label = str_replace('\\', '\\\\', $this->_tokens[$e][$j]);
990                        $label = str_replace('"', '\\"', $label);
991
992                        if ('#' === $label[0]) {
993                            $label = substr($label, 1);
994                        }
995
996                        $out .=
997                            '        "' . $e . '_' . $this->_states[$e][$i] .
998                            '" -> "' . $e . '_' . $state . '"' .
999                            ' [label="' . $label . '"];' . "\n";
1000                    }
1001                }
1002            }
1003
1004            $out .=
1005                '        node[shape=point,label=""] "' . $e . '_";' . "\n" .
1006                '        "' . $e . '_" -> "' . $e . '_GO";' . "\n" .
1007                '    }';
1008        }
1009
1010        $out .= "\n" . '}' . "\n";
1011
1012        return $out;
1013    }
1014}
1015