Parser.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. <?php
  2. /**
  3. * PHP_ParserGenerator, a php 5 parser generator.
  4. *
  5. * This is a direct port of the Lemon parser generator, found at
  6. * {@link http://www.hwaci.com/sw/lemon/}
  7. *
  8. * PHP version 5
  9. *
  10. * LICENSE: This source file is subject to version 3.01 of the PHP license
  11. * that is available through the world-wide-web at the following URI:
  12. * http://www.php.net/license/3_01.txt. If you did not receive a copy of
  13. * the PHP License and are unable to obtain it through the web, please
  14. * send a note to license@php.net so we can mail you a copy immediately.
  15. *
  16. * @category PHP
  17. * @package PHP_ParserGenerator
  18. * @author Gregory Beaver <cellog@php.net>
  19. * @copyright 2006 Gregory Beaver
  20. * @license http://www.php.net/license/3_01.txt PHP License 3.01
  21. * @version CVS: $Id: Parser.php 302382 2010-08-17 06:08:09Z jespino $
  22. * @link http://pear.php.net/package/PHP_ParserGenerator
  23. * @since File available since Release 0.1.0
  24. */
  25. /**
  26. * The grammar parser for lemon grammar files.
  27. *
  28. * @category PHP
  29. * @package PHP_ParserGenerator
  30. * @author Gregory Beaver <cellog@php.net>
  31. * @copyright 2006 Gregory Beaver
  32. * @license http://www.php.net/license/3_01.txt PHP License 3.01
  33. * @link http://pear.php.net/package/PHP_ParserGenerator
  34. * @since Class available since Release 0.1.0
  35. */
  36. class PHP_ParserGenerator_Parser
  37. {
  38. const INITIALIZE = 1;
  39. const WAITING_FOR_DECL_OR_RULE = 2;
  40. const WAITING_FOR_DECL_KEYWORD = 3;
  41. const WAITING_FOR_DECL_ARG = 4;
  42. const WAITING_FOR_PRECEDENCE_SYMBOL = 5;
  43. const WAITING_FOR_ARROW = 6;
  44. const IN_RHS = 7;
  45. const LHS_ALIAS_1 = 8;
  46. const LHS_ALIAS_2 = 9;
  47. const LHS_ALIAS_3 = 10;
  48. const RHS_ALIAS_1 = 11;
  49. const RHS_ALIAS_2 = 12;
  50. const PRECEDENCE_MARK_1 = 13;
  51. const PRECEDENCE_MARK_2 = 14;
  52. const RESYNC_AFTER_RULE_ERROR = 15;
  53. const RESYNC_AFTER_DECL_ERROR = 16;
  54. const WAITING_FOR_DESTRUCTOR_SYMBOL = 17;
  55. const WAITING_FOR_DATATYPE_SYMBOL = 18;
  56. const WAITING_FOR_FALLBACK_ID = 19;
  57. /**
  58. * Name of the input file
  59. *
  60. * @var string
  61. */
  62. public $filename;
  63. /**
  64. * Linenumber at which current token starts
  65. * @var int
  66. */
  67. public $tokenlineno;
  68. /**
  69. * Number of parsing errors so far
  70. * @var int
  71. */
  72. public $errorcnt;
  73. /**
  74. * Index of current token within the input string
  75. * @var int
  76. */
  77. public $tokenstart;
  78. /**
  79. * Global state vector
  80. * @var PHP_ParserGenerator_Data
  81. */
  82. public $gp;
  83. /**
  84. * Parser state (one of the class constants for this class)
  85. *
  86. * - PHP_ParserGenerator_Parser::INITIALIZE,
  87. * - PHP_ParserGenerator_Parser::WAITING_FOR_DECL_OR_RULE,
  88. * - PHP_ParserGenerator_Parser::WAITING_FOR_DECL_KEYWORD,
  89. * - PHP_ParserGenerator_Parser::WAITING_FOR_DECL_ARG,
  90. * - PHP_ParserGenerator_Parser::WAITING_FOR_PRECEDENCE_SYMBOL,
  91. * - PHP_ParserGenerator_Parser::WAITING_FOR_ARROW,
  92. * - PHP_ParserGenerator_Parser::IN_RHS,
  93. * - PHP_ParserGenerator_Parser::LHS_ALIAS_1,
  94. * - PHP_ParserGenerator_Parser::LHS_ALIAS_2,
  95. * - PHP_ParserGenerator_Parser::LHS_ALIAS_3,
  96. * - PHP_ParserGenerator_Parser::RHS_ALIAS_1,
  97. * - PHP_ParserGenerator_Parser::RHS_ALIAS_2,
  98. * - PHP_ParserGenerator_Parser::PRECEDENCE_MARK_1,
  99. * - PHP_ParserGenerator_Parser::PRECEDENCE_MARK_2,
  100. * - PHP_ParserGenerator_Parser::RESYNC_AFTER_RULE_ERROR,
  101. * - PHP_ParserGenerator_Parser::RESYNC_AFTER_DECL_ERROR,
  102. * - PHP_ParserGenerator_Parser::WAITING_FOR_DESTRUCTOR_SYMBOL,
  103. * - PHP_ParserGenerator_Parser::WAITING_FOR_DATATYPE_SYMBOL,
  104. * - PHP_ParserGenerator_Parser::WAITING_FOR_FALLBACK_ID
  105. * @var int
  106. */
  107. public $state;
  108. /**
  109. * The fallback token
  110. * @var PHP_ParserGenerator_Symbol
  111. */
  112. public $fallback;
  113. /**
  114. * Left-hand side of the current rule
  115. * @var PHP_ParserGenerator_Symbol
  116. */
  117. public $lhs;
  118. /**
  119. * Alias for the LHS
  120. * @var string
  121. */
  122. public $lhsalias;
  123. /**
  124. * Number of right-hand side symbols seen
  125. * @var int
  126. */
  127. public $nrhs;
  128. /**
  129. * Right-hand side symbols
  130. * @var array array of {@link PHP_ParserGenerator_Symbol} objects
  131. */
  132. public $rhs = array();
  133. /**
  134. * Aliases for each RHS symbol name (or NULL)
  135. * @var array array of strings
  136. */
  137. public $alias = array();
  138. /**
  139. * Previous rule parsed
  140. * @var PHP_ParserGenerator_Rule
  141. */
  142. public $prevrule;
  143. /**
  144. * Keyword of a declaration
  145. *
  146. * This is one of the %keyword keywords in the grammar file
  147. * @var string
  148. */
  149. public $declkeyword;
  150. /**
  151. * Where the declaration argument should be put
  152. *
  153. * This is assigned as a reference to an internal variable
  154. * @var mixed
  155. */
  156. public $declargslot = array();
  157. /**
  158. * Where the declaration linenumber is put
  159. *
  160. * This is assigned as a reference to an internal variable
  161. * @var mixed
  162. */
  163. public $decllnslot;
  164. /*enum e_assoc*/
  165. public $declassoc; /* Assign this association to decl arguments */
  166. public $preccounter; /* Assign this precedence to decl arguments */
  167. /**
  168. * @var PHP_ParserGenerator_Rule
  169. */
  170. public $firstrule; /* Pointer to first rule in the grammar */
  171. /**
  172. * @var PHP_ParserGenerator_Rule
  173. */
  174. public $lastrule; /* Pointer to the most recently parsed rule */
  175. /**
  176. * @var PHP_ParserGenerator
  177. */
  178. private $lemon;
  179. function __construct(PHP_ParserGenerator $lem)
  180. {
  181. $this->lemon = $lem;
  182. }
  183. /**
  184. * Run the preprocessor over the input file text. The Lemon variable
  185. * $azDefine contains the names of all defined
  186. * macros. This routine looks for "%ifdef" and "%ifndef" and "%endif" and
  187. * comments them out. Text in between is also commented out as appropriate.
  188. * @param string
  189. */
  190. private function preprocess_input(&$z)
  191. {
  192. $lineno = $exclude = 0;
  193. for ($i=0; $i < strlen($z); $i++) {
  194. if ($z[$i] == "\n") {
  195. $lineno++;
  196. }
  197. if ($z[$i] != '%' || ($i > 0 && $z[$i-1] != "\n")) {
  198. continue;
  199. }
  200. if (substr($z, $i, 6) === "%endif" && trim($z[$i+6]) === '') {
  201. if ($exclude) {
  202. $exclude--;
  203. if ($exclude === 0) {
  204. for ($j = $start; $j < $i; $j++) {
  205. if ($z[$j] != "\n") $z[$j] = ' ';
  206. }
  207. }
  208. }
  209. for ($j = $i; $j < strlen($z) && $z[$j] != "\n"; $j++) {
  210. $z[$j] = ' ';
  211. }
  212. } elseif (substr($z, $i, 6) === "%ifdef" && trim($z[$i+6]) === '' ||
  213. substr($z, $i, 7) === "%ifndef" && trim($z[$i+7]) === '') {
  214. if ($exclude) {
  215. $exclude++;
  216. } else {
  217. $j = $i;
  218. $n = strtok(substr($z, $j), " \t");
  219. $exclude = 1;
  220. if (isset($this->lemon->azDefine[$n])) {
  221. $exclude = 0;
  222. }
  223. if ($z[$i + 3]=='n') {
  224. // this is a rather obtuse way of checking whether this is %ifndef
  225. $exclude = !$exclude;
  226. }
  227. if ($exclude) {
  228. $start = $i;
  229. $start_lineno = $lineno;
  230. }
  231. }
  232. //for ($j = $i; $j < strlen($z) && $z[$j] != "\n"; $j++) $z[$j] = ' ';
  233. $j = strpos(substr($z, $i), "\n");
  234. if ($j === false) {
  235. $z = substr($z, 0, $i); // remove instead of adding ' '
  236. } else {
  237. $z = substr($z, 0, $i) . substr($z, $i + $j); // remove instead of adding ' '
  238. }
  239. }
  240. }
  241. if ($exclude) {
  242. throw new Exception("unterminated %ifdef starting on line $start_lineno\n");
  243. }
  244. }
  245. /**
  246. * In spite of its name, this function is really a scanner.
  247. *
  248. * It reads in the entire input file (all at once) then tokenizes it.
  249. * Each token is passed to the function "parseonetoken" which builds all
  250. * the appropriate data structures in the global state vector "gp".
  251. * @param PHP_ParserGenerator_Data
  252. */
  253. function Parse(PHP_ParserGenerator_Data $gp)
  254. {
  255. $startline = 0;
  256. $this->gp = $gp;
  257. $this->filename = $gp->filename;
  258. $this->errorcnt = 0;
  259. $this->state = self::INITIALIZE;
  260. /* Begin by reading the input file */
  261. $filebuf = file_get_contents($this->filename);
  262. if (!$filebuf) {
  263. PHP_ParserGenerator::ErrorMsg($this->filename, 0, "Can't open this file for reading.");
  264. $gp->errorcnt++;
  265. return;
  266. }
  267. if (filesize($this->filename) != strlen($filebuf)) {
  268. ErrorMsg($this->filename, 0, "Can't read in all %d bytes of this file.",
  269. filesize($this->filename));
  270. $gp->errorcnt++;
  271. return;
  272. }
  273. /* Make an initial pass through the file to handle %ifdef and %ifndef */
  274. $this->preprocess_input($filebuf);
  275. /* Now scan the text of the input file */
  276. $lineno = 1;
  277. for ($cp = 0, $c = $filebuf[0]; $cp < strlen($filebuf); $cp++) {
  278. $c = $filebuf[$cp];
  279. if ($c == "\n") $lineno++; /* Keep track of the line number */
  280. if (trim($c) === '') {
  281. continue;
  282. } /* Skip all white space */
  283. if ($filebuf[$cp] == '/' && ($cp + 1 < strlen($filebuf)) && $filebuf[$cp + 1] == '/') {
  284. /* Skip C++ style comments */
  285. $cp += 2;
  286. $z = strpos(substr($filebuf, $cp), "\n");
  287. if ($z === false) {
  288. $cp = strlen($filebuf);
  289. break;
  290. }
  291. $lineno++;
  292. $cp += $z;
  293. continue;
  294. }
  295. if ($filebuf[$cp] == '/' && ($cp + 1 < strlen($filebuf)) && $filebuf[$cp + 1] == '*') {
  296. /* Skip C style comments */
  297. $cp += 2;
  298. $z = strpos(substr($filebuf, $cp), '*/');
  299. if ($z !== false) {
  300. $lineno += count(explode("\n", substr($filebuf, $cp, $z))) - 1;
  301. }
  302. $cp += $z + 1;
  303. continue;
  304. }
  305. $this->tokenstart = $cp; /* Mark the beginning of the token */
  306. $this->tokenlineno = $lineno; /* Linenumber on which token begins */
  307. if ($filebuf[$cp] == '"') { /* String literals */
  308. $cp++;
  309. $oldcp = $cp;
  310. $test = strpos(substr($filebuf, $cp), '"');
  311. if ($test === false) {
  312. PHP_ParserGenerator::ErrorMsg($this->filename, $startline,
  313. "String starting on this line is not terminated before the end of the file.");
  314. $this->errorcnt++;
  315. $nextcp = $cp = strlen($filebuf);
  316. } else {
  317. $cp += $test;
  318. $nextcp = $cp + 1;
  319. }
  320. $lineno += count(explode("\n", substr($filebuf, $oldcp, $cp - $oldcp))) - 1;
  321. } elseif ($filebuf[$cp] == '{') { /* A block of C code */
  322. $cp++;
  323. if ($filebuf[$cp]=="}") {
  324. $filebuf = substr($filebuf, 0, $cp)." ".substr($filebuf, $cp);
  325. }
  326. for ($level = 1; $cp < strlen($filebuf) && ($level > 1 || $filebuf[$cp] != '}'); $cp++) {
  327. if ($filebuf[$cp] == "\n") {
  328. $lineno++;
  329. } elseif ($filebuf[$cp] == '{') {
  330. $level++;
  331. } elseif ($filebuf[$cp] == '}') {
  332. $level--;
  333. } elseif ($filebuf[$cp] == '/' && $filebuf[$cp + 1] == '*') {
  334. /* Skip comments */
  335. $cp += 2;
  336. $z = strpos(substr($filebuf, $cp), '*/');
  337. if ($z !== false) {
  338. $lineno += count(explode("\n", substr($filebuf, $cp, $z))) - 1;
  339. }
  340. $cp += $z + 2;
  341. } elseif ($filebuf[$cp] == '/' && $filebuf[$cp + 1] == '/') {
  342. /* Skip C++ style comments too */
  343. $cp += 2;
  344. $z = strpos(substr($filebuf, $cp), "\n");
  345. if ($z === false) {
  346. $cp = strlen($filebuf);
  347. break;
  348. } else {
  349. $lineno++;
  350. }
  351. $cp += $z;
  352. } elseif ($filebuf[$cp] == "'" || $filebuf[$cp] == '"') {
  353. /* String a character literals */
  354. $startchar = $filebuf[$cp];
  355. $prevc = 0;
  356. for ($cp++; $cp < strlen($filebuf) && ($filebuf[$cp] != $startchar || $prevc === '\\'); $cp++) {
  357. if ($filebuf[$cp] == "\n") {
  358. $lineno++;
  359. }
  360. if ($prevc === '\\') {
  361. $prevc = 0;
  362. } else {
  363. $prevc = $filebuf[$cp];
  364. }
  365. }
  366. }
  367. }
  368. if ($cp >= strlen($filebuf)) {
  369. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  370. "PHP code starting on this line is not terminated before the end of the file.");
  371. $this->errorcnt++;
  372. $nextcp = $cp;
  373. } else {
  374. $nextcp = $cp + 1;
  375. }
  376. } elseif (preg_match('/[a-zA-Z0-9]/', $filebuf[$cp])) {
  377. /* Identifiers */
  378. preg_match('/[a-zA-Z0-9_]+/', substr($filebuf, $cp), $preg_results);
  379. $cp += strlen($preg_results[0]);
  380. $nextcp = $cp;
  381. } elseif ($filebuf[$cp] == ':' && $filebuf[$cp + 1] == ':' &&
  382. $filebuf[$cp + 2] == '=') {
  383. /* The operator "::=" */
  384. $cp += 3;
  385. $nextcp = $cp;
  386. } elseif (($filebuf[$cp] == '/' || $filebuf[$cp] == '|') &&
  387. preg_match('/[a-zA-Z]/', $filebuf[$cp + 1])) {
  388. $cp += 2;
  389. preg_match('/[a-zA-Z0-9_]+/', substr($filebuf, $cp), $preg_results);
  390. $cp += strlen($preg_results[0]);
  391. $nextcp = $cp;
  392. } else {
  393. /* All other (one character) operators */
  394. $cp ++;
  395. $nextcp = $cp;
  396. }
  397. $this->parseonetoken(substr($filebuf, $this->tokenstart,
  398. $cp - $this->tokenstart)); /* Parse the token */
  399. $cp = $nextcp - 1;
  400. }
  401. $gp->rule = $this->firstrule;
  402. $gp->errorcnt = $this->errorcnt;
  403. }
  404. /**
  405. * Parse a single token
  406. * @param string token
  407. */
  408. function parseonetoken($token)
  409. {
  410. $x = $token;
  411. $this->a = 0; // for referencing in WAITING_FOR_DECL_KEYWORD
  412. if (PHP_ParserGenerator::DEBUG) {
  413. printf("%s:%d: Token=[%s] state=%d\n",
  414. $this->filename, $this->tokenlineno, $token, $this->state);
  415. }
  416. switch ($this->state) {
  417. case self::INITIALIZE:
  418. $this->prevrule = 0;
  419. $this->preccounter = 0;
  420. $this->firstrule = $this->lastrule = 0;
  421. $this->gp->nrule = 0;
  422. /* Fall thru to next case */
  423. case self::WAITING_FOR_DECL_OR_RULE:
  424. if ($x[0] == '%') {
  425. $this->state = self::WAITING_FOR_DECL_KEYWORD;
  426. } elseif (preg_match('/[a-z]/', $x[0])) {
  427. $this->lhs = PHP_ParserGenerator_Symbol::Symbol_new($x);
  428. $this->nrhs = 0;
  429. $this->lhsalias = 0;
  430. $this->state = self::WAITING_FOR_ARROW;
  431. } elseif ($x[0] == '{') {
  432. if ($this->prevrule === 0) {
  433. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  434. "There is no prior rule opon which to attach the code
  435. fragment which begins on this line.");
  436. $this->errorcnt++;
  437. } elseif ($this->prevrule->code != 0) {
  438. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  439. "Code fragment beginning on this line is not the first \
  440. to follow the previous rule.");
  441. $this->errorcnt++;
  442. } else {
  443. $this->prevrule->line = $this->tokenlineno;
  444. $this->prevrule->code = substr($x, 1);
  445. }
  446. } elseif ($x[0] == '[') {
  447. $this->state = self::PRECEDENCE_MARK_1;
  448. } else {
  449. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  450. "Token \"%s\" should be either \"%%\" or a nonterminal name.",
  451. $x);
  452. $this->errorcnt++;
  453. }
  454. break;
  455. case self::PRECEDENCE_MARK_1:
  456. if (!preg_match('/[A-Z]/', $x[0])) {
  457. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  458. "The precedence symbol must be a terminal.");
  459. $this->errorcnt++;
  460. } elseif ($this->prevrule === 0) {
  461. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  462. "There is no prior rule to assign precedence \"[%s]\".", $x);
  463. $this->errorcnt++;
  464. } elseif ($this->prevrule->precsym != 0) {
  465. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  466. "Precedence mark on this line is not the first to follow the previous rule.");
  467. $this->errorcnt++;
  468. } else {
  469. $this->prevrule->precsym = PHP_ParserGenerator_Symbol::Symbol_new($x);
  470. }
  471. $this->state = self::PRECEDENCE_MARK_2;
  472. break;
  473. case self::PRECEDENCE_MARK_2:
  474. if ($x[0] != ']') {
  475. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  476. "Missing \"]\" on precedence mark.");
  477. $this->errorcnt++;
  478. }
  479. $this->state = self::WAITING_FOR_DECL_OR_RULE;
  480. break;
  481. case self::WAITING_FOR_ARROW:
  482. if ($x[0] == ':' && $x[1] == ':' && $x[2] == '=') {
  483. $this->state = self::IN_RHS;
  484. } elseif ($x[0] == '(') {
  485. $this->state = self::LHS_ALIAS_1;
  486. } else {
  487. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  488. "Expected to see a \":\" following the LHS symbol \"%s\".",
  489. $this->lhs->name);
  490. $this->errorcnt++;
  491. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  492. }
  493. break;
  494. case self::LHS_ALIAS_1:
  495. if (preg_match('/[A-Za-z]/', $x[0])) {
  496. $this->lhsalias = $x;
  497. $this->state = self::LHS_ALIAS_2;
  498. } else {
  499. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  500. "\"%s\" is not a valid alias for the LHS \"%s\"\n",
  501. $x, $this->lhs->name);
  502. $this->errorcnt++;
  503. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  504. }
  505. break;
  506. case self::LHS_ALIAS_2:
  507. if ($x[0] == ')') {
  508. $this->state = self::LHS_ALIAS_3;
  509. } else {
  510. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  511. "Missing \")\" following LHS alias name \"%s\".",$this->lhsalias);
  512. $this->errorcnt++;
  513. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  514. }
  515. break;
  516. case self::LHS_ALIAS_3:
  517. if ($x == '::=') {
  518. $this->state = self::IN_RHS;
  519. } else {
  520. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  521. "Missing \"->\" following: \"%s(%s)\".",
  522. $this->lhs->name, $this->lhsalias);
  523. $this->errorcnt++;
  524. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  525. }
  526. break;
  527. case self::IN_RHS:
  528. if ($x[0] == '.') {
  529. $rp = new PHP_ParserGenerator_Rule;
  530. $rp->ruleline = $this->tokenlineno;
  531. for ($i = 0; $i < $this->nrhs; $i++) {
  532. $rp->rhs[$i] = $this->rhs[$i];
  533. $rp->rhsalias[$i] = $this->alias[$i];
  534. }
  535. if (count(array_unique($rp->rhsalias)) != count($rp->rhsalias)) {
  536. $used = array();
  537. foreach ($rp->rhsalias as $i => $symbol) {
  538. if (!is_string($symbol)) {
  539. continue;
  540. }
  541. if (isset($used[$symbol])) {
  542. PHP_ParserGenerator::ErrorMsg($this->filename,
  543. $this->tokenlineno,
  544. "RHS symbol \"%s\" used multiple times.",
  545. $symbol);
  546. $this->errorcnt++;
  547. } else {
  548. $used[$symbol] = $i;
  549. }
  550. }
  551. }
  552. $rp->lhs = $this->lhs;
  553. $rp->lhsalias = $this->lhsalias;
  554. $rp->nrhs = $this->nrhs;
  555. $rp->code = 0;
  556. $rp->precsym = 0;
  557. $rp->index = $this->gp->nrule++;
  558. $rp->nextlhs = $rp->lhs->rule;
  559. $rp->lhs->rule = $rp;
  560. $rp->next = 0;
  561. if ($this->firstrule === 0) {
  562. $this->firstrule = $this->lastrule = $rp;
  563. } else {
  564. $this->lastrule->next = $rp;
  565. $this->lastrule = $rp;
  566. }
  567. $this->prevrule = $rp;
  568. $this->state = self::WAITING_FOR_DECL_OR_RULE;
  569. } elseif (preg_match('/[a-zA-Z]/', $x[0])) {
  570. if ($this->nrhs >= PHP_ParserGenerator::MAXRHS) {
  571. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  572. "Too many symbols on RHS or rule beginning at \"%s\".",
  573. $x);
  574. $this->errorcnt++;
  575. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  576. } else {
  577. if (isset($this->rhs[$this->nrhs - 1])) {
  578. $msp = $this->rhs[$this->nrhs - 1];
  579. if ($msp->type == PHP_ParserGenerator_Symbol::MULTITERMINAL) {
  580. $inf = array_reduce($msp->subsym,
  581. array($this, '_printmulti'), '');
  582. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  583. 'WARNING: symbol ' . $x . ' will not' .
  584. ' be part of previous multiterminal %s',
  585. substr($inf, 0, strlen($inf) - 1)
  586. );
  587. }
  588. }
  589. $this->rhs[$this->nrhs] = PHP_ParserGenerator_Symbol::Symbol_new($x);
  590. $this->alias[$this->nrhs] = 0;
  591. $this->nrhs++;
  592. }
  593. } elseif (($x[0] == '|' || $x[0] == '/') && $this->nrhs > 0) {
  594. $msp = $this->rhs[$this->nrhs - 1];
  595. if ($msp->type != PHP_ParserGenerator_Symbol::MULTITERMINAL) {
  596. $origsp = $msp;
  597. $msp = new PHP_ParserGenerator_Symbol;
  598. $msp->type = PHP_ParserGenerator_Symbol::MULTITERMINAL;
  599. $msp->nsubsym = 1;
  600. $msp->subsym = array($origsp);
  601. $msp->name = $origsp->name;
  602. $this->rhs[$this->nrhs - 1] = $msp;
  603. }
  604. $msp->nsubsym++;
  605. $msp->subsym[$msp->nsubsym - 1] = PHP_ParserGenerator_Symbol::Symbol_new(substr($x, 1));
  606. if (preg_match('/[a-z]/', $x[1]) ||
  607. preg_match('/[a-z]/', $msp->subsym[0]->name[0])) {
  608. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  609. "Cannot form a compound containing a non-terminal");
  610. $this->errorcnt++;
  611. }
  612. } elseif ($x[0] == '(' && $this->nrhs > 0) {
  613. $this->state = self::RHS_ALIAS_1;
  614. } else {
  615. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  616. "Illegal character on RHS of rule: \"%s\".", $x);
  617. $this->errorcnt++;
  618. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  619. }
  620. break;
  621. case self::RHS_ALIAS_1:
  622. if (preg_match('/[A-Za-z]/', $x[0])) {
  623. $this->alias[$this->nrhs - 1] = $x;
  624. $this->state = self::RHS_ALIAS_2;
  625. } else {
  626. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  627. "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n",
  628. $x, $this->rhs[$this->nrhs - 1]->name);
  629. $this->errorcnt++;
  630. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  631. }
  632. break;
  633. case self::RHS_ALIAS_2:
  634. if ($x[0] == ')') {
  635. $this->state = self::IN_RHS;
  636. } else {
  637. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  638. "Missing \")\" following LHS alias name \"%s\".", $this->lhsalias);
  639. $this->errorcnt++;
  640. $this->state = self::RESYNC_AFTER_RULE_ERROR;
  641. }
  642. break;
  643. case self::WAITING_FOR_DECL_KEYWORD:
  644. if(preg_match('/[A-Za-z]/', $x[0])) {
  645. $this->declkeyword = $x;
  646. $this->declargslot = &$this->a;
  647. $this->decllnslot = &$this->a;
  648. $this->state = self::WAITING_FOR_DECL_ARG;
  649. if ('name' == $x) {
  650. $this->declargslot = &$this->gp->name;
  651. } elseif ('include' == $x) {
  652. $this->declargslot = &$this->gp->include_code;
  653. $this->decllnslot = &$this->gp->includeln;
  654. } elseif ('include_class' == $x) {
  655. $this->declargslot = &$this->gp->include_classcode;
  656. $this->decllnslot = &$this->gp->include_classln;
  657. } elseif ('declare_class' == $x) {
  658. $this->declargslot = &$this->gp->declare_classcode;
  659. $this->decllnslot = &$this->gp->declare_classln;
  660. } elseif ('code' == $x) {
  661. $this->declargslot = &$this->gp->extracode;
  662. $this->decllnslot = &$this->gp->extracodeln;
  663. } elseif ('token_destructor' == $x) {
  664. $this->declargslot = &$this->gp->tokendest;
  665. $this->decllnslot = &$this->gp->tokendestln;
  666. } elseif ('default_destructor' == $x) {
  667. $this->declargslot = &$this->gp->vardest;
  668. $this->decllnslot = &$this->gp->vardestln;
  669. } elseif ('token_prefix' == $x) {
  670. $this->declargslot = &$this->gp->tokenprefix;
  671. } elseif ('syntax_error' == $x) {
  672. $this->declargslot = &$this->gp->error;
  673. $this->decllnslot = &$this->gp->errorln;
  674. } elseif ('parse_accept' == $x) {
  675. $this->declargslot = &$this->gp->accept;
  676. $this->decllnslot = &$this->gp->acceptln;
  677. } elseif ('parse_failure' == $x) {
  678. $this->declargslot = &$this->gp->failure;
  679. $this->decllnslot = &$this->gp->failureln;
  680. } elseif ('stack_overflow' == $x) {
  681. $this->declargslot = &$this->gp->overflow;
  682. $this->decllnslot = &$this->gp->overflowln;
  683. } elseif ('token_type' == $x) {
  684. $this->declargslot = &$this->gp->tokentype;
  685. } elseif ('default_type' == $x) {
  686. $this->declargslot = &$this->gp->vartype;
  687. } elseif ('stack_size' == $x) {
  688. $this->declargslot = &$this->gp->stacksize;
  689. } elseif ('start_symbol' == $x) {
  690. $this->declargslot = &$this->gp->start;
  691. } elseif ('left' == $x) {
  692. $this->preccounter++;
  693. $this->declassoc = PHP_ParserGenerator_Symbol::LEFT;
  694. $this->state = self::WAITING_FOR_PRECEDENCE_SYMBOL;
  695. } elseif ('right' == $x) {
  696. $this->preccounter++;
  697. $this->declassoc = PHP_ParserGenerator_Symbol::RIGHT;
  698. $this->state = self::WAITING_FOR_PRECEDENCE_SYMBOL;
  699. } elseif ('nonassoc' == $x) {
  700. $this->preccounter++;
  701. $this->declassoc = PHP_ParserGenerator_Symbol::NONE;
  702. $this->state = self::WAITING_FOR_PRECEDENCE_SYMBOL;
  703. } elseif ('destructor' == $x) {
  704. $this->state = self::WAITING_FOR_DESTRUCTOR_SYMBOL;
  705. } elseif ('type' == $x) {
  706. $this->state = self::WAITING_FOR_DATATYPE_SYMBOL;
  707. } elseif ('fallback' == $x) {
  708. $this->fallback = 0;
  709. $this->state = self::WAITING_FOR_FALLBACK_ID;
  710. } else {
  711. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  712. "Unknown declaration keyword: \"%%%s\".", $x);
  713. $this->errorcnt++;
  714. $this->state = self::RESYNC_AFTER_DECL_ERROR;
  715. }
  716. } else {
  717. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  718. "Illegal declaration keyword: \"%s\".", $x);
  719. $this->errorcnt++;
  720. $this->state = self::RESYNC_AFTER_DECL_ERROR;
  721. }
  722. break;
  723. case self::WAITING_FOR_DESTRUCTOR_SYMBOL:
  724. if (!preg_match('/[A-Za-z]/', $x[0])) {
  725. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  726. "Symbol name missing after %destructor keyword");
  727. $this->errorcnt++;
  728. $this->state = self::RESYNC_AFTER_DECL_ERROR;
  729. } else {
  730. $sp = PHP_ParserGenerator_Symbol::Symbol_new($x);
  731. $this->declargslot = &$sp->destructor;
  732. $this->decllnslot = &$sp->destructorln;
  733. $this->state = self::WAITING_FOR_DECL_ARG;
  734. }
  735. break;
  736. case self::WAITING_FOR_DATATYPE_SYMBOL:
  737. if (!preg_match('/[A-Za-z]/', $x[0])) {
  738. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  739. "Symbol name missing after %destructor keyword");
  740. $this->errorcnt++;
  741. $this->state = self::RESYNC_AFTER_DECL_ERROR;
  742. } else {
  743. $sp = PHP_ParserGenerator_Symbol::Symbol_new($x);
  744. $this->declargslot = &$sp->datatype;
  745. $this->state = self::WAITING_FOR_DECL_ARG;
  746. }
  747. break;
  748. case self::WAITING_FOR_PRECEDENCE_SYMBOL:
  749. if ($x[0] == '.') {
  750. $this->state = self::WAITING_FOR_DECL_OR_RULE;
  751. } elseif (preg_match('/[A-Z]/', $x[0])) {
  752. $sp = PHP_ParserGenerator_Symbol::Symbol_new($x);
  753. if ($sp->prec >= 0) {
  754. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  755. "Symbol \"%s\" has already been given a precedence.", $x);
  756. $this->errorcnt++;
  757. } else {
  758. $sp->prec = $this->preccounter;
  759. $sp->assoc = $this->declassoc;
  760. }
  761. } else {
  762. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  763. "Can't assign a precedence to \"%s\".", $x);
  764. $this->errorcnt++;
  765. }
  766. break;
  767. case self::WAITING_FOR_DECL_ARG:
  768. if (preg_match('/[A-Za-z0-9{"]/', $x[0])) {
  769. if ($this->declargslot != 0) {
  770. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  771. "The argument \"%s\" to declaration \"%%%s\" is not the first.",
  772. $x[0] == '"' ? substr($x, 1) : $x, $this->declkeyword);
  773. $this->errorcnt++;
  774. $this->state = self::RESYNC_AFTER_DECL_ERROR;
  775. } else {
  776. $this->declargslot = ($x[0] == '"' || $x[0] == '{') ? substr($x, 1) : $x;
  777. $this->a = 1;
  778. if (!$this->decllnslot) {
  779. $this->decllnslot = $this->tokenlineno;
  780. }
  781. $this->state = self::WAITING_FOR_DECL_OR_RULE;
  782. }
  783. } else {
  784. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  785. "Illegal argument to %%%s: %s",$this->declkeyword, $x);
  786. $this->errorcnt++;
  787. $this->state = self::RESYNC_AFTER_DECL_ERROR;
  788. }
  789. break;
  790. case self::WAITING_FOR_FALLBACK_ID:
  791. if ($x[0] == '.') {
  792. $this->state = self::WAITING_FOR_DECL_OR_RULE;
  793. } elseif (!preg_match('/[A-Z]/', $x[0])) {
  794. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  795. "%%fallback argument \"%s\" should be a token", $x);
  796. $this->errorcnt++;
  797. } else {
  798. $sp = PHP_ParserGenerator_Symbol::Symbol_new($x);
  799. if ($this->fallback === 0) {
  800. $this->fallback = $sp;
  801. } elseif (is_object($sp->fallback)) {
  802. PHP_ParserGenerator::ErrorMsg($this->filename, $this->tokenlineno,
  803. "More than one fallback assigned to token %s", $x);
  804. $this->errorcnt++;
  805. } else {
  806. $sp->fallback = $this->fallback;
  807. $this->gp->has_fallback = 1;
  808. }
  809. }
  810. break;
  811. case self::RESYNC_AFTER_RULE_ERROR:
  812. /* if ($x[0] == '.') $this->state = self::WAITING_FOR_DECL_OR_RULE;
  813. ** break; */
  814. case self::RESYNC_AFTER_DECL_ERROR:
  815. if ($x[0] == '.') {
  816. $this->state = self::WAITING_FOR_DECL_OR_RULE;
  817. }
  818. if ($x[0] == '%') {
  819. $this->state = self::WAITING_FOR_DECL_KEYWORD;
  820. }
  821. break;
  822. }
  823. }
  824. /**
  825. * return a descriptive string for a multi-terminal token.
  826. *
  827. * @param string $a
  828. * @param string $b
  829. * @return string
  830. */
  831. private function _printmulti($a, $b)
  832. {
  833. if (!$a) {
  834. $a = '';
  835. }
  836. $a .= $b->name . '|';
  837. return $a;
  838. }
  839. }