You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1016 lines
28 KiB

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