PageRenderTime 55ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/externals/dojo/util/docscripts/lib/parser2/Parser.php

https://bitbucket.org/dbaltas/zend-framework-1.x-on-git
PHP | 407 lines | 270 code | 46 blank | 91 comment | 64 complexity | a681020d7b670d9b25f0ca4feb5ee7e9 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, MIT
  1. <?php
  2. require_once('Scope.php');
  3. require_once('Symbol.php');
  4. require_once('Destructable.php');
  5. abstract class Parser extends Destructable {
  6. private static $symbol_tables = array();
  7. protected $scopes = array();
  8. protected $tokens;
  9. protected $token_pos = 0;
  10. protected $token_length = 0;
  11. private $symbol_table;
  12. public $token;
  13. public $scope;
  14. abstract protected function build();
  15. protected $symbol_class = Symbol;
  16. public function __construct($tokens) {
  17. $id = get_class($this);
  18. if (empty(self::$symbol_tables[$id])) {
  19. $this->symbol_table = array();
  20. $this->build();
  21. self::$symbol_tables[$id] = $this->symbol_table;
  22. }
  23. $this->symbol_table = self::$symbol_tables[$id];
  24. $this->scopes[] = $this->scope = new Scope();
  25. $this->tokens = $tokens;
  26. $this->token_length = count($tokens);
  27. }
  28. public function __destruct() {
  29. $this->mem_flush('scopes', 'tokens', 'token', 'scope');
  30. }
  31. public function skip_terminators() {
  32. while ($this->peek(';')) {
  33. $this->advance(';');
  34. }
  35. }
  36. /**
  37. * Quick error check as we advance through the tokens
  38. *
  39. * @param string $id Check that the current token is what's expected
  40. */
  41. public function advance($id = NULL) {
  42. if ($id && !$this->peek($id)) {
  43. throw new Exception("Line {$this->token->line_number}, character {$this->token->char_pos}: Expected '$id' but got {$this->token->id}:'{$this->token->value}'");
  44. }
  45. $this->token = $this->next();
  46. while ($this->token->value == "\n") {
  47. // We can ignore line-breaks that are mid-expression
  48. $this->token = $this->next();
  49. }
  50. }
  51. /**
  52. * Look at the next non-whitespace token for a value. Also needed
  53. * for std functions to move up to the next non-whitespace character
  54. */
  55. public function peek($id = NULL) {
  56. if ($id && !is_array($id)) {
  57. $id = array($id);
  58. }
  59. if ($id && in_array("\n", $id) && $this->token->value == "\n") {
  60. return true;
  61. }
  62. while ($this->token->value == "\n") {
  63. // We can ignore line-breaks that are mid-expression
  64. $this->token = $this->next();
  65. }
  66. return $id ? in_array($this->token->id, $id) : NULL;
  67. }
  68. public function peek2($id) {
  69. if (!is_array($id)) {
  70. $id = array($id);
  71. }
  72. for ($i = $this->token_pos; $i < $this->token_length; $i++) {
  73. $token = $this->tokens[$i];
  74. if (in_array("\n", $id) && $token['value'] == "\n") {
  75. return true;
  76. }
  77. if ($token['type'] != 'string' && $token['value'] != "\n") {
  78. return in_array($token['value'], $id);
  79. }
  80. }
  81. }
  82. /**
  83. * Grab all statements
  84. */
  85. public function statements($terminators = array()) {
  86. if (!$this->token_pos) {
  87. $this->advance();
  88. }
  89. $terminators[] = '(end)';
  90. $statements = array();
  91. while (1) {
  92. // Statements occur within {} blocks as well
  93. if ($this->peek($terminators)) {
  94. break;
  95. }
  96. if ($statement = $this->statement($terminators)) {
  97. $statements[] = $statement;
  98. }
  99. }
  100. return $statements;
  101. }
  102. /**
  103. * Grab a single statement
  104. */
  105. public function statement($exclude = array()) {
  106. $skip = array_diff(array(';', "\n", ','), $exclude);
  107. while (in_array($this->token->id, $skip)) {
  108. $this->advance($this->token->id);
  109. }
  110. $token = $this->token;
  111. if ($token->std) {
  112. $this->token = $this->next(); // Line breaks are *really* important to some statements
  113. $this->scope->reserve($token);
  114. return $token->std($this);
  115. }
  116. $expression = $this->expression();
  117. while (in_array($this->token->id, $skip)) {
  118. $this->advance($this->token->id);
  119. }
  120. return $expression;
  121. }
  122. private function comments_from_pos($i) {
  123. $last = NULL;
  124. $comments = array();
  125. for (; $i < $this->token_length; $i++) {
  126. $token = $this->tokens[$i];
  127. if ($token['type'] == 'comment') {
  128. $comments[] = $token['value'];
  129. }
  130. elseif ($token['value'] != "\n") {
  131. break;
  132. }
  133. elseif ($last == "\n") {
  134. $comments[] = '';
  135. }
  136. $last = $token['value'];
  137. }
  138. return $comments;
  139. }
  140. public function comments_before($symbol) {
  141. if (!isset($symbol->token_pos)) {
  142. throw new Exception('Need valid token to look up comments');
  143. }
  144. $comments = FALSE;
  145. for ($i = $symbol->token_pos - 1; $i > 0; $i--) {
  146. $token = $this->tokens[$i];
  147. if ($token['type'] == 'comment') {
  148. $comments = TRUE;
  149. }
  150. elseif ($token['value'] != "\n") {
  151. if ($comments) {
  152. return $this->comments_from_pos($i + 1);
  153. }
  154. break;
  155. }
  156. }
  157. return array();
  158. }
  159. public function comments_after($symbol) {
  160. if (!isset($symbol->token_pos)) {
  161. throw new Exception('Need valid token to look up comments');
  162. }
  163. return $this->comments_from_pos($symbol->token_pos + 1);
  164. }
  165. /**
  166. * Simply advance through the tokens
  167. */
  168. public function next() {
  169. if ($this->token_pos < $this->token_length) {
  170. $token = $this->tokens[$this->token_pos++];
  171. $value = $token['value'];
  172. $type = $arity = $token['type'];
  173. if ($arity == 'string' || $arity == 'number' || $arity == 'regex') {
  174. $arity = 'literal';
  175. $s = $this->new_symbol('(literal)');
  176. }
  177. elseif ($s = $this->new_symbol($value)) {
  178. // short circuit
  179. }
  180. elseif ($arity == 'name') {
  181. $s = $this->scope->find($value, $this->symbol_table);
  182. }
  183. elseif ($arity == 'comment') {
  184. return $this->next();
  185. }
  186. else {
  187. throw new Exception("Line {$token['line_number']}, char {$token['char_pos']}: Unknown operator ($arity:'$value')");
  188. }
  189. $s->token_pos = $this->token_pos - 1;
  190. $s->line = $token['line'];
  191. $s->line_number = $token['line_number'];
  192. $s->char_pos = $token['char_pos'];
  193. $s->value = $value;
  194. $s->arity = $arity;
  195. $s->type = $type;
  196. return $s;
  197. }
  198. return $this->new_symbol('(end)');
  199. }
  200. /**
  201. * Creates a new scope, setting the old one as its parent
  202. */
  203. public function new_scope() {
  204. $this->scopes[] = $scope = new Scope();
  205. $scope->setParent($this->scope);
  206. return ($this->scope = $scope);
  207. }
  208. /**
  209. * Reassigns the parents scope
  210. */
  211. public function scope_pop() {
  212. return ($this->scope = $this->scope->parent());
  213. }
  214. /**
  215. * Moves through tokens with higher binding powers
  216. * than the passed binding power
  217. *
  218. * @param int $bp
  219. */
  220. public function expression($bp = 0) {
  221. $token = $this->token;
  222. $this->advance();
  223. while ($this->token->value == "\n") {
  224. // We can ignore line-breaks that are mid-expression
  225. $token = $this->token;
  226. $this->advance();
  227. }
  228. $left = $token->nud($this);
  229. while ($bp < $this->token->lbp($this, $left)) {
  230. $token = $this->token;
  231. $this->advance();
  232. $left = $token->led($this, $left);
  233. }
  234. return $left;
  235. }
  236. public function new_symbol($id, $raw = FALSE) {
  237. $symbol = $this->symbol_table[$id];
  238. if ($symbol) {
  239. if ($raw) {
  240. return $symbol;
  241. }
  242. $symbol = clone $symbol;
  243. $symbol->scope = $this->scope;
  244. return $symbol;
  245. }
  246. }
  247. /**
  248. * Takes a symbol ID and a left binding power
  249. * and returns a Symbol instance
  250. *
  251. * @param string $id
  252. * @param int $b
  253. */
  254. protected function symbol($id, $bp = 0) {
  255. if (($s = $this->symbol_table[$id]) && is_numeric($s->lbp)) {
  256. $s->lbp = max($bp, $s->lbp);
  257. }
  258. else {
  259. $s = new $this->symbol_class();
  260. $s->id = $id;
  261. $s->lbp = $bp;
  262. $this->symbol_table[$id] = $s;
  263. }
  264. return $s;
  265. }
  266. /**
  267. * Creates a symbol with a left denotation function
  268. * that will save the current symbol in its left property
  269. * and the rest of the expression on its right
  270. *
  271. * @param string $id
  272. * @param int $bp
  273. * @param string $ld String to use for the left_denotation function
  274. */
  275. protected function infix($id, $bp, $led = NULL) {
  276. $symbol = $this->symbol($id, $bp);
  277. if ($led) {
  278. $symbol->led = $led;
  279. }
  280. else {
  281. $symbol->led = 'led_infix';
  282. $symbol->bp = $bp;
  283. }
  284. return $symbol;
  285. }
  286. /**
  287. * Creates a symbol with a left denotation function
  288. * that will save symbols "below" it
  289. *
  290. * @param string $id
  291. * @param int $bp
  292. * @param string $led String to use for the left_denotation function
  293. */
  294. protected function infixr($id, $bp, $led = NULL) {
  295. $symbol = $this->symbol($id, $bp);
  296. if ($led) {
  297. $symbol->led = $led;
  298. }
  299. else {
  300. $symbol->led = 'led_infixr';
  301. $symbol->bp = $bp;
  302. }
  303. return $symbol;
  304. }
  305. /**
  306. * Create a symbol with a null denotation function
  307. * that will set its left property to what its
  308. * modifying
  309. *
  310. * @param string $id
  311. * @param string $nud String to use for the null_denotation function
  312. */
  313. protected function prefix($id, $nud = NULL) {
  314. $symbol = $this->symbol($id);
  315. $symbol->nud = $nud ? $nud : 'nud_prefix';
  316. return $symbol;
  317. }
  318. protected function itself($id) {
  319. return $this->prefix($id, 'nud_itself');
  320. }
  321. /**
  322. * Creates a symbol with a null denotation function that
  323. * makes sure it's being assigned to a variable
  324. * and sets its left property to what's being assigned
  325. * to it. Also marks its assignment value to true
  326. *
  327. * @param string $id
  328. */
  329. protected function assignment($id) {
  330. return $this->infixr($id, 10, 'led_assignment');
  331. }
  332. /**
  333. * Creates a symbol with a null denotation function
  334. * that turns a name token into a literal token
  335. * by marking it reserved in the current scope
  336. * and setting its value to a language-level literal
  337. *
  338. * @param string $id
  339. * @param anything $null_denotation String to use for the null_denotation function
  340. */
  341. protected function constant($name, $value) {
  342. $symbol = $this->symbol($name);
  343. $symbol->nud = 'nud_constant';
  344. $symbol->value = $value;
  345. $symbol->arity = 'constant';
  346. return $symbol;
  347. }
  348. /**
  349. * Creates a symbol with a statement denotation function
  350. * passed to it.
  351. *
  352. * @param string $id
  353. * @param string $std String to use for the statement_denotation function
  354. */
  355. protected function stmt($id, $std) {
  356. $symbol = $this->symbol($id);
  357. $symbol->std = $std;
  358. $symbol->nud = $std; // This makes statement nesting (with no block) possible
  359. return $symbol;
  360. }
  361. }