Commit | Line | Data |
---|---|---|
f63072f5 | 1 | <?php |
a1b88eca | 2 | /* |
ef0503fc DO |
3 | * This file implements lexical scanner and syntax analyzer for the |
4 | * RackCode language. These functions are necessary for parsing and | |
5 | * analysis, which don't happen at every script invocation, and this | |
6 | * file is only included when necessary. | |
a1b88eca | 7 | * |
a1b88eca | 8 | */ |
f63072f5 DO |
9 | |
10 | // Complain about martian char. | |
05693a83 | 11 | function lexError1 ($state, $text, $pos, $ln = 'N/A') |
f63072f5 | 12 | { |
05693a83 DO |
13 | return array |
14 | ( | |
15 | 'result' => 'NAK', | |
16 | 'load' => "Invalid character '" . mb_substr ($text, $pos, 1) . "' near line ${ln}" | |
17 | ); | |
f63072f5 DO |
18 | } |
19 | ||
20 | // Complain about martian keyword. | |
05693a83 | 21 | function lexError2 ($word, $ln = 'N/A') |
f63072f5 | 22 | { |
cf25e649 DO |
23 | return array |
24 | ( | |
25 | 'result' => 'NAK', | |
05693a83 | 26 | 'load' => "Invalid keyword '${word}' near line ${ln}" |
cf25e649 | 27 | ); |
f63072f5 DO |
28 | } |
29 | ||
a1b88eca | 30 | // Complain about wrong FSM state. |
05693a83 | 31 | function lexError3 ($state, $ln = 'N/A') |
a1b88eca | 32 | { |
cf25e649 DO |
33 | return array |
34 | ( | |
35 | 'result' => 'NAK', | |
a0527aef | 36 | 'load' => "Lexical error in scanner state '${state}' near line ${ln}" |
cf25e649 | 37 | ); |
a1b88eca DO |
38 | } |
39 | ||
05693a83 | 40 | function lexError4 ($s, $ln = 'N/A') |
9f54e6e9 DO |
41 | { |
42 | return array | |
43 | ( | |
44 | 'result' => 'NAK', | |
05693a83 | 45 | 'load' => "Invalid name '${s}' near line ${ln}" |
9f54e6e9 DO |
46 | ); |
47 | } | |
48 | ||
521a51a2 | 49 | /* Produce a list of lexems (tokens) from the given text. Possible lexems are: |
a0527aef DO |
50 | * |
51 | * LEX_LBRACE | |
52 | * LEX_RBRACE | |
f9cb5a98 DO |
53 | * LEX_ALLOW |
54 | * LEX_DENY | |
a0527aef | 55 | * LEX_DEFINE |
e332b979 DO |
56 | * LEX_TRUE |
57 | * LEX_FALSE | |
a0527aef DO |
58 | * LEX_NOT |
59 | * LEX_TAG | |
60 | * LEX_AUTOTAG | |
61 | * LEX_PREDICATE | |
521a51a2 DO |
62 | * LEX_AND |
63 | * LEX_OR | |
a0527aef DO |
64 | * LEX_CONTEXT |
65 | * LEX_CLEAR | |
66 | * LEX_INSERT | |
67 | * LEX_REMOVE | |
68 | * LEX_ON | |
69 | * | |
04d16213 | 70 | * FIXME: would these work better as normal (integer) constants? |
a0527aef DO |
71 | */ |
72 | function getLexemsFromRawText ($text) | |
f63072f5 DO |
73 | { |
74 | $ret = array(); | |
a0527aef DO |
75 | // Add a mock character to aid in synchronization with otherwise correct, |
76 | // but short or odd-terminated final lines. | |
77 | $text .= ' '; | |
bcd49780 | 78 | $textlen = mb_strlen ($text); |
05693a83 | 79 | $lineno = 1; |
f9cb5a98 | 80 | $state = 'ESOTSM'; |
f63072f5 | 81 | for ($i = 0; $i < $textlen; $i++) : |
bcd49780 | 82 | $char = mb_substr ($text, $i, 1); |
f63072f5 DO |
83 | $newstate = $state; |
84 | switch ($state) : | |
85 | case 'ESOTSM': | |
86 | switch (TRUE) | |
87 | { | |
f63072f5 | 88 | case ($char == '('): |
05693a83 | 89 | $ret[] = array ('type' => 'LEX_LBRACE', 'lineno' => $lineno); |
f63072f5 DO |
90 | break; |
91 | case ($char == ')'): | |
05693a83 | 92 | $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno); |
f63072f5 DO |
93 | break; |
94 | case ($char == '#'): | |
95 | $newstate = 'skipping comment'; | |
96 | break; | |
b7b4b225 | 97 | case (preg_match ('/[\p{L}]/u', $char) == 1): |
cf25e649 | 98 | $newstate = 'reading keyword'; |
f63072f5 DO |
99 | $buffer = $char; |
100 | break; | |
05693a83 DO |
101 | case ($char == "\n"): |
102 | $lineno++; // fall through | |
f9cb5a98 | 103 | case ($char == ' '): |
05693a83 | 104 | case ($char == "\t"): |
f63072f5 DO |
105 | // nom-nom... |
106 | break; | |
107 | case ($char == '{'): | |
108 | $newstate = 'reading tag 1'; | |
109 | break; | |
110 | case ($char == '['): | |
111 | $newstate = 'reading predicate 1'; | |
112 | break; | |
113 | default: | |
05693a83 | 114 | return lexError1 ($state, $text, $i, $lineno); |
f63072f5 DO |
115 | } |
116 | break; | |
cf25e649 | 117 | case 'reading keyword': |
f63072f5 DO |
118 | switch (TRUE) |
119 | { | |
b7b4b225 | 120 | case (preg_match ('/[\p{L}]/u', $char) == 1): |
f63072f5 DO |
121 | $buffer .= $char; |
122 | break; | |
05693a83 DO |
123 | case ($char == "\n"): |
124 | $lineno++; // fall through | |
f9cb5a98 | 125 | case ($char == ' '): |
05693a83 | 126 | case ($char == "\t"): |
f5e48a49 | 127 | case ($char == ')'): // this will be handled below |
f63072f5 DO |
128 | // got a word, sort it out |
129 | switch ($buffer) | |
130 | { | |
131 | case 'allow': | |
f9cb5a98 DO |
132 | $ret[] = array ('type' => 'LEX_ALLOW', 'lineno' => $lineno); |
133 | break; | |
f63072f5 | 134 | case 'deny': |
f9cb5a98 | 135 | $ret[] = array ('type' => 'LEX_DENY', 'lineno' => $lineno); |
f63072f5 DO |
136 | break; |
137 | case 'define': | |
05693a83 | 138 | $ret[] = array ('type' => 'LEX_DEFINE', 'lineno' => $lineno); |
f63072f5 DO |
139 | break; |
140 | case 'and': | |
521a51a2 DO |
141 | $ret[] = array ('type' => 'LEX_AND', 'lineno' => $lineno); |
142 | break; | |
f63072f5 | 143 | case 'or': |
521a51a2 | 144 | $ret[] = array ('type' => 'LEX_OR', 'lineno' => $lineno); |
a1b88eca DO |
145 | break; |
146 | case 'not': | |
05693a83 | 147 | $ret[] = array ('type' => 'LEX_NOT', 'lineno' => $lineno); |
a1b88eca | 148 | break; |
a1b88eca | 149 | case 'true': |
e332b979 DO |
150 | $ret[] = array ('type' => 'LEX_TRUE', 'lineno' => $lineno); |
151 | break; | |
152 | case 'false': | |
153 | $ret[] = array ('type' => 'LEX_FALSE', 'lineno' => $lineno); | |
f63072f5 | 154 | break; |
578d9d02 DO |
155 | case 'context': |
156 | $ret[] = array ('type' => 'LEX_CONTEXT', 'lineno' => $lineno); | |
157 | break; | |
158 | case 'clear': | |
159 | $ret[] = array ('type' => 'LEX_CLEAR', 'lineno' => $lineno); | |
160 | break; | |
161 | case 'insert': | |
162 | $ret[] = array ('type' => 'LEX_INSERT', 'lineno' => $lineno); | |
163 | break; | |
164 | case 'remove': | |
165 | $ret[] = array ('type' => 'LEX_REMOVE', 'lineno' => $lineno); | |
166 | break; | |
167 | case 'on': | |
168 | $ret[] = array ('type' => 'LEX_ON', 'lineno' => $lineno); | |
169 | break; | |
f63072f5 | 170 | default: |
05693a83 | 171 | return lexError2 ($buffer, $lineno); |
f63072f5 | 172 | } |
f5e48a49 DO |
173 | if ($char == ')') |
174 | $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno); | |
f63072f5 DO |
175 | $newstate = 'ESOTSM'; |
176 | break; | |
177 | default: | |
05693a83 | 178 | return lexError1 ($state, $text, $i, $lineno); |
f63072f5 DO |
179 | } |
180 | break; | |
181 | case 'reading tag 1': | |
182 | switch (TRUE) | |
183 | { | |
05693a83 | 184 | case ($char == "\n"): |
05693a83 | 185 | $lineno++; // fall through |
f9cb5a98 | 186 | case ($char == ' '): |
05693a83 | 187 | case ($char == "\t"): |
f63072f5 DO |
188 | // nom-nom... |
189 | break; | |
b7b4b225 | 190 | case (preg_match ('/[\p{L}0-9\$]/u', $char) == 1): |
f63072f5 DO |
191 | $buffer = $char; |
192 | $newstate = 'reading tag 2'; | |
193 | break; | |
194 | default: | |
05693a83 | 195 | return lexError1 ($state, $text, $i, $lineno); |
f63072f5 DO |
196 | } |
197 | break; | |
198 | case 'reading tag 2': | |
199 | switch (TRUE) | |
200 | { | |
201 | case ($char == '}'): | |
e5bd52e5 | 202 | $buffer = rtrim ($buffer); |
9f54e6e9 | 203 | if (!validTagName ($buffer, TRUE)) |
05693a83 | 204 | return lexError4 ($buffer, $lineno); |
d42589e4 | 205 | $ret[] = array ('type' => ($buffer[0] == '$' ? 'LEX_AUTOTAG' : 'LEX_TAG'), 'load' => $buffer, 'lineno' => $lineno); |
f63072f5 DO |
206 | $newstate = 'ESOTSM'; |
207 | break; | |
b7b4b225 | 208 | case (preg_match ('/[\p{L}0-9. _~-]/u', $char) == 1): |
f63072f5 DO |
209 | $buffer .= $char; |
210 | break; | |
211 | default: | |
05693a83 | 212 | return lexError1 ($state, $text, $i, $lineno); |
f63072f5 DO |
213 | } |
214 | break; | |
215 | case 'reading predicate 1': | |
216 | switch (TRUE) | |
217 | { | |
f9cb5a98 DO |
218 | case ($char == "\n"): |
219 | $lineno++; // fall through | |
220 | case ($char == ' '): | |
221 | case ($char == "\t"): | |
f63072f5 DO |
222 | // nom-nom... |
223 | break; | |
b7b4b225 | 224 | case (preg_match ('/[\p{L}0-9]/u', $char) == 1): |
f63072f5 DO |
225 | $buffer = $char; |
226 | $newstate = 'reading predicate 2'; | |
227 | break; | |
228 | default: | |
05693a83 | 229 | return lexError1 ($state, $text, $i, $lineno); |
f63072f5 DO |
230 | } |
231 | break; | |
232 | case 'reading predicate 2': | |
233 | switch (TRUE) | |
234 | { | |
235 | case ($char == ']'): | |
e5bd52e5 | 236 | $buffer = rtrim ($buffer); |
9f54e6e9 | 237 | if (!validTagName ($buffer)) |
05693a83 DO |
238 | return lexError4 ($buffer, $lineno); |
239 | $ret[] = array ('type' => 'LEX_PREDICATE', 'load' => $buffer, 'lineno' => $lineno); | |
f63072f5 DO |
240 | $newstate = 'ESOTSM'; |
241 | break; | |
b7b4b225 | 242 | case (preg_match ('/[\p{L}0-9. _~-]/u', $char) == 1): |
f63072f5 DO |
243 | $buffer .= $char; |
244 | break; | |
245 | default: | |
05693a83 | 246 | return lexError1 ($state, $text, $i, $lineno); |
f63072f5 DO |
247 | } |
248 | break; | |
249 | case 'skipping comment': | |
bcd49780 | 250 | switch ($char) |
f63072f5 DO |
251 | { |
252 | case "\n": | |
05693a83 | 253 | $lineno++; |
f63072f5 DO |
254 | $newstate = 'ESOTSM'; |
255 | default: // eat char, nom-nom... | |
256 | break; | |
257 | } | |
258 | break; | |
259 | default: | |
260 | die (__FUNCTION__ . "(): internal error, state == ${state}"); | |
261 | endswitch; | |
262 | $state = $newstate; | |
263 | endfor; | |
e6a4adb9 | 264 | if ($state != 'ESOTSM' and $state != 'skipping comment') |
05693a83 | 265 | return lexError3 ($state, $lineno); |
cf25e649 | 266 | return array ('result' => 'ACK', 'load' => $ret); |
f63072f5 DO |
267 | } |
268 | ||
a1b88eca | 269 | // Parse the given lexems stream into a list of RackCode sentences. Each such |
a27a02c5 DO |
270 | // sentence is a syntax tree, suitable for tag sequence evaluation. The final |
271 | // parse tree may contain the following nodes: | |
272 | // LEX_TAG | |
d42589e4 | 273 | // LEX_AUTOTAG |
a27a02c5 | 274 | // LEX_PREDICATE |
e332b979 DO |
275 | // LEX_TRUE |
276 | // LEX_FALSE | |
f9cb5a98 DO |
277 | // SYNT_NOT_EXPR (one arg in "load") |
278 | // SYNT_AND_EXPR (two args in "left" and "right") | |
521a51a2 | 279 | // SYNT_EXPR (idem), in fact it's boolean OR, but we keep the naming for compatibility |
f9cb5a98 DO |
280 | // SYNT_DEFINITION (2 args in "term" and "definition") |
281 | // SYNT_GRANT (2 args in "decision" and "condition") | |
578d9d02 | 282 | // SYNT_ADJUSTMENT (context modifier with action(s) and condition) |
1381b655 | 283 | // SYNT_CODETEXT (sequence of sentences) |
a27a02c5 DO |
284 | // |
285 | // After parsing the input successfully a list of SYNT_GRANT and SYNT_DEFINITION | |
286 | // trees is returned. | |
a0527aef DO |
287 | // |
288 | // P.S. The above is true for input, which is a complete and correct RackCode text. | |
289 | // Other inputs may produce different combinations of lex/synt structures. Calling | |
290 | // function must check the parse tree itself. | |
291 | function getParseTreeFromLexems ($lexems) | |
a1b88eca | 292 | { |
a1b88eca DO |
293 | $stack = array(); // subject to array_push() and array_pop() |
294 | $done = 0; // $lexems[$done] is the next item in the tape | |
295 | $todo = count ($lexems); | |
296 | ||
1381b655 DO |
297 | // Perform shift-reduce processing. The "accept" actions occurs with an |
298 | // empty input tape and the stack holding only one symbol (the start | |
05693a83 DO |
299 | // symbol, SYNT_CODETEXT). When reducing, set the "line number" of |
300 | // the reduction result to the line number of the "latest" item of the | |
301 | // reduction base (the one on the stack top). This will help locating | |
302 | // parse errors, if any. | |
1381b655 | 303 | while (TRUE) |
a1b88eca | 304 | { |
578d9d02 | 305 | $stacktop = $stacksecondtop = $stackthirdtop = $stackfourthtop = array ('type' => 'null'); |
a1b88eca DO |
306 | $stacksize = count ($stack); |
307 | if ($stacksize >= 1) | |
308 | { | |
309 | $stacktop = array_pop ($stack); | |
310 | // It is possible to run into a S/R conflict, when having a syntaxically | |
311 | // correct sentence base on the stack and some "and {something}" items | |
312 | // on the input tape, hence let's detect this specific case and insist | |
f9cb5a98 | 313 | // on "shift" action to make SYNT_AND_EXPR parsing hungry. |
09f6ee21 DO |
314 | // P.S. Same action is taken for SYNT_EXPR (logical-OR) to prevent |
315 | // premature reduction of "condition" for grant/definition/context | |
316 | // modifier sentences. The shift tries to be conservative, it advances | |
317 | // by only one token on the tape. | |
1381b655 DO |
318 | if |
319 | ( | |
09f6ee21 DO |
320 | $stacktop['type'] == 'SYNT_AND_EXPR' and $done < $todo and $lexems[$done]['type'] == 'LEX_AND' or |
321 | $stacktop['type'] == 'SYNT_EXPR' and $done < $todo and $lexems[$done]['type'] == 'LEX_OR' | |
1381b655 | 322 | ) |
a1b88eca DO |
323 | { |
324 | // shift! | |
325 | array_push ($stack, $stacktop); | |
326 | array_push ($stack, $lexems[$done++]); | |
327 | continue; | |
328 | } | |
329 | if ($stacksize >= 2) | |
330 | { | |
331 | $stacksecondtop = array_pop ($stack); | |
332 | if ($stacksize >= 3) | |
333 | { | |
334 | $stackthirdtop = array_pop ($stack); | |
578d9d02 DO |
335 | if ($stacksize >= 4) |
336 | { | |
337 | $stackfourthtop = array_pop ($stack); | |
338 | array_push ($stack, $stackfourthtop); | |
339 | } | |
a1b88eca DO |
340 | array_push ($stack, $stackthirdtop); |
341 | } | |
342 | array_push ($stack, $stacksecondtop); | |
343 | } | |
344 | array_push ($stack, $stacktop); | |
1381b655 | 345 | // First detect definition start to save the predicate from being reduced into |
521a51a2 DO |
346 | // unary expression. |
347 | // DEFINE ::= define PREDICATE | |
1381b655 DO |
348 | if |
349 | ( | |
350 | $stacktop['type'] == 'LEX_PREDICATE' and | |
351 | $stacksecondtop['type'] == 'LEX_DEFINE' | |
352 | ) | |
353 | { | |
05693a83 | 354 | // reduce! |
1381b655 DO |
355 | array_pop ($stack); |
356 | array_pop ($stack); | |
357 | array_push | |
358 | ( | |
359 | $stack, | |
360 | array | |
361 | ( | |
362 | 'type' => 'SYNT_DEFINE', | |
05693a83 | 363 | 'lineno' => $stacktop['lineno'], |
1381b655 DO |
364 | 'load' => $stacktop['load'] |
365 | ) | |
366 | ); | |
367 | continue; | |
368 | } | |
521a51a2 | 369 | // CTXMOD ::= clear |
578d9d02 DO |
370 | if |
371 | ( | |
372 | $stacktop['type'] == 'LEX_CLEAR' | |
373 | ) | |
374 | { | |
375 | // reduce! | |
376 | array_pop ($stack); | |
377 | array_push | |
378 | ( | |
379 | $stack, | |
380 | array | |
381 | ( | |
382 | 'type' => 'SYNT_CTXMOD', | |
383 | 'lineno' => $stacktop['lineno'], | |
384 | 'load' => array ('op' => 'clear') | |
385 | ) | |
386 | ); | |
387 | continue; | |
388 | } | |
521a51a2 | 389 | // CTXMOD ::= insert TAG |
578d9d02 DO |
390 | if |
391 | ( | |
392 | $stacktop['type'] == 'LEX_TAG' and | |
393 | $stacksecondtop['type'] == 'LEX_INSERT' | |
394 | ) | |
395 | { | |
396 | // reduce! | |
397 | array_pop ($stack); | |
534d6c2a | 398 | array_pop ($stack); |
578d9d02 DO |
399 | array_push |
400 | ( | |
401 | $stack, | |
402 | array | |
403 | ( | |
404 | 'type' => 'SYNT_CTXMOD', | |
405 | 'lineno' => $stacktop['lineno'], | |
d94022fe | 406 | 'load' => array ('op' => 'insert', 'tag' => $stacktop['load'], 'lineno' => $stacktop['lineno']) |
578d9d02 DO |
407 | ) |
408 | ); | |
409 | continue; | |
410 | } | |
521a51a2 | 411 | // CTXMOD ::= remove TAG |
578d9d02 DO |
412 | if |
413 | ( | |
414 | $stacktop['type'] == 'LEX_TAG' and | |
415 | $stacksecondtop['type'] == 'LEX_REMOVE' | |
416 | ) | |
417 | { | |
418 | // reduce! | |
419 | array_pop ($stack); | |
534d6c2a | 420 | array_pop ($stack); |
578d9d02 DO |
421 | array_push |
422 | ( | |
423 | $stack, | |
424 | array | |
425 | ( | |
426 | 'type' => 'SYNT_CTXMOD', | |
427 | 'lineno' => $stacktop['lineno'], | |
d94022fe | 428 | 'load' => array ('op' => 'remove', 'tag' => $stacktop['load'], 'lineno' => $stacktop['lineno']) |
578d9d02 DO |
429 | ) |
430 | ); | |
431 | continue; | |
432 | } | |
09f6ee21 | 433 | // CTXMODLIST ::= CTXMODLIST CTXMOD |
578d9d02 DO |
434 | if |
435 | ( | |
09f6ee21 DO |
436 | $stacktop['type'] == 'SYNT_CTXMOD' and |
437 | $stacksecondtop['type'] == 'SYNT_CTXMODLIST' | |
578d9d02 DO |
438 | ) |
439 | { | |
09f6ee21 | 440 | array_pop ($stack); |
578d9d02 DO |
441 | array_pop ($stack); |
442 | array_push | |
443 | ( | |
444 | $stack, | |
445 | array | |
446 | ( | |
447 | 'type' => 'SYNT_CTXMODLIST', | |
448 | 'lineno' => $stacktop['lineno'], | |
09f6ee21 | 449 | 'load' => array_merge ($stacksecondtop['load'], array ($stacktop['load'])) |
578d9d02 DO |
450 | ) |
451 | ); | |
452 | continue; | |
453 | } | |
09f6ee21 | 454 | // CTXMODLIST ::= CTXMOD |
578d9d02 DO |
455 | if |
456 | ( | |
09f6ee21 | 457 | $stacktop['type'] == 'SYNT_CTXMOD' |
578d9d02 DO |
458 | ) |
459 | { | |
578d9d02 DO |
460 | array_pop ($stack); |
461 | array_push | |
462 | ( | |
463 | $stack, | |
464 | array | |
465 | ( | |
466 | 'type' => 'SYNT_CTXMODLIST', | |
467 | 'lineno' => $stacktop['lineno'], | |
09f6ee21 | 468 | 'load' => array ($stacktop['load']) |
578d9d02 DO |
469 | ) |
470 | ); | |
471 | continue; | |
472 | } | |
a1b88eca DO |
473 | // Try "replace" action only on a non-empty stack. |
474 | // If a handle is found for reversing a production rule, do it and start a new | |
475 | // cycle instead of advancing further on rule list. This will preserve rule priority | |
476 | // in the grammar and keep us from an extra shift action. | |
521a51a2 | 477 | // UNARY_EXPRESSION ::= true | false | TAG | AUTOTAG | PREDICATE |
a1b88eca DO |
478 | if |
479 | ( | |
e332b979 DO |
480 | $stacktop['type'] == 'LEX_TAG' or // first look for tokens, which are most |
481 | $stacktop['type'] == 'LEX_AUTOTAG' or // likely to appear in the text | |
482 | $stacktop['type'] == 'LEX_PREDICATE' or // supplied by user | |
483 | $stacktop['type'] == 'LEX_TRUE' or | |
484 | $stacktop['type'] == 'LEX_FALSE' | |
a1b88eca DO |
485 | ) |
486 | { | |
1381b655 | 487 | // reduce! |
a1b88eca DO |
488 | array_pop ($stack); |
489 | array_push | |
490 | ( | |
491 | $stack, | |
492 | array | |
493 | ( | |
521a51a2 | 494 | 'type' => 'SYNT_UNARY_EXPR', |
05693a83 | 495 | 'lineno' => $stacktop['lineno'], |
a1b88eca DO |
496 | 'load' => $stacktop |
497 | ) | |
498 | ); | |
499 | continue; | |
500 | } | |
521a51a2 DO |
501 | // UNARY_EXPRESSION ::= (EXPRESSION) |
502 | // Useful trick about AND- and OR-expressions is to check, if the | |
503 | // node we are reducing contains only 1 argument. In this case | |
504 | // discard the wrapper and join the "load" argument into new node directly. | |
a1b88eca DO |
505 | if |
506 | ( | |
521a51a2 DO |
507 | $stacktop['type'] == 'LEX_RBRACE' and |
508 | $stacksecondtop['type'] == 'SYNT_EXPR' and | |
509 | $stackthirdtop['type'] == 'LEX_LBRACE' | |
510 | ) | |
511 | { | |
512 | // reduce! | |
513 | array_pop ($stack); | |
514 | array_pop ($stack); | |
515 | array_pop ($stack); | |
516 | array_push | |
517 | ( | |
518 | $stack, | |
519 | array | |
520 | ( | |
521 | 'type' => 'SYNT_UNARY_EXPR', | |
522 | 'lineno' => $stacksecondtop['lineno'], | |
523 | 'load' => isset ($stacksecondtop['load']) ? $stacksecondtop['load'] : $stacksecondtop | |
524 | ) | |
525 | ); | |
526 | continue; | |
527 | } | |
528 | // UNARY_EXPRESSION ::= not UNARY_EXPRESSION | |
529 | if | |
530 | ( | |
531 | $stacktop['type'] == 'SYNT_UNARY_EXPR' and | |
a1b88eca DO |
532 | $stacksecondtop['type'] == 'LEX_NOT' |
533 | ) | |
534 | { | |
1381b655 | 535 | // reduce! |
a1b88eca DO |
536 | array_pop ($stack); |
537 | array_pop ($stack); | |
538 | array_push | |
539 | ( | |
540 | $stack, | |
541 | array | |
542 | ( | |
521a51a2 | 543 | 'type' => 'SYNT_UNARY_EXPR', |
05693a83 | 544 | 'lineno' => $stacktop['lineno'], |
a1b88eca DO |
545 | 'load' => array |
546 | ( | |
521a51a2 | 547 | 'type' => 'SYNT_NOT_EXPR', |
a27a02c5 | 548 | 'load' => $stacktop['load'] |
a1b88eca DO |
549 | ) |
550 | ) | |
551 | ); | |
552 | continue; | |
553 | } | |
521a51a2 | 554 | // AND_EXPRESSION ::= AND_EXPRESSION and UNARY_EXPRESSION |
a1b88eca DO |
555 | if |
556 | ( | |
521a51a2 DO |
557 | $stacktop['type'] == 'SYNT_UNARY_EXPR' and |
558 | $stacksecondtop['type'] == 'LEX_AND' and | |
559 | $stackthirdtop['type'] == 'SYNT_AND_EXPR' | |
a1b88eca DO |
560 | ) |
561 | { | |
562 | array_pop ($stack); | |
563 | array_pop ($stack); | |
564 | array_pop ($stack); | |
565 | array_push | |
566 | ( | |
567 | $stack, | |
521a51a2 DO |
568 | array |
569 | ( | |
570 | 'type' => 'SYNT_AND_EXPR', | |
571 | 'lineno' => $stacktop['lineno'], | |
572 | 'left' => isset ($stackthirdtop['load']) ? $stackthirdtop['load'] : $stackthirdtop, | |
573 | 'right' => $stacktop['load'] | |
574 | ) | |
a1b88eca DO |
575 | ); |
576 | continue; | |
577 | } | |
521a51a2 | 578 | // AND_EXPRESSION ::= UNARY_EXPRESSION |
a1b88eca DO |
579 | if |
580 | ( | |
521a51a2 DO |
581 | $stacktop['type'] == 'SYNT_UNARY_EXPR' |
582 | ) | |
583 | { | |
584 | array_pop ($stack); | |
585 | array_push | |
586 | ( | |
587 | $stack, | |
588 | array | |
589 | ( | |
590 | 'type' => 'SYNT_AND_EXPR', | |
591 | 'lineno' => $stacktop['lineno'], | |
592 | 'load' => $stacktop['load'] | |
593 | ) | |
594 | ); | |
595 | continue; | |
596 | } | |
597 | // EXPRESSION ::= EXPRESSION or AND_EXPRESSION | |
598 | if | |
599 | ( | |
600 | $stacktop['type'] == 'SYNT_AND_EXPR' and | |
601 | $stacksecondtop['type'] == 'LEX_OR' and | |
a1b88eca DO |
602 | $stackthirdtop['type'] == 'SYNT_EXPR' |
603 | ) | |
604 | { | |
605 | array_pop ($stack); | |
606 | array_pop ($stack); | |
607 | array_pop ($stack); | |
608 | array_push | |
609 | ( | |
610 | $stack, | |
611 | array | |
612 | ( | |
613 | 'type' => 'SYNT_EXPR', | |
05693a83 | 614 | 'lineno' => $stacktop['lineno'], |
521a51a2 DO |
615 | 'left' => isset ($stackthirdtop['load']) ? $stackthirdtop['load'] : $stackthirdtop, |
616 | 'right' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop | |
617 | ) | |
618 | ); | |
619 | continue; | |
620 | } | |
621 | // EXPRESSION ::= AND_EXPRESSION | |
622 | if | |
623 | ( | |
624 | $stacktop['type'] == 'SYNT_AND_EXPR' | |
625 | ) | |
626 | { | |
627 | array_pop ($stack); | |
628 | array_push | |
629 | ( | |
630 | $stack, | |
631 | array | |
632 | ( | |
633 | 'type' => 'SYNT_EXPR', | |
634 | 'lineno' => $stacktop['lineno'], | |
635 | 'load' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop | |
a1b88eca DO |
636 | ) |
637 | ); | |
638 | continue; | |
639 | } | |
f9cb5a98 | 640 | // GRANT ::= allow EXPRESSION | deny EXPRESSION |
1381b655 DO |
641 | if |
642 | ( | |
643 | $stacktop['type'] == 'SYNT_EXPR' and | |
f9cb5a98 | 644 | ($stacksecondtop['type'] == 'LEX_ALLOW' or $stacksecondtop['type'] == 'LEX_DENY') |
1381b655 DO |
645 | ) |
646 | { | |
647 | // reduce! | |
648 | array_pop ($stack); | |
649 | array_pop ($stack); | |
650 | array_push | |
651 | ( | |
652 | $stack, | |
653 | array | |
654 | ( | |
655 | 'type' => 'SYNT_GRANT', | |
05693a83 | 656 | 'lineno' => $stacktop['lineno'], |
f9cb5a98 | 657 | 'decision' => $stacksecondtop['type'], |
8da17b46 | 658 | 'condition' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop |
1381b655 DO |
659 | ) |
660 | ); | |
661 | continue; | |
662 | } | |
8da17b46 | 663 | // DEFINITION ::= DEFINE EXPRESSION |
1381b655 DO |
664 | if |
665 | ( | |
666 | $stacktop['type'] == 'SYNT_EXPR' and | |
667 | $stacksecondtop['type'] == 'SYNT_DEFINE' | |
668 | ) | |
669 | { | |
670 | // reduce! | |
671 | array_pop ($stack); | |
672 | array_pop ($stack); | |
673 | array_push | |
674 | ( | |
675 | $stack, | |
676 | array | |
677 | ( | |
678 | 'type' => 'SYNT_DEFINITION', | |
05693a83 | 679 | 'lineno' => $stacktop['lineno'], |
a27a02c5 | 680 | 'term' => $stacksecondtop['load'], |
8da17b46 | 681 | 'definition' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop |
1381b655 DO |
682 | ) |
683 | ); | |
684 | continue; | |
685 | } | |
521a51a2 | 686 | // ADJUSTMENT ::= context CTXMODLIST on EXPRESSION |
1381b655 DO |
687 | if |
688 | ( | |
578d9d02 DO |
689 | $stacktop['type'] == 'SYNT_EXPR' and |
690 | $stacksecondtop['type'] == 'LEX_ON' and | |
691 | $stackthirdtop['type'] == 'SYNT_CTXMODLIST' and | |
692 | $stackfourthtop['type'] == 'LEX_CONTEXT' | |
693 | ) | |
694 | { | |
695 | // reduce! | |
696 | array_pop ($stack); | |
697 | array_pop ($stack); | |
698 | array_pop ($stack); | |
699 | array_pop ($stack); | |
700 | array_push | |
701 | ( | |
702 | $stack, | |
703 | array | |
704 | ( | |
705 | 'type' => 'SYNT_ADJUSTMENT', | |
706 | 'lineno' => $stacktop['lineno'], | |
707 | 'modlist' => $stackthirdtop['load'], | |
8da17b46 | 708 | 'condition' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop |
578d9d02 DO |
709 | ) |
710 | ); | |
711 | continue; | |
712 | } | |
f9cb5a98 | 713 | // CODETEXT ::= CODETEXT GRANT | CODETEXT DEFINITION | CODETEXT ADJUSTMENT |
578d9d02 DO |
714 | if |
715 | ( | |
716 | ($stacktop['type'] == 'SYNT_GRANT' or $stacktop['type'] == 'SYNT_DEFINITION' or $stacktop['type'] == 'SYNT_ADJUSTMENT') and | |
1381b655 DO |
717 | $stacksecondtop['type'] == 'SYNT_CODETEXT' |
718 | ) | |
719 | { | |
720 | // reduce! | |
721 | array_pop ($stack); | |
722 | array_pop ($stack); | |
723 | $stacksecondtop['load'][] = $stacktop; | |
05693a83 | 724 | $stacksecondtop['lineno'] = $stacktop['lineno']; |
1381b655 DO |
725 | array_push |
726 | ( | |
727 | $stack, | |
728 | $stacksecondtop | |
729 | ); | |
730 | continue; | |
731 | } | |
f9cb5a98 | 732 | // CODETEXT ::= GRANT | DEFINITION | ADJUSTMENT |
1381b655 DO |
733 | if |
734 | ( | |
a27a02c5 | 735 | $stacktop['type'] == 'SYNT_GRANT' or |
578d9d02 DO |
736 | $stacktop['type'] == 'SYNT_DEFINITION' or |
737 | $stacktop['type'] == 'SYNT_ADJUSTMENT' | |
1381b655 DO |
738 | ) |
739 | { | |
740 | // reduce! | |
741 | array_pop ($stack); | |
1381b655 DO |
742 | array_push |
743 | ( | |
744 | $stack, | |
745 | array | |
746 | ( | |
747 | 'type' => 'SYNT_CODETEXT', | |
05693a83 | 748 | 'lineno' => $stacktop['lineno'], |
1381b655 DO |
749 | 'load' => array ($stacktop) |
750 | ) | |
751 | ); | |
752 | continue; | |
753 | } | |
754 | } | |
755 | // The fact we execute here means, that no reduction or early shift | |
756 | // has been done. The only way to enter another iteration is to "shift" | |
a0527aef DO |
757 | // more, if possible. If shifting isn't possible due to empty input tape, |
758 | // we are facing the final "accept"/"reject" dilemma. In this case our | |
759 | // work is done here, so return the whole stack to the calling function | |
760 | // to decide depending on what it is expecting. | |
1381b655 DO |
761 | if ($done < $todo) |
762 | { | |
763 | array_push ($stack, $lexems[$done++]); | |
764 | continue; | |
a1b88eca | 765 | } |
1381b655 | 766 | // The moment of truth. |
a0527aef | 767 | return $stack; |
a1b88eca | 768 | } |
a1b88eca DO |
769 | } |
770 | ||
3082e137 DO |
771 | // Return NULL, if the given expression can be evaluated against the given |
772 | // predicate list. Return the name of the first show stopper otherwise. | |
773 | function firstUnrefPredicate ($plist, $expr) | |
e6a4adb9 | 774 | { |
e332b979 | 775 | $self = __FUNCTION__; |
e6a4adb9 DO |
776 | switch ($expr['type']) |
777 | { | |
e332b979 DO |
778 | case 'LEX_TRUE': |
779 | case 'LEX_FALSE': | |
e6a4adb9 | 780 | case 'LEX_TAG': |
d42589e4 | 781 | case 'LEX_AUTOTAG': |
3082e137 | 782 | return NULL; |
e6a4adb9 | 783 | case 'LEX_PREDICATE': |
3082e137 | 784 | return in_array ($expr['load'], $plist) ? NULL : $expr['load']; |
521a51a2 | 785 | case 'SYNT_NOT_EXPR': |
e332b979 | 786 | return $self ($plist, $expr['load']); |
aaa8d409 DO |
787 | case 'SYNT_EXPR': |
788 | case 'SYNT_AND_EXPR': | |
e332b979 | 789 | if (($tmp = $self ($plist, $expr['left'])) !== NULL) |
3082e137 | 790 | return $tmp; |
e332b979 | 791 | if (($tmp = $self ($plist, $expr['right'])) !== NULL) |
3082e137 DO |
792 | return $tmp; |
793 | return NULL; | |
e6a4adb9 | 794 | default: |
3082e137 | 795 | return NULL; |
e6a4adb9 DO |
796 | } |
797 | } | |
798 | ||
cf25e649 | 799 | function semanticFilter ($code) |
e6a4adb9 DO |
800 | { |
801 | $predicatelist = array(); | |
802 | foreach ($code as $sentence) | |
803 | switch ($sentence['type']) | |
804 | { | |
805 | case 'SYNT_DEFINITION': | |
f1aa773e DO |
806 | // A predicate can only be defined once. |
807 | if (in_array ($sentence['term'], $predicatelist)) | |
808 | return array | |
809 | ( | |
810 | 'result' => 'NAK', | |
811 | 'load' => "[${sentence['term']}] has already been defined earlier" | |
812 | ); | |
813 | // Check below makes sure, that definitions are built from already existing | |
814 | // tokens. This also makes recursive definitions impossible. | |
3082e137 DO |
815 | $up = firstUnrefPredicate ($predicatelist, $sentence['definition']); |
816 | if ($up !== NULL) | |
817 | return array | |
818 | ( | |
819 | 'result' => 'NAK', | |
f1aa773e | 820 | 'load' => "definition of [${sentence['term']}] refers to [${up}], which is not (yet) defined" |
3082e137 | 821 | ); |
e6a4adb9 DO |
822 | $predicatelist[] = $sentence['term']; |
823 | break; | |
824 | case 'SYNT_GRANT': | |
3082e137 DO |
825 | $up = firstUnrefPredicate ($predicatelist, $sentence['condition']); |
826 | if ($up !== NULL) | |
827 | return array | |
828 | ( | |
829 | 'result' => 'NAK', | |
830 | 'load' => "grant sentence uses unknown predicate [${up}]" | |
831 | ); | |
e6a4adb9 | 832 | break; |
578d9d02 DO |
833 | case 'SYNT_ADJUSTMENT': |
834 | // Only condition part gets tested, because it's normal to set (or even to unset) | |
835 | // something, that's not set. | |
836 | $up = firstUnrefPredicate ($predicatelist, $sentence['condition']); | |
837 | if ($up !== NULL) | |
838 | return array | |
839 | ( | |
840 | 'result' => 'NAK', | |
841 | 'load' => "adjustment sentence uses unknown predicate [${up}]" | |
842 | ); | |
843 | break; | |
e6a4adb9 | 844 | default: |
cf25e649 | 845 | return array ('result' => 'NAK', 'load' => 'unknown sentence type'); |
e6a4adb9 | 846 | } |
cf25e649 | 847 | return array ('result' => 'ACK', 'load' => $code); |
e6a4adb9 DO |
848 | } |
849 | ||
05693a83 DO |
850 | // Accept a stack and figure out the cause of it not being parsed into a tree. |
851 | // Return the line number or zero. | |
852 | function locateSyntaxError ($stack) | |
853 | { | |
854 | // The first SYNT_CODETEXT node, if is present, holds stuff already | |
855 | // successfully processed. Its line counter shows, where the last reduction | |
856 | // took place (it _might_ be the same line, which causes the syntax error). | |
857 | // The next node (it's very likely to exist) should have its line counter | |
858 | // pointing to the place, where the first (of 1 or more) error is located. | |
859 | if (isset ($stack[0]['type']) and $stack[0]['type'] == 'SYNT_CODETEXT') | |
860 | unset ($stack[0]); | |
861 | foreach ($stack as $node) | |
862 | // Satisfy with the first line number met. | |
863 | if (isset ($node['lineno'])) | |
864 | return $node['lineno']; | |
865 | return 0; | |
866 | } | |
867 | ||
061452b9 DO |
868 | function refRCLineno ($ln) |
869 | { | |
790a60e8 | 870 | return "<a href='index.php?page=perms&tab=default#line${ln}'>line ${ln}</a>"; |
061452b9 DO |
871 | } |
872 | ||
52837ce6 | 873 | // Scan the given expression and return any issues found about its autotags. |
15555995 DO |
874 | function findAutoTagWarnings ($expr) |
875 | { | |
b82cce3f | 876 | $self = __FUNCTION__; |
15555995 DO |
877 | switch ($expr['type']) |
878 | { | |
e332b979 DO |
879 | case 'LEX_TRUE': |
880 | case 'LEX_FALSE': | |
15555995 | 881 | case 'LEX_PREDICATE': |
15555995 | 882 | case 'LEX_TAG': |
d42589e4 DO |
883 | return array(); |
884 | case 'LEX_AUTOTAG': | |
84986395 | 885 | switch (1) |
15555995 | 886 | { |
84986395 DO |
887 | case (preg_match ('/^\$id_/', $expr['load'])): |
888 | $recid = preg_replace ('/^\$id_/', '', $expr['load']); | |
adc18935 DO |
889 | try |
890 | { | |
891 | spotEntity ('object', $recid); | |
15555995 | 892 | return array(); |
adc18935 DO |
893 | } |
894 | catch (EntityNotFoundException $e) | |
895 | { | |
896 | return array (array | |
897 | ( | |
898 | 'header' => refRCLineno ($expr['lineno']), | |
899 | 'class' => 'warning', | |
900 | 'text' => "An object with ID '${recid}' does not exist." | |
901 | )); | |
902 | } | |
84986395 DO |
903 | case (preg_match ('/^\$ipv4netid_/', $expr['load'])): |
904 | $recid = preg_replace ('/^\$ipv4netid_/', '', $expr['load']); | |
adc18935 DO |
905 | try |
906 | { | |
907 | spotEntity ('ipv4net', $recid); | |
15555995 | 908 | return array(); |
adc18935 DO |
909 | } |
910 | catch (EntityNotFoundException $e) | |
911 | { | |
912 | return array (array | |
913 | ( | |
914 | 'header' => refRCLineno ($expr['lineno']), | |
915 | 'class' => 'warning', | |
916 | 'text' => "IPv4 network with ID '${recid}' does not exist." | |
917 | )); | |
918 | } | |
84986395 DO |
919 | case (preg_match ('/^\$userid_/', $expr['load'])): |
920 | $recid = preg_replace ('/^\$userid_/', '', $expr['load']); | |
adc18935 DO |
921 | try |
922 | { | |
923 | spotEntity ('user', $recid); | |
1a315491 | 924 | return array(); |
adc18935 DO |
925 | } |
926 | catch (EntityNotFoundException $e) | |
927 | { | |
928 | return array (array | |
929 | ( | |
930 | 'header' => refRCLineno ($expr['lineno']), | |
931 | 'class' => 'warning', | |
932 | 'text' => "User account with ID '${recid}' does not exist." | |
933 | )); | |
934 | } | |
84986395 DO |
935 | case (preg_match ('/^\$username_/', $expr['load'])): |
936 | $recid = preg_replace ('/^\$username_/', '', $expr['load']); | |
b82cce3f DO |
937 | global $require_local_account; |
938 | if (!$require_local_account) | |
939 | return array(); | |
940 | if (NULL !== getUserIDByUsername ($recid)) | |
1a315491 DO |
941 | return array(); |
942 | return array (array | |
943 | ( | |
061452b9 | 944 | 'header' => refRCLineno ($expr['lineno']), |
1a315491 | 945 | 'class' => 'warning', |
b82cce3f | 946 | 'text' => "Local user account '${recid}' does not exist." |
15555995 | 947 | )); |
743b8274 | 948 | // FIXME: pull identifier at the same pass, which does the matching |
b7b4b225 | 949 | case (preg_match ('/^\$page_[\p{L}0-9]+$/u', $expr['load'])): |
84986395 | 950 | $recid = preg_replace ('/^\$page_/', '', $expr['load']); |
743b8274 DO |
951 | global $page; |
952 | if (isset ($page[$recid])) | |
953 | return array(); | |
954 | return array (array | |
955 | ( | |
956 | 'header' => refRCLineno ($expr['lineno']), | |
957 | 'class' => 'warning', | |
958 | 'text' => "Page number '${recid}' does not exist." | |
959 | )); | |
b7b4b225 DO |
960 | case (preg_match ('/^\$(tab|op)_[\p{L}0-9]+$/u', $expr['load'])): |
961 | case (preg_match ('/^\$any_(op|rack|object|ip4net|net|ipv4vs|vs|ipv4rsp|rsp|file)$/', $expr['load'])): | |
84986395 DO |
962 | case (preg_match ('/^\$typeid_[[:digit:]]+$/', $expr['load'])): // FIXME: check value validity |
963 | case (preg_match ('/^\$cn_.+$/', $expr['load'])): // FIXME: check name validity and asset existence | |
964 | case (preg_match ('/^\$lgcn_.+$/', $expr['load'])): // FIXME: check name validity | |
b7b4b225 | 965 | case (preg_match ('/^\$(fromvlan|tovlan)_[[:digit:]]+$/', $expr['load'])): |
b48d8d61 | 966 | case (preg_match ('/^\$(unmounted|untagged|no_asset_tag|runs_8021Q)$/', $expr['load'])): |
84986395 | 967 | case (preg_match ('/^\$masklen_(eq|le|ge)_[[:digit:]][[:digit:]]?$/', $expr['load'])): |
1f54e1ba | 968 | case (preg_match ('/^\$attr_\d+_\d+$/', $expr['load'])): |
15555995 | 969 | return array(); |
dc97ad5e DO |
970 | default: |
971 | return array (array | |
972 | ( | |
973 | 'header' => refRCLineno ($expr['lineno']), | |
974 | 'class' => 'warning', | |
975 | 'text' => "Martian autotag '${expr['load']}'" | |
976 | )); | |
15555995 | 977 | } |
521a51a2 | 978 | case 'SYNT_NOT_EXPR': |
b82cce3f | 979 | return $self ($expr['load']); |
99148244 DO |
980 | case 'SYNT_AND_EXPR': |
981 | case 'SYNT_EXPR': | |
15555995 DO |
982 | return array_merge |
983 | ( | |
b82cce3f DO |
984 | $self ($expr['left']), |
985 | $self ($expr['right']) | |
15555995 DO |
986 | ); |
987 | default: | |
988 | return array (array | |
989 | ( | |
99148244 | 990 | 'header' => "internal error in ${self}", |
15555995 | 991 | 'class' => 'error', |
1a4096cb DO |
992 | 'text' => "Skipped expression of unknown type '${expr['type']}'" |
993 | )); | |
994 | } | |
995 | } | |
996 | ||
997 | // Idem WRT tags. | |
998 | function findTagWarnings ($expr) | |
999 | { | |
e332b979 | 1000 | $self = __FUNCTION__; |
1a4096cb DO |
1001 | switch ($expr['type']) |
1002 | { | |
e332b979 DO |
1003 | case 'LEX_TRUE': |
1004 | case 'LEX_FALSE': | |
1a4096cb | 1005 | case 'LEX_PREDICATE': |
d42589e4 | 1006 | case 'LEX_AUTOTAG': |
1a4096cb DO |
1007 | return array(); |
1008 | case 'LEX_TAG': | |
d94022fe DO |
1009 | if (getTagByName ($expr['load']) !== NULL) |
1010 | return array(); | |
1a4096cb DO |
1011 | return array (array |
1012 | ( | |
061452b9 | 1013 | 'header' => refRCLineno ($expr['lineno']), |
1a4096cb DO |
1014 | 'class' => 'warning', |
1015 | 'text' => "Tag '${expr['load']}' does not exist." | |
1016 | )); | |
521a51a2 | 1017 | case 'SYNT_NOT_EXPR': |
e332b979 | 1018 | return $self ($expr['load']); |
99148244 DO |
1019 | case 'SYNT_AND_EXPR': |
1020 | case 'SYNT_EXPR': | |
1a4096cb DO |
1021 | return array_merge |
1022 | ( | |
e332b979 DO |
1023 | $self ($expr['left']), |
1024 | $self ($expr['right']) | |
1a4096cb DO |
1025 | ); |
1026 | default: | |
1027 | return array (array | |
1028 | ( | |
99148244 | 1029 | 'header' => "internal error in ${self}", |
1a4096cb DO |
1030 | 'class' => 'error', |
1031 | 'text' => "Skipped expression of unknown type '${expr['type']}'" | |
15555995 DO |
1032 | )); |
1033 | } | |
1034 | } | |
1035 | ||
d94022fe DO |
1036 | // Check context modifiers, warn about those, which try referencing non-existent tags. |
1037 | function findCtxModWarnings ($modlist) | |
1038 | { | |
1039 | $ret = array(); | |
1040 | foreach ($modlist as $mod) | |
1041 | if (($mod['op'] == 'insert' or $mod['op'] == 'remove') and NULL === getTagByName ($mod['tag'])) | |
1042 | $ret[] = array | |
1043 | ( | |
1044 | 'header' => refRCLineno ($mod['lineno']), | |
1045 | 'class' => 'warning', | |
1046 | 'text' => "Tag '${mod['tag']}' does not exist." | |
1047 | ); | |
1048 | return $ret; | |
1049 | } | |
1050 | ||
52837ce6 DO |
1051 | // Return true, if the expression makes use of the predicate given. |
1052 | function referencedPredicate ($pname, $expr) | |
1053 | { | |
e332b979 | 1054 | $self = __FUNCTION__; |
52837ce6 DO |
1055 | switch ($expr['type']) |
1056 | { | |
e332b979 DO |
1057 | case 'LEX_TRUE': |
1058 | case 'LEX_FALSE': | |
52837ce6 | 1059 | case 'LEX_TAG': |
d42589e4 | 1060 | case 'LEX_AUTOTAG': |
52837ce6 DO |
1061 | return FALSE; |
1062 | case 'LEX_PREDICATE': | |
1063 | return $pname == $expr['load']; | |
521a51a2 | 1064 | case 'SYNT_NOT_EXPR': |
e332b979 | 1065 | return $self ($pname, $expr['load']); |
99148244 DO |
1066 | case 'SYNT_AND_EXPR': |
1067 | case 'SYNT_EXPR': | |
e332b979 | 1068 | return $self ($pname, $expr['left']) or $self ($pname, $expr['right']); |
52837ce6 DO |
1069 | default: // This is actually an internal error. |
1070 | return FALSE; | |
1071 | } | |
1072 | } | |
1073 | ||
d05ed211 DO |
1074 | // Return 'always true', 'always false' or any other verdict. |
1075 | function invariantExpression ($expr) | |
1076 | { | |
1077 | $self = __FUNCTION__; | |
1078 | switch ($expr['type']) | |
1079 | { | |
1080 | case 'SYNT_GRANT': | |
1081 | return $self ($expr['condition']); | |
1082 | case 'SYNT_DEFINITION': | |
1083 | return $self ($expr['definition']); | |
e332b979 DO |
1084 | case 'LEX_TRUE': |
1085 | return 'always true'; | |
1086 | case 'LEX_FALSE': | |
d05ed211 DO |
1087 | return 'always false'; |
1088 | case 'LEX_TAG': | |
d42589e4 | 1089 | case 'LEX_AUTOTAG': |
d05ed211 DO |
1090 | case 'LEX_PREDICATE': |
1091 | return 'sometimes something'; | |
521a51a2 | 1092 | case 'SYNT_NOT_EXPR': |
d05ed211 | 1093 | return $self ($expr['load']); |
99148244 | 1094 | case 'SYNT_AND_EXPR': |
d05ed211 DO |
1095 | $leftanswer = $self ($expr['left']); |
1096 | $rightanswer = $self ($expr['right']); | |
d05ed211 | 1097 | // "false and anything" is always false and thus const |
99148244 | 1098 | if ($leftanswer == 'always false' or $rightanswer == 'always false') |
d05ed211 DO |
1099 | return 'always false'; |
1100 | // "true and true" is true | |
99148244 DO |
1101 | if ($leftanswer == 'always true' and $rightanswer == 'always true') |
1102 | return 'always true'; | |
1103 | return ''; | |
1104 | case 'SYNT_EXPR': | |
1105 | $leftanswer = $self ($expr['left']); | |
1106 | $rightanswer = $self ($expr['right']); | |
1107 | // "true or anything" is always true and thus const | |
1108 | if ($leftanswer == 'always true' or $rightanswer == 'always true') | |
d05ed211 DO |
1109 | return 'always true'; |
1110 | // "false or false" is false | |
99148244 | 1111 | if ($leftanswer == 'always false' and $rightanswer == 'always false') |
d05ed211 DO |
1112 | return 'always false'; |
1113 | return ''; | |
1114 | default: // This is actually an internal error. | |
1115 | break; | |
1116 | } | |
1117 | } | |
1118 | ||
f63072f5 | 1119 | ?> |