r2794 - reset RackCode cache on upgrade to 0.17.1
[racktables] / inc / code.php
CommitLineData
f63072f5 1<?php
a1b88eca 2/*
a27a02c5 3 * This file implements lexical scanner and syntax analyzer for the RackCode
a1b88eca
DO
4 * access configuration language.
5 *
a1b88eca 6 */
f63072f5
DO
7
8// Complain about martian char.
05693a83 9function lexError1 ($state, $text, $pos, $ln = 'N/A')
f63072f5 10{
05693a83
DO
11 return array
12 (
13 'result' => 'NAK',
14 'load' => "Invalid character '" . mb_substr ($text, $pos, 1) . "' near line ${ln}"
15 );
f63072f5
DO
16}
17
18// Complain about martian keyword.
05693a83 19function lexError2 ($word, $ln = 'N/A')
f63072f5 20{
cf25e649
DO
21 return array
22 (
23 'result' => 'NAK',
05693a83 24 'load' => "Invalid keyword '${word}' near line ${ln}"
cf25e649 25 );
f63072f5
DO
26}
27
a1b88eca 28// Complain about wrong FSM state.
05693a83 29function lexError3 ($state, $ln = 'N/A')
a1b88eca 30{
cf25e649
DO
31 return array
32 (
33 'result' => 'NAK',
a0527aef 34 'load' => "Lexical error in scanner state '${state}' near line ${ln}"
cf25e649 35 );
a1b88eca
DO
36}
37
05693a83 38function lexError4 ($s, $ln = 'N/A')
9f54e6e9
DO
39{
40 return array
41 (
42 'result' => 'NAK',
05693a83 43 'load' => "Invalid name '${s}' near line ${ln}"
9f54e6e9
DO
44 );
45}
46
a0527aef
DO
47/* Produce a list of lexems from the given text. Possible lexems are:
48 *
49 * LEX_LBRACE
50 * LEX_RBRACE
51 * LEX_DECISION
52 * LEX_DEFINE
e332b979
DO
53 * LEX_TRUE
54 * LEX_FALSE
a0527aef
DO
55 * LEX_NOT
56 * LEX_TAG
57 * LEX_AUTOTAG
58 * LEX_PREDICATE
59 * LEX_BOOLOP
60 * LEX_CONTEXT
61 * LEX_CLEAR
62 * LEX_INSERT
63 * LEX_REMOVE
64 * LEX_ON
65 *
66 */
67function getLexemsFromRawText ($text)
f63072f5
DO
68{
69 $ret = array();
a0527aef
DO
70 // Add a mock character to aid in synchronization with otherwise correct,
71 // but short or odd-terminated final lines.
72 $text .= ' ';
bcd49780 73 $textlen = mb_strlen ($text);
05693a83 74 $lineno = 1;
f63072f5
DO
75 $state = "ESOTSM";
76 for ($i = 0; $i < $textlen; $i++) :
bcd49780 77 $char = mb_substr ($text, $i, 1);
f63072f5
DO
78 $newstate = $state;
79 switch ($state) :
80 case 'ESOTSM':
81 switch (TRUE)
82 {
f63072f5 83 case ($char == '('):
05693a83 84 $ret[] = array ('type' => 'LEX_LBRACE', 'lineno' => $lineno);
f63072f5
DO
85 break;
86 case ($char == ')'):
05693a83 87 $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno);
f63072f5
DO
88 break;
89 case ($char == '#'):
90 $newstate = 'skipping comment';
91 break;
bcd49780 92 case (mb_ereg ('[[:alpha:]]', $char) > 0):
cf25e649 93 $newstate = 'reading keyword';
f63072f5
DO
94 $buffer = $char;
95 break;
05693a83
DO
96 case ($char == "\n"):
97 $lineno++; // fall through
98 case ($char == " "):
99 case ($char == "\t"):
f63072f5
DO
100 // nom-nom...
101 break;
102 case ($char == '{'):
103 $newstate = 'reading tag 1';
104 break;
105 case ($char == '['):
106 $newstate = 'reading predicate 1';
107 break;
108 default:
05693a83 109 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
110 }
111 break;
cf25e649 112 case 'reading keyword':
f63072f5
DO
113 switch (TRUE)
114 {
bcd49780 115 case (mb_ereg ('[[:alpha:]]', $char) > 0):
f63072f5
DO
116 $buffer .= $char;
117 break;
05693a83
DO
118 case ($char == "\n"):
119 $lineno++; // fall through
120 case ($char == " "):
121 case ($char == "\t"):
f5e48a49 122 case ($char == ')'): // this will be handled below
f63072f5
DO
123 // got a word, sort it out
124 switch ($buffer)
125 {
126 case 'allow':
f63072f5 127 case 'deny':
05693a83 128 $ret[] = array ('type' => 'LEX_DECISION', 'load' => $buffer, 'lineno' => $lineno);
f63072f5
DO
129 break;
130 case 'define':
05693a83 131 $ret[] = array ('type' => 'LEX_DEFINE', 'lineno' => $lineno);
f63072f5
DO
132 break;
133 case 'and':
f63072f5 134 case 'or':
05693a83 135 $ret[] = array ('type' => 'LEX_BOOLOP', 'load' => $buffer, 'lineno' => $lineno);
a1b88eca
DO
136 break;
137 case 'not':
05693a83 138 $ret[] = array ('type' => 'LEX_NOT', 'lineno' => $lineno);
a1b88eca 139 break;
a1b88eca 140 case 'true':
e332b979
DO
141 $ret[] = array ('type' => 'LEX_TRUE', 'lineno' => $lineno);
142 break;
143 case 'false':
144 $ret[] = array ('type' => 'LEX_FALSE', 'lineno' => $lineno);
f63072f5 145 break;
578d9d02
DO
146 case 'context':
147 $ret[] = array ('type' => 'LEX_CONTEXT', 'lineno' => $lineno);
148 break;
149 case 'clear':
150 $ret[] = array ('type' => 'LEX_CLEAR', 'lineno' => $lineno);
151 break;
152 case 'insert':
153 $ret[] = array ('type' => 'LEX_INSERT', 'lineno' => $lineno);
154 break;
155 case 'remove':
156 $ret[] = array ('type' => 'LEX_REMOVE', 'lineno' => $lineno);
157 break;
158 case 'on':
159 $ret[] = array ('type' => 'LEX_ON', 'lineno' => $lineno);
160 break;
f63072f5 161 default:
05693a83 162 return lexError2 ($buffer, $lineno);
f63072f5 163 }
f5e48a49
DO
164 if ($char == ')')
165 $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno);
f63072f5
DO
166 $newstate = 'ESOTSM';
167 break;
168 default:
05693a83 169 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
170 }
171 break;
172 case 'reading tag 1':
173 switch (TRUE)
174 {
05693a83 175 case ($char == "\n"):
05693a83
DO
176 $lineno++; // fall through
177 case ($char == " "):
178 case ($char == "\t"):
f63072f5
DO
179 // nom-nom...
180 break;
9f54e6e9 181 case (mb_ereg ('[[:alnum:]\$]', $char) > 0):
f63072f5
DO
182 $buffer = $char;
183 $newstate = 'reading tag 2';
184 break;
185 default:
05693a83 186 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
187 }
188 break;
189 case 'reading tag 2':
190 switch (TRUE)
191 {
192 case ($char == '}'):
e5bd52e5 193 $buffer = rtrim ($buffer);
9f54e6e9 194 if (!validTagName ($buffer, TRUE))
05693a83 195 return lexError4 ($buffer, $lineno);
d42589e4 196 $ret[] = array ('type' => ($buffer[0] == '$' ? 'LEX_AUTOTAG' : 'LEX_TAG'), 'load' => $buffer, 'lineno' => $lineno);
f63072f5
DO
197 $newstate = 'ESOTSM';
198 break;
23818dde 199 case (mb_ereg ('[[:alnum:]\. _~-]', $char) > 0):
f63072f5
DO
200 $buffer .= $char;
201 break;
202 default:
05693a83 203 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
204 }
205 break;
206 case 'reading predicate 1':
207 switch (TRUE)
208 {
a872b565 209 case (preg_match ('/^[ \t\n]$/', $char)):
f63072f5
DO
210 // nom-nom...
211 break;
9f54e6e9 212 case (mb_ereg ('[[:alnum:]]', $char) > 0):
f63072f5
DO
213 $buffer = $char;
214 $newstate = 'reading predicate 2';
215 break;
216 default:
05693a83 217 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
218 }
219 break;
220 case 'reading predicate 2':
221 switch (TRUE)
222 {
223 case ($char == ']'):
e5bd52e5 224 $buffer = rtrim ($buffer);
9f54e6e9 225 if (!validTagName ($buffer))
05693a83
DO
226 return lexError4 ($buffer, $lineno);
227 $ret[] = array ('type' => 'LEX_PREDICATE', 'load' => $buffer, 'lineno' => $lineno);
f63072f5
DO
228 $newstate = 'ESOTSM';
229 break;
23818dde 230 case (mb_ereg ('[[:alnum:]\. _~-]', $char) > 0):
f63072f5
DO
231 $buffer .= $char;
232 break;
233 default:
05693a83 234 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
235 }
236 break;
237 case 'skipping comment':
bcd49780 238 switch ($char)
f63072f5
DO
239 {
240 case "\n":
05693a83 241 $lineno++;
f63072f5
DO
242 $newstate = 'ESOTSM';
243 default: // eat char, nom-nom...
244 break;
245 }
246 break;
247 default:
248 die (__FUNCTION__ . "(): internal error, state == ${state}");
249 endswitch;
250 $state = $newstate;
251 endfor;
e6a4adb9 252 if ($state != 'ESOTSM' and $state != 'skipping comment')
05693a83 253 return lexError3 ($state, $lineno);
cf25e649 254 return array ('result' => 'ACK', 'load' => $ret);
f63072f5
DO
255}
256
a0527aef
DO
257// Take a parse tree and figure out if it is a valid payload or not.
258// Depending on that return either NULL or an array filled with the load
259// of that expression.
260function spotPayload ($text, $reqtype = 'SYNT_CODETEXT')
261{
262 $lex = getLexemsFromRawText ($text);
263 if ($lex['result'] != 'ACK')
264 return $lex;
265 $stack = getParseTreeFromLexems ($lex['load']);
266 // The only possible way to "accept" is to have sole starting
267 // nonterminal on the stack (and it must be of the requested class).
268 if (count ($stack) == 1 and $stack[0]['type'] == $reqtype)
269 return array ('result' => 'ACK', 'load' => $stack[0]['load']);
270 // No luck. Prepare to complain.
271 if ($lineno = locateSyntaxError ($stack))
272 return array ('result' => 'NAK', 'load' => "Syntax error for type '${reqtype}' near line ${lineno}");
273 // HCF!
3918aaa3 274 return array ('result' => 'NAK', 'load' => "Syntax error for type '${reqtype}', line number unknown");
a0527aef
DO
275}
276
a1b88eca 277// Parse the given lexems stream into a list of RackCode sentences. Each such
a27a02c5
DO
278// sentence is a syntax tree, suitable for tag sequence evaluation. The final
279// parse tree may contain the following nodes:
280// LEX_TAG
d42589e4 281// LEX_AUTOTAG
a27a02c5 282// LEX_PREDICATE
e332b979
DO
283// LEX_TRUE
284// LEX_FALSE
05693a83 285// SYNT_NOTEXPR (1 argument, holding SYNT_EXPR)
e5bd52e5 286// SYNT_BOOLOP (2 arguments, each holding SYNT_EXPR)
e5bd52e5 287// SYNT_DEFINITION (2 arguments: term and definition)
1381b655 288// SYNT_GRANT (2 arguments: decision and condition)
578d9d02 289// SYNT_ADJUSTMENT (context modifier with action(s) and condition)
1381b655 290// SYNT_CODETEXT (sequence of sentences)
a27a02c5
DO
291//
292// After parsing the input successfully a list of SYNT_GRANT and SYNT_DEFINITION
293// trees is returned.
a0527aef
DO
294//
295// P.S. The above is true for input, which is a complete and correct RackCode text.
296// Other inputs may produce different combinations of lex/synt structures. Calling
297// function must check the parse tree itself.
298function getParseTreeFromLexems ($lexems)
a1b88eca 299{
a1b88eca
DO
300 $stack = array(); // subject to array_push() and array_pop()
301 $done = 0; // $lexems[$done] is the next item in the tape
302 $todo = count ($lexems);
303
1381b655
DO
304 // Perform shift-reduce processing. The "accept" actions occurs with an
305 // empty input tape and the stack holding only one symbol (the start
05693a83
DO
306 // symbol, SYNT_CODETEXT). When reducing, set the "line number" of
307 // the reduction result to the line number of the "latest" item of the
308 // reduction base (the one on the stack top). This will help locating
309 // parse errors, if any.
1381b655 310 while (TRUE)
a1b88eca 311 {
578d9d02 312 $stacktop = $stacksecondtop = $stackthirdtop = $stackfourthtop = array ('type' => 'null');
a1b88eca
DO
313 $stacksize = count ($stack);
314 if ($stacksize >= 1)
315 {
316 $stacktop = array_pop ($stack);
317 // It is possible to run into a S/R conflict, when having a syntaxically
318 // correct sentence base on the stack and some "and {something}" items
319 // on the input tape, hence let's detect this specific case and insist
320 // on "shift" action to make EXPR parsing hungry one.
1381b655
DO
321 if
322 (
323 $stacktop['type'] == 'SYNT_EXPR' and
324 ($done < $todo) and
325 $lexems[$done]['type'] == 'LEX_BOOLOP'
326 )
a1b88eca
DO
327 {
328 // shift!
329 array_push ($stack, $stacktop);
330 array_push ($stack, $lexems[$done++]);
331 continue;
332 }
333 if ($stacksize >= 2)
334 {
335 $stacksecondtop = array_pop ($stack);
336 if ($stacksize >= 3)
337 {
338 $stackthirdtop = array_pop ($stack);
578d9d02
DO
339 if ($stacksize >= 4)
340 {
341 $stackfourthtop = array_pop ($stack);
342 array_push ($stack, $stackfourthtop);
343 }
a1b88eca
DO
344 array_push ($stack, $stackthirdtop);
345 }
346 array_push ($stack, $stacksecondtop);
347 }
348 array_push ($stack, $stacktop);
1381b655
DO
349 // First detect definition start to save the predicate from being reduced into
350 // expression.
351 if
352 (
353 $stacktop['type'] == 'LEX_PREDICATE' and
354 $stacksecondtop['type'] == 'LEX_DEFINE'
355 )
356 {
05693a83 357 // reduce!
1381b655
DO
358 array_pop ($stack);
359 array_pop ($stack);
360 array_push
361 (
362 $stack,
363 array
364 (
365 'type' => 'SYNT_DEFINE',
05693a83 366 'lineno' => $stacktop['lineno'],
1381b655
DO
367 'load' => $stacktop['load']
368 )
369 );
370 continue;
371 }
578d9d02
DO
372 if
373 (
374 $stacktop['type'] == 'LEX_CLEAR'
375 )
376 {
377 // reduce!
378 array_pop ($stack);
379 array_push
380 (
381 $stack,
382 array
383 (
384 'type' => 'SYNT_CTXMOD',
385 'lineno' => $stacktop['lineno'],
386 'load' => array ('op' => 'clear')
387 )
388 );
389 continue;
390 }
391 if
392 (
393 $stacktop['type'] == 'LEX_TAG' and
394 $stacksecondtop['type'] == 'LEX_INSERT'
395 )
396 {
397 // reduce!
398 array_pop ($stack);
534d6c2a 399 array_pop ($stack);
578d9d02
DO
400 array_push
401 (
402 $stack,
403 array
404 (
405 'type' => 'SYNT_CTXMOD',
406 'lineno' => $stacktop['lineno'],
d94022fe 407 'load' => array ('op' => 'insert', 'tag' => $stacktop['load'], 'lineno' => $stacktop['lineno'])
578d9d02
DO
408 )
409 );
410 continue;
411 }
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 }
433 if
434 (
435 $stacktop['type'] == 'SYNT_CTXMOD' and
436 $stacksecondtop['type'] == 'SYNT_CTXMODLIST'
437 )
438 {
439 // reduce!
440 array_pop ($stack);
441 array_pop ($stack);
442 array_push
443 (
444 $stack,
445 array
446 (
447 'type' => 'SYNT_CTXMODLIST',
448 'lineno' => $stacktop['lineno'],
00f887f4 449 'load' => array_merge ($stacksecondtop['load'], array ($stacktop['load']))
578d9d02
DO
450 )
451 );
452 continue;
453 }
454 if
455 (
456 $stacktop['type'] == 'SYNT_CTXMOD'
457 )
458 {
459 // reduce!
460 array_pop ($stack);
461 array_push
462 (
463 $stack,
464 array
465 (
466 'type' => 'SYNT_CTXMODLIST',
467 'lineno' => $stacktop['lineno'],
8e80aaa9 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.
477 if
478 (
e332b979
DO
479 $stacktop['type'] == 'LEX_TAG' or // first look for tokens, which are most
480 $stacktop['type'] == 'LEX_AUTOTAG' or // likely to appear in the text
481 $stacktop['type'] == 'LEX_PREDICATE' or // supplied by user
482 $stacktop['type'] == 'LEX_TRUE' or
483 $stacktop['type'] == 'LEX_FALSE'
a1b88eca
DO
484 )
485 {
1381b655 486 // reduce!
a1b88eca
DO
487 array_pop ($stack);
488 array_push
489 (
490 $stack,
491 array
492 (
493 'type' => 'SYNT_EXPR',
05693a83 494 'lineno' => $stacktop['lineno'],
a1b88eca
DO
495 'load' => $stacktop
496 )
497 );
498 continue;
499 }
500 if
501 (
502 $stacktop['type'] == 'SYNT_EXPR' and
503 $stacksecondtop['type'] == 'LEX_NOT'
504 )
505 {
1381b655 506 // reduce!
a1b88eca
DO
507 array_pop ($stack);
508 array_pop ($stack);
509 array_push
510 (
511 $stack,
512 array
513 (
514 'type' => 'SYNT_EXPR',
05693a83 515 'lineno' => $stacktop['lineno'],
a1b88eca
DO
516 'load' => array
517 (
518 'type' => 'SYNT_NOTEXPR',
a27a02c5 519 'load' => $stacktop['load']
a1b88eca
DO
520 )
521 )
522 );
523 continue;
524 }
525 if
526 (
527 $stacktop['type'] == 'LEX_RBRACE' and
528 $stacksecondtop['type'] == 'SYNT_EXPR' and
529 $stackthirdtop['type'] == 'LEX_LBRACE'
530 )
531 {
1381b655 532 // reduce!
a1b88eca
DO
533 array_pop ($stack);
534 array_pop ($stack);
535 array_pop ($stack);
05693a83 536 $stacksecondtop['lineno'] = $stacktop['lineno'];
a1b88eca
DO
537 array_push
538 (
539 $stack,
540 $stacksecondtop
541 );
542 continue;
543 }
544 if
545 (
546 $stacktop['type'] == 'SYNT_EXPR' and
547 $stacksecondtop['type'] == 'LEX_BOOLOP' and
548 $stackthirdtop['type'] == 'SYNT_EXPR'
549 )
550 {
1381b655 551 // reduce!
a1b88eca
DO
552 array_pop ($stack);
553 array_pop ($stack);
554 array_pop ($stack);
555 array_push
556 (
557 $stack,
558 array
559 (
560 'type' => 'SYNT_EXPR',
05693a83 561 'lineno' => $stacktop['lineno'],
a1b88eca
DO
562 'load' => array
563 (
564 'type' => 'SYNT_BOOLOP',
565 'subtype' => $stacksecondtop['load'],
a27a02c5
DO
566 'left' => $stackthirdtop['load'],
567 'right' => $stacktop['load']
a1b88eca
DO
568 )
569 )
570 );
571 continue;
572 }
1381b655
DO
573 if
574 (
575 $stacktop['type'] == 'SYNT_EXPR' and
576 $stacksecondtop['type'] == 'LEX_DECISION'
577 )
578 {
579 // reduce!
580 array_pop ($stack);
581 array_pop ($stack);
582 array_push
583 (
584 $stack,
585 array
586 (
587 'type' => 'SYNT_GRANT',
05693a83 588 'lineno' => $stacktop['lineno'],
1381b655 589 'decision' => $stacksecondtop['load'],
a27a02c5 590 'condition' => $stacktop['load']
1381b655
DO
591 )
592 );
593 continue;
594 }
595 if
596 (
597 $stacktop['type'] == 'SYNT_EXPR' and
598 $stacksecondtop['type'] == 'SYNT_DEFINE'
599 )
600 {
601 // reduce!
602 array_pop ($stack);
603 array_pop ($stack);
604 array_push
605 (
606 $stack,
607 array
608 (
609 'type' => 'SYNT_DEFINITION',
05693a83 610 'lineno' => $stacktop['lineno'],
a27a02c5 611 'term' => $stacksecondtop['load'],
bcd37231 612 'definition' => $stacktop['load']
1381b655
DO
613 )
614 );
615 continue;
616 }
617 if
618 (
578d9d02
DO
619 $stacktop['type'] == 'SYNT_EXPR' and
620 $stacksecondtop['type'] == 'LEX_ON' and
621 $stackthirdtop['type'] == 'SYNT_CTXMODLIST' and
622 $stackfourthtop['type'] == 'LEX_CONTEXT'
623 )
624 {
625 // reduce!
626 array_pop ($stack);
627 array_pop ($stack);
628 array_pop ($stack);
629 array_pop ($stack);
630 array_push
631 (
632 $stack,
633 array
634 (
635 'type' => 'SYNT_ADJUSTMENT',
636 'lineno' => $stacktop['lineno'],
637 'modlist' => $stackthirdtop['load'],
638 'condition' => $stacktop['load']
639 )
640 );
641 continue;
642 }
643 if
644 (
645 ($stacktop['type'] == 'SYNT_GRANT' or $stacktop['type'] == 'SYNT_DEFINITION' or $stacktop['type'] == 'SYNT_ADJUSTMENT') and
1381b655
DO
646 $stacksecondtop['type'] == 'SYNT_CODETEXT'
647 )
648 {
649 // reduce!
650 array_pop ($stack);
651 array_pop ($stack);
652 $stacksecondtop['load'][] = $stacktop;
05693a83 653 $stacksecondtop['lineno'] = $stacktop['lineno'];
1381b655
DO
654 array_push
655 (
656 $stack,
657 $stacksecondtop
658 );
659 continue;
660 }
661 if
662 (
a27a02c5 663 $stacktop['type'] == 'SYNT_GRANT' or
578d9d02
DO
664 $stacktop['type'] == 'SYNT_DEFINITION' or
665 $stacktop['type'] == 'SYNT_ADJUSTMENT'
1381b655
DO
666 )
667 {
668 // reduce!
669 array_pop ($stack);
1381b655
DO
670 array_push
671 (
672 $stack,
673 array
674 (
675 'type' => 'SYNT_CODETEXT',
05693a83 676 'lineno' => $stacktop['lineno'],
1381b655
DO
677 'load' => array ($stacktop)
678 )
679 );
680 continue;
681 }
682 }
683 // The fact we execute here means, that no reduction or early shift
684 // has been done. The only way to enter another iteration is to "shift"
a0527aef
DO
685 // more, if possible. If shifting isn't possible due to empty input tape,
686 // we are facing the final "accept"/"reject" dilemma. In this case our
687 // work is done here, so return the whole stack to the calling function
688 // to decide depending on what it is expecting.
1381b655
DO
689 if ($done < $todo)
690 {
691 array_push ($stack, $lexems[$done++]);
692 continue;
a1b88eca 693 }
1381b655 694 // The moment of truth.
a0527aef 695 return $stack;
a1b88eca 696 }
a1b88eca
DO
697}
698
a0527aef 699function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
a27a02c5 700{
e332b979 701 $self = __FUNCTION__;
bcd37231 702 switch ($expr['type'])
a27a02c5 703 {
d42589e4
DO
704 // Return true, if given tag is present on the tag chain.
705 case 'LEX_TAG':
706 case 'LEX_AUTOTAG':
bcd37231
DO
707 foreach ($tagchain as $tagInfo)
708 if ($expr['load'] == $tagInfo['tag'])
709 return TRUE;
a27a02c5 710 return FALSE;
bcd37231
DO
711 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
712 $pname = $expr['load'];
713 if (!isset ($ptable[$pname]))
714 {
a0527aef
DO
715 if (!$silent)
716 showError ("Predicate '${pname}' is referenced before declaration", __FUNCTION__);
717 return NULL;
bcd37231 718 }
e332b979
DO
719 return $self ($ptable[$pname], $tagchain, $ptable);
720 case 'LEX_TRUE':
721 return TRUE;
722 case 'LEX_FALSE':
723 return FALSE;
bcd37231 724 case 'SYNT_NOTEXPR':
e332b979 725 $tmp = $self ($expr['load'], $tagchain, $ptable);
a0527aef
DO
726 if ($tmp === TRUE)
727 return FALSE;
728 elseif ($tmp === FALSE)
729 return TRUE;
730 else
731 return $tmp;
bcd37231 732 case 'SYNT_BOOLOP':
e332b979 733 $leftresult = $self ($expr['left'], $tagchain, $ptable);
bcd37231
DO
734 switch ($expr['subtype'])
735 {
736 case 'or':
737 if ($leftresult)
738 return TRUE; // early success
e332b979 739 return $self ($expr['right'], $tagchain, $ptable);
bcd37231
DO
740 case 'and':
741 if (!$leftresult)
742 return FALSE; // early failure
e332b979 743 return $self ($expr['right'], $tagchain, $ptable);
bcd37231 744 default:
a0527aef
DO
745 if (!$silent)
746 showError ("Cannot evaluate unknown boolean operation '${boolop['subtype']}'");
747 return NULL;
bcd37231 748 }
a27a02c5 749 default:
a0527aef
DO
750 if (!$silent)
751 showError ("Evaluation error, cannot process expression type '${expr['type']}'", __FUNCTION__);
752 return NULL;
a27a02c5
DO
753 break;
754 }
755}
756
8e80aaa9
DO
757// Process a context adjustment request, update given chain accordingly,
758// return TRUE on any changes done.
759// The request is a sequence of clear/insert/remove requests exactly as cooked
760// for each SYNT_CTXMODLIST node.
00f887f4 761function processAdjustmentSentence ($modlist, &$chain)
a27a02c5 762{
8e80aaa9
DO
763 global $rackCode;
764 $didChanges = FALSE;
00f887f4
DO
765 foreach ($modlist as $mod)
766 switch ($mod['op'])
767 {
768 case 'insert':
8e80aaa9
DO
769 foreach ($chain as $etag)
770 if ($etag['tag'] == $mod['tag']) // already there, next request
771 break 2;
7ddb2c05
DO
772 $search = getTagByName ($mod['tag']);
773 if ($search === NULL) // skip martians silently
774 break;
775 $chain[] = $search;
8e80aaa9 776 $didChanges = TRUE;
00f887f4
DO
777 break;
778 case 'remove':
8e80aaa9
DO
779 foreach ($chain as $key => $etag)
780 if ($etag['tag'] == $mod['tag']) // drop first match and return
781 {
782 unset ($chain[$key]);
783 $didChanges = TRUE;
784 break 2;
785 }
00f887f4
DO
786 break;
787 case 'clear':
8e80aaa9
DO
788 $chain = array();
789 $didChanges = TRUE;
00f887f4
DO
790 break;
791 default: // HCF
792 showError ('Internal error', __FUNCTION__);
793 die;
794 }
8e80aaa9 795 return $didChanges;
00f887f4
DO
796}
797
798// The argument doesn't include explicit and implicit tags. This allows us to derive implicit chain
799// each time we modify the given argument (and work with the modified copy from now on).
800// After the work is done the global $impl_tags is silently modified
801function gotClearanceForTagChain ($const_base)
802{
803 global $rackCode, $expl_tags, $impl_tags;
a27a02c5 804 $ptable = array();
bcd37231 805 foreach ($rackCode as $sentence)
a27a02c5
DO
806 {
807 switch ($sentence['type'])
808 {
809 case 'SYNT_DEFINITION':
810 $ptable[$sentence['term']] = $sentence['definition'];
811 break;
812 case 'SYNT_GRANT':
00f887f4 813 if (eval_expression ($sentence['condition'], array_merge ($const_base, $expl_tags, $impl_tags), $ptable))
a27a02c5
DO
814 switch ($sentence['decision'])
815 {
816 case 'allow':
817 return TRUE;
818 case 'deny':
819 return FALSE;
820 default:
85a8ec3f 821 showError ("Condition match for unknown grant decision '${sentence['decision']}'", __FUNCTION__);
a27a02c5
DO
822 break;
823 }
bcd37231 824 break;
578d9d02 825 case 'SYNT_ADJUSTMENT':
00f887f4
DO
826 if
827 (
828 eval_expression ($sentence['condition'], array_merge ($const_base, $expl_tags, $impl_tags), $ptable) and
829 processAdjustmentSentence ($sentence['modlist'], $expl_tags)
830 ) // recalculate implicit chain only after actual change, not just on matched condition
831 $impl_tags = getImplicitTags ($expl_tags); // recalculate
578d9d02 832 break;
a27a02c5 833 default:
85a8ec3f 834 showError ("Can't process sentence of unknown type '${sentence['type']}'", __FUNCTION__);
a27a02c5
DO
835 break;
836 }
837 }
838 return FALSE;
839}
840
a0527aef
DO
841// Top-level wrapper for most of the code in this file. Get a text, return a parse tree
842// (or error message).
cf25e649 843function getRackCode ($text)
bcd37231 844{
cf25e649
DO
845 if (!mb_strlen ($text))
846 return array ('result' => 'NAK', 'load' => 'The RackCode text was found empty in ' . __FUNCTION__);
847 $text = str_replace ("\r", '', $text) . "\n";
3918aaa3 848 $synt = spotPayload ($text, 'SYNT_CODETEXT');
cf25e649
DO
849 if ($synt['result'] != 'ACK')
850 return $synt;
851 // An empty sentence list is semantically valid, yet senseless,
852 // so checking intermediate result once more won't hurt.
853 if (!count ($synt['load']))
854 return array ('result' => 'NAK', 'load' => 'Empty parse tree found in ' . __FUNCTION__);
855 return semanticFilter ($synt['load']);
bcd37231
DO
856}
857
3082e137
DO
858// Return NULL, if the given expression can be evaluated against the given
859// predicate list. Return the name of the first show stopper otherwise.
860function firstUnrefPredicate ($plist, $expr)
e6a4adb9 861{
e332b979 862 $self = __FUNCTION__;
e6a4adb9
DO
863 switch ($expr['type'])
864 {
e332b979
DO
865 case 'LEX_TRUE':
866 case 'LEX_FALSE':
e6a4adb9 867 case 'LEX_TAG':
d42589e4 868 case 'LEX_AUTOTAG':
3082e137 869 return NULL;
e6a4adb9 870 case 'LEX_PREDICATE':
3082e137 871 return in_array ($expr['load'], $plist) ? NULL : $expr['load'];
e6a4adb9 872 case 'SYNT_NOTEXPR':
e332b979 873 return $self ($plist, $expr['load']);
e6a4adb9 874 case 'SYNT_BOOLOP':
e332b979 875 if (($tmp = $self ($plist, $expr['left'])) !== NULL)
3082e137 876 return $tmp;
e332b979 877 if (($tmp = $self ($plist, $expr['right'])) !== NULL)
3082e137
DO
878 return $tmp;
879 return NULL;
e6a4adb9 880 default:
3082e137 881 return NULL;
e6a4adb9
DO
882 }
883}
884
cf25e649 885function semanticFilter ($code)
e6a4adb9
DO
886{
887 $predicatelist = array();
888 foreach ($code as $sentence)
889 switch ($sentence['type'])
890 {
891 case 'SYNT_DEFINITION':
f1aa773e
DO
892 // A predicate can only be defined once.
893 if (in_array ($sentence['term'], $predicatelist))
894 return array
895 (
896 'result' => 'NAK',
897 'load' => "[${sentence['term']}] has already been defined earlier"
898 );
899 // Check below makes sure, that definitions are built from already existing
900 // tokens. This also makes recursive definitions impossible.
3082e137
DO
901 $up = firstUnrefPredicate ($predicatelist, $sentence['definition']);
902 if ($up !== NULL)
903 return array
904 (
905 'result' => 'NAK',
f1aa773e 906 'load' => "definition of [${sentence['term']}] refers to [${up}], which is not (yet) defined"
3082e137 907 );
e6a4adb9
DO
908 $predicatelist[] = $sentence['term'];
909 break;
910 case 'SYNT_GRANT':
3082e137
DO
911 $up = firstUnrefPredicate ($predicatelist, $sentence['condition']);
912 if ($up !== NULL)
913 return array
914 (
915 'result' => 'NAK',
916 'load' => "grant sentence uses unknown predicate [${up}]"
917 );
e6a4adb9 918 break;
578d9d02
DO
919 case 'SYNT_ADJUSTMENT':
920 // Only condition part gets tested, because it's normal to set (or even to unset)
921 // something, that's not set.
922 $up = firstUnrefPredicate ($predicatelist, $sentence['condition']);
923 if ($up !== NULL)
924 return array
925 (
926 'result' => 'NAK',
927 'load' => "adjustment sentence uses unknown predicate [${up}]"
928 );
929 break;
e6a4adb9 930 default:
cf25e649 931 return array ('result' => 'NAK', 'load' => 'unknown sentence type');
e6a4adb9 932 }
cf25e649 933 return array ('result' => 'ACK', 'load' => $code);
e6a4adb9
DO
934}
935
05693a83
DO
936// Accept a stack and figure out the cause of it not being parsed into a tree.
937// Return the line number or zero.
938function locateSyntaxError ($stack)
939{
940 // The first SYNT_CODETEXT node, if is present, holds stuff already
941 // successfully processed. Its line counter shows, where the last reduction
942 // took place (it _might_ be the same line, which causes the syntax error).
943 // The next node (it's very likely to exist) should have its line counter
944 // pointing to the place, where the first (of 1 or more) error is located.
945 if (isset ($stack[0]['type']) and $stack[0]['type'] == 'SYNT_CODETEXT')
946 unset ($stack[0]);
947 foreach ($stack as $node)
948 // Satisfy with the first line number met.
949 if (isset ($node['lineno']))
950 return $node['lineno'];
951 return 0;
952}
953
061452b9
DO
954function refRCLineno ($ln)
955{
956 global $root;
957 return "<a href='${root}?page=perms&tab=default#line${ln}'>line ${ln}</a>";
958}
959
15555995 960function getRackCodeWarnings ()
88d513cd 961{
15555995
DO
962 $ret = array();
963 global $rackCode;
1a4096cb
DO
964 // tags
965 foreach ($rackCode as $sentence)
966 switch ($sentence['type'])
967 {
968 case 'SYNT_DEFINITION':
969 $ret = array_merge ($ret, findTagWarnings ($sentence['definition']));
970 break;
578d9d02 971 case 'SYNT_ADJUSTMENT':
d94022fe
DO
972 $ret = array_merge ($ret, findTagWarnings ($sentence['condition']));
973 $ret = array_merge ($ret, findCtxModWarnings ($sentence['modlist']));
974 break;
975 case 'SYNT_GRANT':
1a4096cb
DO
976 $ret = array_merge ($ret, findTagWarnings ($sentence['condition']));
977 break;
978 default:
979 $ret[] = array
980 (
981 'header' => 'internal error',
982 'class' => 'error',
983 'text' => "Skipped sentence of unknown type '${sentence['type']}'"
984 );
985 }
15555995
DO
986 // autotags
987 foreach ($rackCode as $sentence)
988 switch ($sentence['type'])
989 {
990 case 'SYNT_DEFINITION':
991 $ret = array_merge ($ret, findAutoTagWarnings ($sentence['definition']));
992 break;
993 case 'SYNT_GRANT':
578d9d02 994 case 'SYNT_ADJUSTMENT':
15555995
DO
995 $ret = array_merge ($ret, findAutoTagWarnings ($sentence['condition']));
996 break;
997 default:
998 $ret[] = array
999 (
1000 'header' => 'internal error',
1001 'class' => 'error',
1002 'text' => "Skipped sentence of unknown type '${sentence['type']}'"
1003 );
1004 }
52837ce6
DO
1005 // predicates
1006 $plist = array();
1007 foreach ($rackCode as $sentence)
1008 if ($sentence['type'] == 'SYNT_DEFINITION')
1009 $plist[$sentence['term']] = $sentence['lineno'];
1010 foreach ($plist as $pname => $lineno)
1011 {
1012 foreach ($rackCode as $sentence)
1013 switch ($sentence['type'])
1014 {
1015 case 'SYNT_DEFINITION':
1016 if (referencedPredicate ($pname, $sentence['definition']))
1017 continue 3; // clear, next term
1018 break;
1019 case 'SYNT_GRANT':
578d9d02 1020 case 'SYNT_ADJUSTMENT':
52837ce6
DO
1021 if (referencedPredicate ($pname, $sentence['condition']))
1022 continue 3; // idem
1023 break;
1024 }
1025 $ret[] = array
1026 (
061452b9 1027 'header' => refRCLineno ($lineno),
52837ce6
DO
1028 'class' => 'warning',
1029 'text' => "Predicate '${pname}' is defined, but never used."
1030 );
1031 }
d05ed211
DO
1032 // expressions
1033 foreach ($rackCode as $sentence)
1034 switch (invariantExpression ($sentence))
1035 {
1036 case 'always true':
1037 $ret[] = array
1038 (
061452b9 1039 'header' => refRCLineno ($sentence['lineno']),
d05ed211
DO
1040 'class' => 'warning',
1041 'text' => "Expression is always true."
1042 );
1043 break;
1044 case 'always false':
1045 $ret[] = array
1046 (
061452b9 1047 'header' => refRCLineno ($sentence['lineno']),
d05ed211
DO
1048 'class' => 'warning',
1049 'text' => "Expression is always false."
1050 );
1051 break;
1052 default:
1053 break;
1054 }
52837ce6 1055 // bail out
15555995
DO
1056 $nwarnings = count ($ret);
1057 $ret[] = array
1058 (
1059 'header' => 'summary',
1060 'class' => $nwarnings ? 'error' : 'success',
1061 'text' => "Analysis complete, ${nwarnings} issues discovered."
1062 );
88d513cd
DO
1063 return $ret;
1064}
1065
52837ce6 1066// Scan the given expression and return any issues found about its autotags.
15555995
DO
1067function findAutoTagWarnings ($expr)
1068{
b82cce3f 1069 $self = __FUNCTION__;
15555995
DO
1070 switch ($expr['type'])
1071 {
e332b979
DO
1072 case 'LEX_TRUE':
1073 case 'LEX_FALSE':
15555995 1074 case 'LEX_PREDICATE':
15555995 1075 case 'LEX_TAG':
d42589e4
DO
1076 return array();
1077 case 'LEX_AUTOTAG':
15555995
DO
1078 switch (TRUE)
1079 {
1080 case (mb_ereg_match ('^\$id_', $expr['load'])):
1081 $recid = mb_ereg_replace ('^\$id_', '', $expr['load']);
a6e91ac2 1082 if (NULL !== spotEntity ('object', $recid))
15555995
DO
1083 return array();
1084 return array (array
1085 (
061452b9 1086 'header' => refRCLineno ($expr['lineno']),
15555995 1087 'class' => 'warning',
52837ce6 1088 'text' => "An object with ID '${recid}' does not exist."
15555995
DO
1089 ));
1090 case (mb_ereg_match ('^\$ipv4netid_', $expr['load'])):
1091 $recid = mb_ereg_replace ('^\$ipv4netid_', '', $expr['load']);
a6e91ac2 1092 if (NULL != spotEntity ('ipv4net', $recid))
15555995
DO
1093 return array();
1094 return array (array
1095 (
061452b9 1096 'header' => refRCLineno ($expr['lineno']),
15555995 1097 'class' => 'warning',
1a315491
DO
1098 'text' => "IPv4 network with ID '${recid}' does not exist."
1099 ));
1100 case (mb_ereg_match ('^\$userid_', $expr['load'])):
1101 $recid = mb_ereg_replace ('^\$userid_', '', $expr['load']);
a6e91ac2 1102 if (NULL !== spotEntity ('user', $recid))
1a315491
DO
1103 return array();
1104 return array (array
1105 (
061452b9 1106 'header' => refRCLineno ($expr['lineno']),
1a315491
DO
1107 'class' => 'warning',
1108 'text' => "User account with ID '${recid}' does not exist."
1109 ));
1110 case (mb_ereg_match ('^\$username_', $expr['load'])):
1a315491 1111 $recid = mb_ereg_replace ('^\$username_', '', $expr['load']);
b82cce3f
DO
1112 global $require_local_account;
1113 if (!$require_local_account)
1114 return array();
1115 if (NULL !== getUserIDByUsername ($recid))
1a315491
DO
1116 return array();
1117 return array (array
1118 (
061452b9 1119 'header' => refRCLineno ($expr['lineno']),
1a315491 1120 'class' => 'warning',
b82cce3f 1121 'text' => "Local user account '${recid}' does not exist."
15555995 1122 ));
743b8274 1123 // FIXME: pull identifier at the same pass, which does the matching
17112e81 1124 case (mb_ereg_match ('^\$page_[[:alnum:]]+$', $expr['load'])):
743b8274
DO
1125 $recid = mb_ereg_replace ('^\$page_', '', $expr['load']);
1126 global $page;
1127 if (isset ($page[$recid]))
1128 return array();
1129 return array (array
1130 (
1131 'header' => refRCLineno ($expr['lineno']),
1132 'class' => 'warning',
1133 'text' => "Page number '${recid}' does not exist."
1134 ));
17112e81
DO
1135 case (mb_ereg_match ('^\$tab_[[:alnum:]]+$', $expr['load'])):
1136 case (mb_ereg_match ('^\$op_[[:alnum:]]+$', $expr['load'])):
dc97ad5e
DO
1137 case (mb_ereg_match ('^\$any_op$', $expr['load'])):
1138 case (mb_ereg_match ('^\$any_rack$', $expr['load'])):
1139 case (mb_ereg_match ('^\$any_object$', $expr['load'])):
1140 case (mb_ereg_match ('^\$any_ip4net$', $expr['load'])):
1141 case (mb_ereg_match ('^\$any_net$', $expr['load'])):
1142 case (mb_ereg_match ('^\$any_ipv4vs$', $expr['load'])):
1143 case (mb_ereg_match ('^\$any_vs$', $expr['load'])):
1144 case (mb_ereg_match ('^\$any_ipv4rsp$', $expr['load'])):
1145 case (mb_ereg_match ('^\$any_rsp$', $expr['load'])):
1146 case (mb_ereg_match ('^\$any_file$', $expr['load'])):
1147 case (mb_ereg_match ('^\$typeid_[[:digit:]]+$', $expr['load'])): // FIXME: check value validity
1148 case (mb_ereg_match ('^\$cn_.+$', $expr['load'])): // FIXME: check name validity and asset existence
1149 case (mb_ereg_match ('^\$lgcn_.+$', $expr['load'])): // FIXME: check name validity
17112e81
DO
1150 case (mb_ereg_match ('^\$fromvlan_[[:digit:]]+$', $expr['load'])):
1151 case (mb_ereg_match ('^\$tovlan_[[:digit:]]+$', $expr['load'])):
3d670bba 1152 case (mb_ereg_match ('^\$unmounted$', $expr['load'])):
15555995 1153 return array();
dc97ad5e
DO
1154 default:
1155 return array (array
1156 (
1157 'header' => refRCLineno ($expr['lineno']),
1158 'class' => 'warning',
1159 'text' => "Martian autotag '${expr['load']}'"
1160 ));
15555995
DO
1161 }
1162 case 'SYNT_NOTEXPR':
b82cce3f 1163 return $self ($expr['load']);
15555995
DO
1164 case 'SYNT_BOOLOP':
1165 return array_merge
1166 (
b82cce3f
DO
1167 $self ($expr['left']),
1168 $self ($expr['right'])
15555995
DO
1169 );
1170 default:
1171 return array (array
1172 (
1173 'header' => 'internal error',
1174 'class' => 'error',
1a4096cb
DO
1175 'text' => "Skipped expression of unknown type '${expr['type']}'"
1176 ));
1177 }
1178}
1179
1180// Idem WRT tags.
1181function findTagWarnings ($expr)
1182{
e332b979 1183 $self = __FUNCTION__;
1a4096cb
DO
1184 switch ($expr['type'])
1185 {
e332b979
DO
1186 case 'LEX_TRUE':
1187 case 'LEX_FALSE':
1a4096cb 1188 case 'LEX_PREDICATE':
d42589e4 1189 case 'LEX_AUTOTAG':
1a4096cb
DO
1190 return array();
1191 case 'LEX_TAG':
d94022fe
DO
1192 if (getTagByName ($expr['load']) !== NULL)
1193 return array();
1a4096cb
DO
1194 return array (array
1195 (
061452b9 1196 'header' => refRCLineno ($expr['lineno']),
1a4096cb
DO
1197 'class' => 'warning',
1198 'text' => "Tag '${expr['load']}' does not exist."
1199 ));
1200 case 'SYNT_NOTEXPR':
e332b979 1201 return $self ($expr['load']);
1a4096cb
DO
1202 case 'SYNT_BOOLOP':
1203 return array_merge
1204 (
e332b979
DO
1205 $self ($expr['left']),
1206 $self ($expr['right'])
1a4096cb
DO
1207 );
1208 default:
1209 return array (array
1210 (
1211 'header' => 'internal error',
1212 'class' => 'error',
1213 'text' => "Skipped expression of unknown type '${expr['type']}'"
15555995
DO
1214 ));
1215 }
1216}
1217
d94022fe
DO
1218// Check context modifiers, warn about those, which try referencing non-existent tags.
1219function findCtxModWarnings ($modlist)
1220{
1221 $ret = array();
1222 foreach ($modlist as $mod)
1223 if (($mod['op'] == 'insert' or $mod['op'] == 'remove') and NULL === getTagByName ($mod['tag']))
1224 $ret[] = array
1225 (
1226 'header' => refRCLineno ($mod['lineno']),
1227 'class' => 'warning',
1228 'text' => "Tag '${mod['tag']}' does not exist."
1229 );
1230 return $ret;
1231}
1232
52837ce6
DO
1233// Return true, if the expression makes use of the predicate given.
1234function referencedPredicate ($pname, $expr)
1235{
e332b979 1236 $self = __FUNCTION__;
52837ce6
DO
1237 switch ($expr['type'])
1238 {
e332b979
DO
1239 case 'LEX_TRUE':
1240 case 'LEX_FALSE':
52837ce6 1241 case 'LEX_TAG':
d42589e4 1242 case 'LEX_AUTOTAG':
52837ce6
DO
1243 return FALSE;
1244 case 'LEX_PREDICATE':
1245 return $pname == $expr['load'];
1246 case 'SYNT_NOTEXPR':
e332b979 1247 return $self ($pname, $expr['load']);
52837ce6 1248 case 'SYNT_BOOLOP':
e332b979 1249 return $self ($pname, $expr['left']) or $self ($pname, $expr['right']);
52837ce6
DO
1250 default: // This is actually an internal error.
1251 return FALSE;
1252 }
1253}
1254
d05ed211
DO
1255// Return 'always true', 'always false' or any other verdict.
1256function invariantExpression ($expr)
1257{
1258 $self = __FUNCTION__;
1259 switch ($expr['type'])
1260 {
1261 case 'SYNT_GRANT':
1262 return $self ($expr['condition']);
1263 case 'SYNT_DEFINITION':
1264 return $self ($expr['definition']);
e332b979
DO
1265 case 'LEX_TRUE':
1266 return 'always true';
1267 case 'LEX_FALSE':
d05ed211
DO
1268 return 'always false';
1269 case 'LEX_TAG':
d42589e4 1270 case 'LEX_AUTOTAG':
d05ed211
DO
1271 case 'LEX_PREDICATE':
1272 return 'sometimes something';
1273 case 'SYNT_NOTEXPR':
1274 return $self ($expr['load']);
1275 case 'SYNT_BOOLOP':
1276 $leftanswer = $self ($expr['left']);
1277 $rightanswer = $self ($expr['right']);
1278 // "true or anything" is always true and thus const
1279 if ($expr['subtype'] == 'or' and ($leftanswer == 'always true' or $rightanswer == 'always true'))
1280 return 'always true';
1281 // "false and anything" is always false and thus const
1282 if ($expr['subtype'] == 'and' and ($leftanswer == 'always false' or $rightanswer == 'always false'))
1283 return 'always false';
1284 // "true and true" is true
1285 if ($expr['subtype'] == 'and' and ($leftanswer == 'always true' and $rightanswer == 'always true'))
1286 return 'always true';
1287 // "false or false" is false
1288 if ($expr['subtype'] == 'or' and ($leftanswer == 'always false' and $rightanswer == 'always false'))
1289 return 'always false';
1290 return '';
1291 default: // This is actually an internal error.
1292 break;
1293 }
1294}
1295
f63072f5 1296?>