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.

Ll1.php 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014
  1. <?php
  2. /**
  3. * Hoa
  4. *
  5. *
  6. * @license
  7. *
  8. * New BSD License
  9. *
  10. * Copyright © 2007-2017, 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. !defined('GO') and define('GO', 'GO');
  40. !defined('__') and 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-2017 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. */
  314. public function __construct(
  315. array $skip,
  316. array $tokens,
  317. array $states,
  318. array $terminal,
  319. array $transitions,
  320. array $actions,
  321. array $names = []
  322. ) {
  323. $this->setSkip($skip);
  324. $this->setTokens($tokens);
  325. $this->setStates($states);
  326. $this->setTerminal($terminal);
  327. $this->setTransitions($transitions);
  328. $this->setActions($actions);
  329. $this->setNames($names);
  330. return;
  331. }
  332. /**
  333. * Compile a source code.
  334. *
  335. * @param string $in Source code.
  336. * @return void
  337. * @throws \Hoa\Compiler\Exception\FinalStateHasNotBeenReached
  338. * @throws \Hoa\Compiler\Exception\IllegalToken
  339. */
  340. public function compile($in)
  341. {
  342. $cacheId = md5($in);
  343. if (true === self::$_cacheEnabled &&
  344. true === array_key_exists($cacheId, self::$_cache)) {
  345. return self::$_cache[$cacheId];
  346. }
  347. $d = 0;
  348. $c = 0; // current automata.
  349. $_skip = array_flip($this->_skip);
  350. $_tokens = array_flip($this->_tokens[$c]);
  351. $_states = array_flip($this->_states[$c]);
  352. $_actions = [$c => 0];
  353. $nextChar = null;
  354. $nextToken = 0;
  355. $nextState = $_states['GO'];
  356. $nextAction = $_states['GO'];
  357. $this->line = $this->getInitialLine();
  358. $this->column = 0;
  359. $this->buffers = [];
  360. $line = $this->line;
  361. $column = $this->column;
  362. $this->pre($in);
  363. for ($i = 0, $max = strlen($in); $i <= $max; $i++) {
  364. //echo "\n---\n\n";
  365. // End of parsing (not automata).
  366. if ($i == $max) {
  367. while ($c > 0 &&
  368. in_array($this->_states[$c][$nextState], $this->_terminal[$c])) {
  369. list($c, $nextState, ) = array_pop($this->_stack);
  370. }
  371. if (in_array($this->_states[$c][$nextState], $this->_terminal[$c]) &&
  372. 0 === $c &&
  373. true === $this->end()) {
  374. //echo '*********** END REACHED **********' . "\n";
  375. if (true === self::$_cacheEnabled) {
  376. self::$_cache[$cacheId] = $this->getResult();
  377. }
  378. return true;
  379. }
  380. throw new Exception\FinalStateHasNotBeenReached(
  381. 'End of code has been reached but not correctly; ' .
  382. 'maybe your program is not complete?',
  383. 0
  384. );
  385. }
  386. $nextChar = $in[$i];
  387. // Skip.
  388. if (isset($_skip[$nextChar])) {
  389. if ("\n" === $nextChar) {
  390. $line++;
  391. $column = 0;
  392. } else {
  393. $column++;
  394. }
  395. continue;
  396. } else {
  397. $continue = false;
  398. $handle = substr($in, $i);
  399. foreach ($_skip as $sk => $e) {
  400. if ($sk[0] != '#') {
  401. continue;
  402. }
  403. $sk = str_replace('#', '\#', substr($sk, 1));
  404. if (0 != preg_match('#^(' . $sk . ')#u', $handle, $match)) {
  405. $strlen = strlen($match[1]);
  406. if ($strlen > 0) {
  407. if (false !== $offset = strrpos($match[1], "\n")) {
  408. $column = $strlen - $offset - 1;
  409. } else {
  410. $column += $strlen;
  411. }
  412. $line += substr_count($match[1], "\n");
  413. $i += $strlen - 1;
  414. $continue = true;
  415. break;
  416. }
  417. }
  418. }
  419. if (true === $continue) {
  420. continue;
  421. }
  422. }
  423. // Epsilon-transition.
  424. $epsilon = false;
  425. while (array_key_exists($nextToken, $this->_actions[$c][$nextState]) &&
  426. (
  427. (
  428. is_array($this->_actions[$c][$nextState][$nextToken]) &&
  429. 0 < $foo = $this->_actions[$c][$nextState][$nextToken][0]
  430. ) ||
  431. (
  432. is_int($this->_actions[$c][$nextState][$nextToken]) &&
  433. 0 < $foo = $this->_actions[$c][$nextState][$nextToken]
  434. )
  435. )
  436. ) {
  437. $epsilon = true;
  438. if ($_actions[$c] == 0) {
  439. //echo '*** Change automata (up to ' . ($foo - 1) . ')' . "\n";
  440. $this->_stack[$d] = [$c, $nextState, $nextToken];
  441. end($this->_stack);
  442. $c = $foo - 1;
  443. $_tokens = array_flip($this->_tokens[$c]);
  444. $_states = array_flip($this->_states[$c]);
  445. $nextState = $_states['GO'];
  446. $nextAction = $_states['GO'];
  447. $nextToken = 0;
  448. $_actions[$c] = 0;
  449. $d++;
  450. } elseif ($_actions[$c] == 2) {
  451. $_actions[$c] = 0;
  452. break;
  453. }
  454. }
  455. if (true === $epsilon) {
  456. $epsilon = false;
  457. $nextToken = false;
  458. }
  459. // Token.
  460. if (isset($_tokens[$nextChar])) {
  461. $token = $nextChar;
  462. $nextToken = $_tokens[$token];
  463. if ("\n" === $nextChar) {
  464. $line++;
  465. $column = 0;
  466. } else {
  467. $column++;
  468. }
  469. } else {
  470. $nextToken = false;
  471. $handle = substr($in, $i);
  472. foreach ($_tokens as $token => $e) {
  473. if ('#' !== $token[0]) {
  474. continue;
  475. }
  476. $ntoken = str_replace('#', '\#', substr($token, 1));
  477. if (0 != preg_match('#^(' . $ntoken . ')#u', $handle, $match)) {
  478. $strlen = strlen($match[1]);
  479. if ($strlen > 0) {
  480. if (false !== $offset = strrpos($match[1], "\n")) {
  481. $column = $strlen - $offset - 1;
  482. } else {
  483. $column += $strlen;
  484. }
  485. $nextChar = $match[1];
  486. $nextToken = $e;
  487. $i += $strlen - 1;
  488. $line += substr_count($match[1], "\n");
  489. break;
  490. }
  491. }
  492. }
  493. }
  494. /*
  495. echo '>>> Automata ' . $c . "\n" .
  496. '>>> Next state ' . $nextState . "\n" .
  497. '>>> Token ' . $token . "\n" .
  498. '>>> Next char ' . $nextChar . "\n";
  499. */
  500. // Got it!
  501. if (false !== $nextToken) {
  502. if (is_array($this->_actions[$c][$nextState][$nextToken])) {
  503. $nextAction = $this->_actions[$c][$nextState][$nextToken][1];
  504. } else {
  505. $nextAction = $this->_actions[$c][$nextState][$nextToken];
  506. }
  507. $nextState = $_states[$this->_transitions[$c][$nextState][$nextToken]];
  508. }
  509. // Oh :-(.
  510. if (false === $nextToken || $nextState === $_states['__']) {
  511. $pop = array_pop($this->_stack);
  512. $d--;
  513. // Go back to a parent automata.
  514. if ((in_array($this->_states[$c][$nextState], $this->_terminal[$c]) &&
  515. null !== $pop) ||
  516. ($nextState === $_states['__'] &&
  517. null !== $pop)) {
  518. //echo '!!! Change automata (down)' . "\n";
  519. list($c, $nextState, $nextToken) = $pop;
  520. $_actions[$c] = 2;
  521. $i -= strlen($nextChar);
  522. $_tokens = array_flip($this->_tokens[$c]);
  523. $_states = array_flip($this->_states[$c]);
  524. /*
  525. echo '!!! Automata ' . $c . "\n" .
  526. '!!! Next state ' . $nextState . "\n";
  527. */
  528. continue;
  529. }
  530. $error = explode("\n", $in);
  531. $error = $error[$this->line];
  532. throw new Exception\IllegalToken(
  533. 'Illegal token at line ' . ($this->line + 1) . ' and column ' .
  534. ($this->column + 1) . "\n" . $error . "\n" .
  535. str_repeat(' ', $this->column) . '↑',
  536. 1,
  537. [],
  538. $this->line + 1, $this->column + 1
  539. );
  540. }
  541. $this->line = $line;
  542. $this->column = $column;
  543. //echo '<<< Next state ' . $nextState . "\n";
  544. $this->buffers[-1] = $nextChar;
  545. // Special actions.
  546. if ($nextAction < 0) {
  547. $buffer = abs($nextAction);
  548. if (($buffer & 1) == 0) {
  549. $this->buffers[($buffer - 2) / 2] = null;
  550. } else {
  551. $buffer = ($buffer - 1) / 2;
  552. if (!(isset($this->buffers[$buffer]))) {
  553. $this->buffers[$buffer] = null;
  554. }
  555. $this->buffers[$buffer] .= $nextChar;
  556. }
  557. continue;
  558. }
  559. if (0 !== $nextAction) {
  560. $this->consume($nextAction);
  561. }
  562. }
  563. return;
  564. }
  565. /**
  566. * Consume actions.
  567. * Please, see the actions table definition to learn more.
  568. *
  569. * @param int $action Action.
  570. * @return void
  571. */
  572. abstract protected function consume($action);
  573. /**
  574. * Compute source code before compiling it.
  575. *
  576. * @param string &$in Source code.
  577. * @return void
  578. */
  579. protected function pre(&$in)
  580. {
  581. return;
  582. }
  583. /**
  584. * Verify compiler state when ending the source code.
  585. *
  586. * @return bool
  587. */
  588. protected function end()
  589. {
  590. return true;
  591. }
  592. /**
  593. * Get the result of the compiling.
  594. *
  595. * @return mixed
  596. */
  597. abstract public function getResult();
  598. /**
  599. * Set initial line.
  600. *
  601. * @param int $line Initial line.
  602. * @return int
  603. */
  604. public function setInitialLine($line)
  605. {
  606. $old = $this->_initialLine;
  607. $this->_initialLine = $line;
  608. return $old;
  609. }
  610. /**
  611. * Set tokens to skip.
  612. *
  613. * @param array $skip Skip.
  614. * @return array
  615. */
  616. public function setSkip(array $skip)
  617. {
  618. $old = $this->_skip;
  619. $this->_skip = $skip;
  620. return $old;
  621. }
  622. /**
  623. * Set tokens.
  624. *
  625. * @param array $tokens Tokens.
  626. * @return array
  627. */
  628. public function setTokens(array $tokens)
  629. {
  630. $old = $this->_tokens;
  631. $this->_tokens = $tokens;
  632. return $old;
  633. }
  634. /**
  635. * Set states.
  636. *
  637. * @param array $states States.
  638. * @return array
  639. */
  640. public function setStates(array $states)
  641. {
  642. $old = $this->_states;
  643. $this->_states = $states;
  644. return $old;
  645. }
  646. /**
  647. * Set terminal states.
  648. *
  649. * @param array $terminal Terminal states.
  650. * @return array
  651. */
  652. public function setTerminal(array $terminal)
  653. {
  654. $old = $this->_terminal;
  655. $this->_terminal = $terminal;
  656. return $old;
  657. }
  658. /**
  659. * Set transitions table.
  660. *
  661. * @param array $transitions Transitions table.
  662. * @return array
  663. */
  664. public function setTransitions(array $transitions)
  665. {
  666. $old = $this->_transitions;
  667. $this->_transitions = $transitions;
  668. return $old;
  669. }
  670. /**
  671. * Set actions table.
  672. *
  673. * @param array $actions Actions table.
  674. * @return array
  675. */
  676. public function setActions(array $actions)
  677. {
  678. foreach ($actions as $e => $automata) {
  679. foreach ($automata as $i => $state) {
  680. foreach ($state as $j => $token) {
  681. if (0 != preg_match('#^(\d+),(.*)$#', $token, $matches)) {
  682. $actions[$e][$i][$j] = [(int) $matches[1], $matches[2]];
  683. }
  684. }
  685. }
  686. }
  687. $old = $this->_actions;
  688. $this->_actions = $actions;
  689. return $old;
  690. }
  691. /**
  692. * Set names of automata.
  693. *
  694. * @param array $names Names of automata.
  695. * @return array
  696. */
  697. public function setNames(array $names)
  698. {
  699. $old = $this->_names;
  700. $this->_names = $names;
  701. return $old;
  702. }
  703. /**
  704. * Get initial line.
  705. *
  706. * @return int
  707. */
  708. public function getInitialLine()
  709. {
  710. return $this->_initialLine;
  711. }
  712. /**
  713. * Get skip tokens.
  714. *
  715. * @return array
  716. */
  717. public function getSkip()
  718. {
  719. return $this->_skip;
  720. }
  721. /**
  722. * Get tokens.
  723. *
  724. * @return array
  725. */
  726. public function getTokens()
  727. {
  728. return $this->_tokens;
  729. }
  730. /**
  731. * Get states.
  732. *
  733. * @return array
  734. */
  735. public function getStates()
  736. {
  737. return $this->_states;
  738. }
  739. /**
  740. * Get terminal states.
  741. *
  742. * @return array
  743. */
  744. public function getTerminal()
  745. {
  746. return $this->_terminal;
  747. }
  748. /**
  749. * Get transitions table.
  750. *
  751. * @return array
  752. */
  753. public function getTransitions()
  754. {
  755. return $this->_transitions;
  756. }
  757. /**
  758. * Get actions table.
  759. *
  760. * @return array
  761. */
  762. public function getActions()
  763. {
  764. return $this->_actions;
  765. }
  766. /**
  767. * Get names of automata.
  768. *
  769. * @return array
  770. */
  771. public function getNames()
  772. {
  773. return $this->_names;
  774. }
  775. /**
  776. * Enable cache
  777. *
  778. * @return bool
  779. */
  780. public static function enableCache()
  781. {
  782. $old = self::$_cacheEnabled;
  783. self::$_cacheEnabled = true;
  784. return $old;
  785. }
  786. /**
  787. * Disable cache
  788. *
  789. * @return bool
  790. */
  791. public static function disableCache()
  792. {
  793. $old = self::$_cacheEnabled;
  794. self::$_cacheEnabled = false;
  795. return $old;
  796. }
  797. /**
  798. * Transform automatas into DOT language.
  799. *
  800. * @return void
  801. */
  802. public function __toString()
  803. {
  804. $out =
  805. 'digraph ' . str_replace('\\', '', get_class($this)) . ' {' .
  806. "\n" .
  807. ' rankdir=LR;' . "\n" .
  808. ' label="Automata of ' .
  809. str_replace('\\', '\\\\', get_class($this)) . '";';
  810. $transitions = array_reverse($this->_transitions, true);
  811. foreach ($transitions as $e => $automata) {
  812. $out .=
  813. "\n\n" . ' subgraph cluster_' . $e . ' {' . "\n" .
  814. ' label="Automata #' . $e .
  815. (isset($this->_names[$e])
  816. ? ' (' . str_replace('"', '\\"', $this->_names[$e]) . ')'
  817. : '') .
  818. '";' . "\n";
  819. if (!empty($this->_terminal[$e])) {
  820. $out .=
  821. ' node[shape=doublecircle] "' . $e . '_' .
  822. implode('" "' . $e . '_', $this->_terminal[$e]) . '";' . "\n";
  823. }
  824. $out .= ' node[shape=circle];' . "\n";
  825. foreach ($this->_states[$e] as $i => $state) {
  826. $name = [];
  827. $label = $state;
  828. if (__ != $state) {
  829. foreach ($this->_transitions[$e][$i] as $j => $foo) {
  830. $ep = $this->_actions[$e][$i][$j];
  831. if (is_array($ep)) {
  832. $ep = $ep[0];
  833. }
  834. if (is_int($ep)) {
  835. $ep--;
  836. if (0 < $ep && !isset($name[$ep])) {
  837. $name[$ep] = $ep;
  838. }
  839. }
  840. }
  841. if (!empty($name)) {
  842. $label .= ' (' . implode(', ', $name) . ')';
  843. }
  844. $out .=
  845. ' "' . $e . '_' . $state . '" ' .
  846. '[label="' . $label . '"];' . "\n";
  847. }
  848. }
  849. foreach ($automata as $i => $transition) {
  850. $transition = array_reverse($transition, true);
  851. foreach ($transition as $j => $state) {
  852. if (__ != $this->_states[$e][$i]
  853. && __ != $state) {
  854. $label = str_replace('\\', '\\\\', $this->_tokens[$e][$j]);
  855. $label = str_replace('"', '\\"', $label);
  856. if ('#' === $label[0]) {
  857. $label = substr($label, 1);
  858. }
  859. $out .=
  860. ' "' . $e . '_' . $this->_states[$e][$i] .
  861. '" -> "' . $e . '_' . $state . '"' .
  862. ' [label="' . $label . '"];' . "\n";
  863. }
  864. }
  865. }
  866. $out .=
  867. ' node[shape=point,label=""] "' . $e . '_";' . "\n" .
  868. ' "' . $e . '_" -> "' . $e . '_GO";' . "\n" .
  869. ' }';
  870. }
  871. $out .= "\n" . '}' . "\n";
  872. return $out;
  873. }
  874. }