1 <?php
3 #include_once 'class.tree.php';
4 #include_once 'class.scanner.php';
5 #include_once 'class.semantics.php';
7 class Parser
8 {
9 var $scanner_;
10 var $script_;
11 var $tree_;
12 var $status_;
13 var $registeredExtensions_;
15 var $status_text;
17 function parse($script)
18 {
19 $this->registeredExtensions_ = array();
20 $this->status_text = "incomplete";
22 $this->script_ = $script;
23 $this->tree_ = new Tree(Scanner::scriptStart());
24 $this->tree_->setDumpFunc(array(&$this, 'dumpToken_'));
25 $this->scanner_ = new Scanner($this->script_);
26 $this->scanner_->setCommentFunc(array($this, 'comment_'));
28 if ($this->commands_($this->tree_->getRoot()) &&
29 $this->scanner_->nextTokenIs('script-end'))
30 {
31 return $this->success_('success');
32 }
34 return $this->status_;
35 }
37 function dumpParseTree()
38 {
39 return $this->tree_->dump();
40 }
42 function dumpToken_(&$token)
43 {
44 if (is_array($token))
45 {
46 $str = "<" . $token['text'] . "> ";
47 foreach ($token as $k => $v)
48 {
49 $str .= " $k:$v";
50 }
51 return $str;
52 }
54 return strval($token);
55 }
57 function getPrevTokenText_($parent_id)
58 {
59 $childs = $this->tree_->getChilds($parent_id);
61 for ($i=count($childs); $i>0; --$i)
62 {
63 $prev = $this->tree_->getNode($childs[$i-1]);
65 if (in_array($prev['text'], array('{', '(', ',')))
66 {
67 // use command owning a block or list
68 $prev = $this->tree_->getNode($parent_id);
69 }
71 if ($prev['class'] != 'comment')
72 {
73 return $prev['text'];
74 }
75 }
77 $prev = $this->tree_->getNode($parent_id);
78 return $prev['text'];
79 }
81 function getSemantics_($token_text)
82 {
83 $semantics = new Semantics($token_text);
84 $semantics->setExtensionFuncs(array(&$this, 'registerExtension_'), array(&$this, 'isExtensionRegistered_'));
85 return $semantics;
86 }
88 function registerExtension_($extension)
89 {
90 array_push($this->registeredExtensions_, str_replace('"', '', $extension));
91 }
93 function isExtensionRegistered_($extension)
94 {
95 return (in_array($extension, $this->registeredExtensions_) ? true : false);
96 }
98 function success_($text = null)
99 {
100 if ($text != null)
101 {
102 $this->status_text = $text;
103 }
105 return $this->status_ = true;
106 }
108 function error_($text, $token = null)
109 {
110 if ($token != null)
111 {
112 $text = 'line '. $token['line'] .': '. $token['class'] . " where $text expected near ". $token['text'];
113 }
115 $this->status_text = $text;
116 return $this->status_ = false;
117 }
119 function done_()
120 {
121 $this->status_ = true;
122 return false;
123 }
125 /*******************************************************************************
126 * methods for recursive descent start below
127 */
129 function comment_($token)
130 {
131 $this->tree_->addChild($token);
132 }
134 function commands_($parent_id)
135 {
136 while ($this->command_($parent_id))
137 ;
139 return $this->status_;
140 }
142 function command_($parent_id)
143 {
144 if (!$this->scanner_->nextTokenIs('identifier'))
145 {
146 if ($this->scanner_->nextTokenIs(array('block-end', 'script-end')))
147 {
148 return $this->done_();
149 }
150 return $this->error_('identifier', $this->scanner_->peekNextToken());
151 }
153 // Get and check a command token
154 $token = $this->scanner_->nextToken();
155 $semantics = $this->getSemantics_($token['text']);
156 if (!$semantics->validCommand($this->getPrevTokenText_($parent_id), $token['line']))
157 {
158 return $this->error_($semantics->message);
159 }
161 // Process eventual arguments
162 $this_node = $this->tree_->addChildTo($parent_id, $token);
163 if ($this->arguments_($this_node, $semantics) == false)
164 {
165 return false;
166 }
168 $token = $this->scanner_->nextToken();
169 if ($token['class'] != 'semicolon')
170 {
171 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
172 {
173 return $this->error_($semantics->message);
174 }
176 if ($token['class'] == 'block-start')
177 {
178 $this->tree_->addChildTo($this_node, $token);
179 $ret = $this->block_($this_node, $semantics);
180 return $ret;
181 }
183 return $this->error_('semicolon', $token);
184 }
186 $this->tree_->addChildTo($this_node, $token);
187 return $this->success_();
188 }
190 function arguments_($parent_id, &$semantics)
191 {
192 while ($this->argument_($parent_id, $semantics))
193 ;
195 if ($this->status_ == true)
196 {
197 $this->testlist_($parent_id, $semantics);
198 }
200 return $this->status_;
201 }
203 function argument_($parent_id, &$semantics)
204 {
205 if ($this->scanner_->nextTokenIs(array('number', 'tag')))
206 {
207 // Check if semantics allow a number or tag
208 $token = $this->scanner_->nextToken();
209 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
210 {
211 return $this->error_($semantics->message);
212 }
214 $this->tree_->addChildTo($parent_id, $token);
215 return $this->success_();
216 }
218 return $this->stringlist_($parent_id, $semantics);
219 }
221 function stringlist_($parent_id, &$semantics)
222 {
223 if (!$this->scanner_->nextTokenIs('left-bracket'))
224 {
225 return $this->string_($parent_id, $semantics);
226 }
228 $token = $this->scanner_->nextToken();
229 if (!$semantics->startStringList($token['line']))
230 {
231 return $this->error_($semantics->message);
232 }
233 $this->tree_->addChildTo($parent_id, $token);
235 while ($token['class'] != 'right-bracket')
236 {
237 if (!$this->string_($parent_id, $semantics))
238 {
239 return $this->status_;
240 }
242 $token = $this->scanner_->nextToken();
244 if ($token['class'] != 'comma' && $token['class'] != 'right-bracket')
245 {
246 return $this->error_('comma or closing bracket', $token);
247 }
249 $this->tree_->addChildTo($parent_id, $token);
250 }
252 $semantics->endStringList();
253 return $this->success_();
254 }
256 function string_($parent_id, &$semantics)
257 {
258 if (!$this->scanner_->nextTokenIs(array('quoted-string', 'multi-line')))
259 {
260 return $this->done_();
261 }
263 $token = $this->scanner_->nextToken();
264 if (!$semantics->validToken('string', $token['text'], $token['line']))
265 {
266 return $this->error_($semantics->message);
267 }
269 $this->tree_->addChildTo($parent_id, $token);
270 return $this->success_();
271 }
273 function testlist_($parent_id, &$semantics)
274 {
275 if (!$this->scanner_->nextTokenIs('left-parant'))
276 {
277 return $this->test_($parent_id, $semantics);
278 }
280 $token = $this->scanner_->nextToken();
281 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
282 {
283 return $this->error_($semantics->message);
284 }
285 $this->tree_->addChildTo($parent_id, $token);
287 while ($token['class'] != 'right-parant')
288 {
289 if (!$this->test_($parent_id, $semantics))
290 {
291 return $this->status_;
292 }
294 $token = $this->scanner_->nextToken();
296 if ($token['class'] != 'comma' && $token['class'] != 'right-parant')
297 {
298 return $this->error_('comma or closing paranthesis', $token);
299 }
301 $this->tree_->addChildTo($parent_id, $token);
302 }
304 return $this->success_();
305 }
307 function test_($parent_id, &$semantics)
308 {
309 if (!$this->scanner_->nextTokenIs('identifier'))
310 {
311 // There is no test
312 return $this->done_();
313 }
315 // Check if semantics allow an identifier
316 $token = $this->scanner_->nextToken();
317 if (!$semantics->validToken($token['class'], $token['text'], $token['line']))
318 {
319 return $this->error_($semantics->message);
320 }
322 // Get semantics for this test command
323 $this_semantics = $this->getSemantics_($token['text']);
324 if (!$this_semantics->validCommand($this->getPrevTokenText_($parent_id), $token['line']))
325 {
326 return $this->error_($this_semantics->message);
327 }
329 $this_node = $this->tree_->addChildTo($parent_id, $token);
331 // Consume eventual argument tokens
332 if (!$this->arguments_($this_node, $this_semantics))
333 {
334 return false;
335 }
337 // Check if arguments were all there
338 $token = $this->scanner_->peekNextToken();
339 if (!$this_semantics->done($token['class'], $token['text'], $token['line']))
340 {
341 return $this->error_($this_semantics->message);
342 }
344 return true;
345 }
347 function block_($parent_id, &$semantics)
348 {
349 if ($this->commands_($parent_id, $semantics))
350 {
351 $token = $this->scanner_->nextToken();
353 if ($token['class'] != 'block-end')
354 {
355 return $this->error_('closing curly brace', $token);
356 }
358 $this->tree_->addChildTo($parent_id, $token);
359 return $this->success_();
360 }
361 return $this->status_;
362 }
363 }
365 ?>