1 <?php
2 class Parser
3 {
4 var $scanner_;
5 var $script_;
6 var $tree_;
7 var $status_;
9 var $status_text;
11 function parse($script)
12 {
13 $this->status_text = "incomplete";
15 $this->script_ = $script;
16 $this->tree_ = new Tree(Scanner::scriptStart());
17 $this->tree_->setDumpFunc(array(&$this, 'dumpToken_'));
18 $this->scanner_ = new Scanner($this->script_);
19 $this->scanner_->setCommentFunc(array($this, 'comment_'));
21 if ($this->commands_($this->tree_->getRoot()) &&
22 $this->scanner_->nextTokenIs('script-end'))
23 {
24 return $this->success_('success');
25 }
27 return $this->status_;
28 }
30 function dumpParseTree()
31 {
32 return $this->tree_->dump();
33 }
35 function dumpToken_(&$token)
36 {
37 if (is_array($token))
38 {
39 $str = "<" . $token['text'] . "> ";
40 foreach ($token as $k => $v)
41 {
42 $str .= " $k:$v";
43 }
44 return $str;
45 }
47 return strval($token);
48 }
50 function success_($text = null)
51 {
52 if ($text != null)
53 {
54 $this->status_text = $text;
55 }
57 return $this->status_ = true;
58 }
60 function error_($text, $token = null)
61 {
62 if ($token != null)
63 {
64 $text = 'line '. $token['line'] .': '. $token['class'] . " where $text expected near ". $token['text'];
65 }
67 $this->status_text = $text;
68 return $this->status_ = false;
69 }
71 function done_()
72 {
73 $this->status_ = true;
74 return false;
75 }
77 function comment_($token)
78 {
79 $this->tree_->addChild($token);
80 }
82 function commands_($parent_id)
83 {
84 while ($this->command_($parent_id))
85 ;
87 return $this->status_;
88 }
90 function command_($parent_id)
91 {
92 if (!$this->scanner_->nextTokenIs('identifier'))
93 {
94 if ($this->scanner_->nextTokenIs(array('block-end', 'script-end')))
95 {
96 return $this->done_();
97 }
98 return $this->error_('identifier', $this->scanner_->peekNextToken());
99 }
101 // Get and check a command token
102 $token = $this->scanner_->nextToken();
103 $semantics = new Semantics($token['text']);
104 if ($semantics->unknown)
105 {
106 return $this->error_('unknown command: '. $token['text']);
107 }
109 $last = $this->tree_->getLastNode($parent_id);
110 if (!$semantics->validAfter($last['text']))
111 {
112 #return $this->error_('"'. $token['text'] .'" may not appear after "'. $last['text'] .'"');
113 }
115 // Process eventual arguments
116 $this_node = $this->tree_->addChildTo($parent_id, $token);
117 if ($this->arguments_($this_node, $semantics) == false)
118 {
119 return false;
120 }
122 $token = $this->scanner_->nextToken();
123 if ($token['class'] != 'semicolon')
124 {
125 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
126 {
127 return $this->error_($semantics->message);
128 }
130 if ($token['class'] == 'block-start')
131 {
132 $this->tree_->addChildTo($this_node, $token);
133 $ret = $this->block_($this_node, $semantics);
134 return $ret;
135 }
137 return $this->error_('semicolon', $token);
138 }
140 $this->tree_->addChildTo($this_node, $token);
141 return $this->success_();
142 }
144 function arguments_($parent_id, &$semantics)
145 {
146 while ($this->argument_($parent_id, &$semantics))
147 ;
149 if ($this->status_ == true)
150 {
151 $this->testlist_($parent_id, $semantics);
152 }
154 return $this->status_;
155 }
157 function argument_($parent_id, &$semantics)
158 {
159 if ($this->scanner_->nextTokenIs(array('number', 'tag')))
160 {
161 // Check if semantics allow a number or tag
162 $token = $this->scanner_->nextToken();
163 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
164 {
165 return $this->error_($semantics->message);
166 }
168 $this->tree_->addChildTo($parent_id, $token);
169 return $this->success_();
170 }
172 return $this->stringlist_($parent_id, &$semantics);
173 }
175 function stringlist_($parent_id, &$semantics)
176 {
177 if (!$this->scanner_->nextTokenIs('left-bracket'))
178 {
179 return $this->string_($parent_id, &$semantics);
180 }
182 $token = $this->scanner_->nextToken();
183 if (!$semantics->startStringList($token['line']))
184 {
185 return $this->error_($semantics->message);
186 }
187 $this->tree_->addChildTo($parent_id, $token);
189 while ($token['class'] != 'right-bracket')
190 {
191 if (!$this->string_($parent_id, &$semantics))
192 {
193 return $this->status_;
194 }
196 $token = $this->scanner_->nextToken();
198 if ($token['class'] != 'comma' && $token['class'] != 'right-bracket')
199 {
200 return $this->error_('comma or closing bracket', $token);
201 }
203 $this->tree_->addChildTo($parent_id, $token);
204 }
206 $semantics->endStringList();
207 return $this->success_();
208 }
210 function string_($parent_id, &$semantics)
211 {
212 if (!$this->scanner_->nextTokenIs(array('quoted-string', 'multi-line')))
213 {
214 return $this->done_();
215 }
217 $token = $this->scanner_->nextToken();
218 if (!$semantics->validToken('string', $token['text'], $token['line']))
219 {
220 return $this->error_($semantics->message);
221 }
223 $this->tree_->addChildTo($parent_id, $token);
224 return $this->success_();
225 }
227 function testlist_($parent_id, &$semantics)
228 {
229 if (!$this->scanner_->nextTokenIs('left-parant'))
230 {
231 return $this->test_($parent_id, $semantics);
232 }
234 $token = $this->scanner_->nextToken();
235 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
236 {
237 return $this->error_($semantics->message);
238 }
239 $this->tree_->addChildTo($parent_id, $token);
241 while ($token['class'] != 'right-parant')
242 {
243 if (!$this->test_($parent_id, $semantics))
244 {
245 return $this->status_;
246 }
248 $token = $this->scanner_->nextToken();
250 if ($token['class'] != 'comma' && $token['class'] != 'right-parant')
251 {
252 return $this->error_('comma or closing paranthesis', $token);
253 }
255 $this->tree_->addChildTo($parent_id, $token);
256 }
258 return $this->success_();
259 }
261 function test_($parent_id, &$semantics)
262 {
263 if (!$this->scanner_->nextTokenIs('identifier'))
264 {
265 // There is no test
266 return $this->done_();
267 }
269 // Check if semantics allow an identifier
270 $token = $this->scanner_->nextToken();
271 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
272 {
273 return $this->error_($semantics->message);
274 }
276 // Get semantics for this test command
277 $this_semantics = new Semantics($token['text']);
278 if ($this_semantics->unknown)
279 {
280 return $this->error_('unknown test: '. $token['text']);
281 }
283 $this_node = $this->tree_->addChildTo($parent_id, $token);
285 // Consume eventual argument tokens
286 if (!$this->arguments_($this_node, $this_semantics))
287 {
288 return false;
289 }
291 // Check if arguments were all there
292 $token = $this->scanner_->peekNextToken();
293 if (!$this_semantics->done($token['class'], $token['text'], $token['line']))
294 {
295 return $this->error_($this_semantics->message);
296 }
298 return true;
299 }
301 function block_($parent_id, &$semantics)
302 {
303 if ($this->commands_($parent_id, $semantics))
304 {
305 $token = $this->scanner_->nextToken();
307 if ($token['class'] != 'block-end')
308 {
309 return $this->error_('closing curly brace', $token);
310 }
312 $this->tree_->addChildTo($parent_id, $token);
313 return $this->success_();
314 }
315 return $this->status_;
316 }
317 }
319 ?>