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