source: Dev/trunk/src/client/util/docscripts/lib/parser2/Parser.php

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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