r2789 - don't rely on interface.php to show errors
[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
53 * LEX_BOOLCONST
54 * LEX_NOT
55 * LEX_TAG
56 * LEX_AUTOTAG
57 * LEX_PREDICATE
58 * LEX_BOOLOP
59 * LEX_CONTEXT
60 * LEX_CLEAR
61 * LEX_INSERT
62 * LEX_REMOVE
63 * LEX_ON
64 *
65 */
66function getLexemsFromRawText ($text)
f63072f5
DO
67{
68 $ret = array();
a0527aef
DO
69 // Add a mock character to aid in synchronization with otherwise correct,
70 // but short or odd-terminated final lines.
71 $text .= ' ';
bcd49780 72 $textlen = mb_strlen ($text);
05693a83 73 $lineno = 1;
f63072f5
DO
74 $state = "ESOTSM";
75 for ($i = 0; $i < $textlen; $i++) :
bcd49780 76 $char = mb_substr ($text, $i, 1);
f63072f5
DO
77 $newstate = $state;
78 switch ($state) :
79 case 'ESOTSM':
80 switch (TRUE)
81 {
f63072f5 82 case ($char == '('):
05693a83 83 $ret[] = array ('type' => 'LEX_LBRACE', 'lineno' => $lineno);
f63072f5
DO
84 break;
85 case ($char == ')'):
05693a83 86 $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno);
f63072f5
DO
87 break;
88 case ($char == '#'):
89 $newstate = 'skipping comment';
90 break;
bcd49780 91 case (mb_ereg ('[[:alpha:]]', $char) > 0):
cf25e649 92 $newstate = 'reading keyword';
f63072f5
DO
93 $buffer = $char;
94 break;
05693a83
DO
95 case ($char == "\n"):
96 $lineno++; // fall through
97 case ($char == " "):
98 case ($char == "\t"):
f63072f5
DO
99 // nom-nom...
100 break;
101 case ($char == '{'):
102 $newstate = 'reading tag 1';
103 break;
104 case ($char == '['):
105 $newstate = 'reading predicate 1';
106 break;
107 default:
05693a83 108 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
109 }
110 break;
cf25e649 111 case 'reading keyword':
f63072f5
DO
112 switch (TRUE)
113 {
bcd49780 114 case (mb_ereg ('[[:alpha:]]', $char) > 0):
f63072f5
DO
115 $buffer .= $char;
116 break;
05693a83
DO
117 case ($char == "\n"):
118 $lineno++; // fall through
119 case ($char == " "):
120 case ($char == "\t"):
f5e48a49 121 case ($char == ')'): // this will be handled below
f63072f5
DO
122 // got a word, sort it out
123 switch ($buffer)
124 {
125 case 'allow':
f63072f5 126 case 'deny':
05693a83 127 $ret[] = array ('type' => 'LEX_DECISION', 'load' => $buffer, 'lineno' => $lineno);
f63072f5
DO
128 break;
129 case 'define':
05693a83 130 $ret[] = array ('type' => 'LEX_DEFINE', 'lineno' => $lineno);
f63072f5
DO
131 break;
132 case 'and':
f63072f5 133 case 'or':
05693a83 134 $ret[] = array ('type' => 'LEX_BOOLOP', 'load' => $buffer, 'lineno' => $lineno);
a1b88eca
DO
135 break;
136 case 'not':
05693a83 137 $ret[] = array ('type' => 'LEX_NOT', 'lineno' => $lineno);
a1b88eca
DO
138 break;
139 case 'false':
140 case 'true':
05693a83 141 $ret[] = array ('type' => 'LEX_BOOLCONST', 'load' => $buffer, 'lineno' => $lineno);
f63072f5 142 break;
578d9d02
DO
143 case 'context':
144 $ret[] = array ('type' => 'LEX_CONTEXT', 'lineno' => $lineno);
145 break;
146 case 'clear':
147 $ret[] = array ('type' => 'LEX_CLEAR', 'lineno' => $lineno);
148 break;
149 case 'insert':
150 $ret[] = array ('type' => 'LEX_INSERT', 'lineno' => $lineno);
151 break;
152 case 'remove':
153 $ret[] = array ('type' => 'LEX_REMOVE', 'lineno' => $lineno);
154 break;
155 case 'on':
156 $ret[] = array ('type' => 'LEX_ON', 'lineno' => $lineno);
157 break;
f63072f5 158 default:
05693a83 159 return lexError2 ($buffer, $lineno);
f63072f5 160 }
f5e48a49
DO
161 if ($char == ')')
162 $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno);
f63072f5
DO
163 $newstate = 'ESOTSM';
164 break;
165 default:
05693a83 166 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
167 }
168 break;
169 case 'reading tag 1':
170 switch (TRUE)
171 {
05693a83 172 case ($char == "\n"):
05693a83
DO
173 $lineno++; // fall through
174 case ($char == " "):
175 case ($char == "\t"):
f63072f5
DO
176 // nom-nom...
177 break;
9f54e6e9 178 case (mb_ereg ('[[:alnum:]\$]', $char) > 0):
f63072f5
DO
179 $buffer = $char;
180 $newstate = 'reading tag 2';
181 break;
182 default:
05693a83 183 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
184 }
185 break;
186 case 'reading tag 2':
187 switch (TRUE)
188 {
189 case ($char == '}'):
e5bd52e5 190 $buffer = rtrim ($buffer);
9f54e6e9 191 if (!validTagName ($buffer, TRUE))
05693a83 192 return lexError4 ($buffer, $lineno);
d42589e4 193 $ret[] = array ('type' => ($buffer[0] == '$' ? 'LEX_AUTOTAG' : 'LEX_TAG'), 'load' => $buffer, 'lineno' => $lineno);
f63072f5
DO
194 $newstate = 'ESOTSM';
195 break;
23818dde 196 case (mb_ereg ('[[:alnum:]\. _~-]', $char) > 0):
f63072f5
DO
197 $buffer .= $char;
198 break;
199 default:
05693a83 200 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
201 }
202 break;
203 case 'reading predicate 1':
204 switch (TRUE)
205 {
a872b565 206 case (preg_match ('/^[ \t\n]$/', $char)):
f63072f5
DO
207 // nom-nom...
208 break;
9f54e6e9 209 case (mb_ereg ('[[:alnum:]]', $char) > 0):
f63072f5
DO
210 $buffer = $char;
211 $newstate = 'reading predicate 2';
212 break;
213 default:
05693a83 214 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
215 }
216 break;
217 case 'reading predicate 2':
218 switch (TRUE)
219 {
220 case ($char == ']'):
e5bd52e5 221 $buffer = rtrim ($buffer);
9f54e6e9 222 if (!validTagName ($buffer))
05693a83
DO
223 return lexError4 ($buffer, $lineno);
224 $ret[] = array ('type' => 'LEX_PREDICATE', 'load' => $buffer, 'lineno' => $lineno);
f63072f5
DO
225 $newstate = 'ESOTSM';
226 break;
23818dde 227 case (mb_ereg ('[[:alnum:]\. _~-]', $char) > 0):
f63072f5
DO
228 $buffer .= $char;
229 break;
230 default:
05693a83 231 return lexError1 ($state, $text, $i, $lineno);
f63072f5
DO
232 }
233 break;
234 case 'skipping comment':
bcd49780 235 switch ($char)
f63072f5
DO
236 {
237 case "\n":
05693a83 238 $lineno++;
f63072f5
DO
239 $newstate = 'ESOTSM';
240 default: // eat char, nom-nom...
241 break;
242 }
243 break;
244 default:
245 die (__FUNCTION__ . "(): internal error, state == ${state}");
246 endswitch;
247 $state = $newstate;
248 endfor;
e6a4adb9 249 if ($state != 'ESOTSM' and $state != 'skipping comment')
05693a83 250 return lexError3 ($state, $lineno);
cf25e649 251 return array ('result' => 'ACK', 'load' => $ret);
f63072f5
DO
252}
253
a0527aef
DO
254// Take a parse tree and figure out if it is a valid payload or not.
255// Depending on that return either NULL or an array filled with the load
256// of that expression.
257function spotPayload ($text, $reqtype = 'SYNT_CODETEXT')
258{
259 $lex = getLexemsFromRawText ($text);
260 if ($lex['result'] != 'ACK')
261 return $lex;
262 $stack = getParseTreeFromLexems ($lex['load']);
263 // The only possible way to "accept" is to have sole starting
264 // nonterminal on the stack (and it must be of the requested class).
265 if (count ($stack) == 1 and $stack[0]['type'] == $reqtype)
266 return array ('result' => 'ACK', 'load' => $stack[0]['load']);
267 // No luck. Prepare to complain.
268 if ($lineno = locateSyntaxError ($stack))
269 return array ('result' => 'NAK', 'load' => "Syntax error for type '${reqtype}' near line ${lineno}");
270 // HCF!
3918aaa3 271 return array ('result' => 'NAK', 'load' => "Syntax error for type '${reqtype}', line number unknown");
a0527aef
DO
272}
273
a1b88eca 274// Parse the given lexems stream into a list of RackCode sentences. Each such
a27a02c5
DO
275// sentence is a syntax tree, suitable for tag sequence evaluation. The final
276// parse tree may contain the following nodes:
277// LEX_TAG
d42589e4 278// LEX_AUTOTAG
a27a02c5
DO
279// LEX_PREDICATE
280// LEX_BOOLCONST
05693a83 281// SYNT_NOTEXPR (1 argument, holding SYNT_EXPR)
e5bd52e5 282// SYNT_BOOLOP (2 arguments, each holding SYNT_EXPR)
e5bd52e5 283// SYNT_DEFINITION (2 arguments: term and definition)
1381b655 284// SYNT_GRANT (2 arguments: decision and condition)
578d9d02 285// SYNT_ADJUSTMENT (context modifier with action(s) and condition)
1381b655 286// SYNT_CODETEXT (sequence of sentences)
a27a02c5
DO
287//
288// After parsing the input successfully a list of SYNT_GRANT and SYNT_DEFINITION
289// trees is returned.
a0527aef
DO
290//
291// P.S. The above is true for input, which is a complete and correct RackCode text.
292// Other inputs may produce different combinations of lex/synt structures. Calling
293// function must check the parse tree itself.
294function getParseTreeFromLexems ($lexems)
a1b88eca 295{
a1b88eca
DO
296 $stack = array(); // subject to array_push() and array_pop()
297 $done = 0; // $lexems[$done] is the next item in the tape
298 $todo = count ($lexems);
299
1381b655
DO
300 // Perform shift-reduce processing. The "accept" actions occurs with an
301 // empty input tape and the stack holding only one symbol (the start
05693a83
DO
302 // symbol, SYNT_CODETEXT). When reducing, set the "line number" of
303 // the reduction result to the line number of the "latest" item of the
304 // reduction base (the one on the stack top). This will help locating
305 // parse errors, if any.
1381b655 306 while (TRUE)
a1b88eca 307 {
578d9d02 308 $stacktop = $stacksecondtop = $stackthirdtop = $stackfourthtop = array ('type' => 'null');
a1b88eca
DO
309 $stacksize = count ($stack);
310 if ($stacksize >= 1)
311 {
312 $stacktop = array_pop ($stack);
313 // It is possible to run into a S/R conflict, when having a syntaxically
314 // correct sentence base on the stack and some "and {something}" items
315 // on the input tape, hence let's detect this specific case and insist
316 // on "shift" action to make EXPR parsing hungry one.
1381b655
DO
317 if
318 (
319 $stacktop['type'] == 'SYNT_EXPR' and
320 ($done < $todo) and
321 $lexems[$done]['type'] == 'LEX_BOOLOP'
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
DO
345 // First detect definition start to save the predicate from being reduced into
346 // expression.
347 if
348 (
349 $stacktop['type'] == 'LEX_PREDICATE' and
350 $stacksecondtop['type'] == 'LEX_DEFINE'
351 )
352 {
05693a83 353 // reduce!
1381b655
DO
354 array_pop ($stack);
355 array_pop ($stack);
356 array_push
357 (
358 $stack,
359 array
360 (
361 'type' => 'SYNT_DEFINE',
05693a83 362 'lineno' => $stacktop['lineno'],
1381b655
DO
363 'load' => $stacktop['load']
364 )
365 );
366 continue;
367 }
578d9d02
DO
368 if
369 (
370 $stacktop['type'] == 'LEX_CLEAR'
371 )
372 {
373 // reduce!
374 array_pop ($stack);
375 array_push
376 (
377 $stack,
378 array
379 (
380 'type' => 'SYNT_CTXMOD',
381 'lineno' => $stacktop['lineno'],
382 'load' => array ('op' => 'clear')
383 )
384 );
385 continue;
386 }
387 if
388 (
389 $stacktop['type'] == 'LEX_TAG' and
390 $stacksecondtop['type'] == 'LEX_INSERT'
391 )
392 {
393 // reduce!
394 array_pop ($stack);
534d6c2a 395 array_pop ($stack);
578d9d02
DO
396 array_push
397 (
398 $stack,
399 array
400 (
401 'type' => 'SYNT_CTXMOD',
402 'lineno' => $stacktop['lineno'],
d94022fe 403 'load' => array ('op' => 'insert', 'tag' => $stacktop['load'], 'lineno' => $stacktop['lineno'])
578d9d02
DO
404 )
405 );
406 continue;
407 }
408 if
409 (
410 $stacktop['type'] == 'LEX_TAG' and
411 $stacksecondtop['type'] == 'LEX_REMOVE'
412 )
413 {
414 // reduce!
415 array_pop ($stack);
534d6c2a 416 array_pop ($stack);
578d9d02
DO
417 array_push
418 (
419 $stack,
420 array
421 (
422 'type' => 'SYNT_CTXMOD',
423 'lineno' => $stacktop['lineno'],
d94022fe 424 'load' => array ('op' => 'remove', 'tag' => $stacktop['load'], 'lineno' => $stacktop['lineno'])
578d9d02
DO
425 )
426 );
427 continue;
428 }
429 if
430 (
431 $stacktop['type'] == 'SYNT_CTXMOD' and
432 $stacksecondtop['type'] == 'SYNT_CTXMODLIST'
433 )
434 {
435 // reduce!
436 array_pop ($stack);
437 array_pop ($stack);
438 array_push
439 (
440 $stack,
441 array
442 (
443 'type' => 'SYNT_CTXMODLIST',
444 'lineno' => $stacktop['lineno'],
00f887f4 445 'load' => array_merge ($stacksecondtop['load'], array ($stacktop['load']))
578d9d02
DO
446 )
447 );
448 continue;
449 }
450 if
451 (
452 $stacktop['type'] == 'SYNT_CTXMOD'
453 )
454 {
455 // reduce!
456 array_pop ($stack);
457 array_push
458 (
459 $stack,
460 array
461 (
462 'type' => 'SYNT_CTXMODLIST',
463 'lineno' => $stacktop['lineno'],
8e80aaa9 464 'load' => array ($stacktop['load'])
578d9d02
DO
465 )
466 );
467 continue;
468 }
a1b88eca
DO
469 // Try "replace" action only on a non-empty stack.
470 // If a handle is found for reversing a production rule, do it and start a new
471 // cycle instead of advancing further on rule list. This will preserve rule priority
472 // in the grammar and keep us from an extra shift action.
473 if
474 (
475 $stacktop['type'] == 'LEX_BOOLCONST' or
476 $stacktop['type'] == 'LEX_TAG' or
d42589e4 477 $stacktop['type'] == 'LEX_AUTOTAG' or
a1b88eca
DO
478 $stacktop['type'] == 'LEX_PREDICATE'
479 )
480 {
1381b655 481 // reduce!
a1b88eca
DO
482 array_pop ($stack);
483 array_push
484 (
485 $stack,
486 array
487 (
488 'type' => 'SYNT_EXPR',
05693a83 489 'lineno' => $stacktop['lineno'],
a1b88eca
DO
490 'load' => $stacktop
491 )
492 );
493 continue;
494 }
495 if
496 (
497 $stacktop['type'] == 'SYNT_EXPR' and
498 $stacksecondtop['type'] == 'LEX_NOT'
499 )
500 {
1381b655 501 // reduce!
a1b88eca
DO
502 array_pop ($stack);
503 array_pop ($stack);
504 array_push
505 (
506 $stack,
507 array
508 (
509 'type' => 'SYNT_EXPR',
05693a83 510 'lineno' => $stacktop['lineno'],
a1b88eca
DO
511 'load' => array
512 (
513 'type' => 'SYNT_NOTEXPR',
a27a02c5 514 'load' => $stacktop['load']
a1b88eca
DO
515 )
516 )
517 );
518 continue;
519 }
520 if
521 (
522 $stacktop['type'] == 'LEX_RBRACE' and
523 $stacksecondtop['type'] == 'SYNT_EXPR' and
524 $stackthirdtop['type'] == 'LEX_LBRACE'
525 )
526 {
1381b655 527 // reduce!
a1b88eca
DO
528 array_pop ($stack);
529 array_pop ($stack);
530 array_pop ($stack);
05693a83 531 $stacksecondtop['lineno'] = $stacktop['lineno'];
a1b88eca
DO
532 array_push
533 (
534 $stack,
535 $stacksecondtop
536 );
537 continue;
538 }
539 if
540 (
541 $stacktop['type'] == 'SYNT_EXPR' and
542 $stacksecondtop['type'] == 'LEX_BOOLOP' and
543 $stackthirdtop['type'] == 'SYNT_EXPR'
544 )
545 {
1381b655 546 // reduce!
a1b88eca
DO
547 array_pop ($stack);
548 array_pop ($stack);
549 array_pop ($stack);
550 array_push
551 (
552 $stack,
553 array
554 (
555 'type' => 'SYNT_EXPR',
05693a83 556 'lineno' => $stacktop['lineno'],
a1b88eca
DO
557 'load' => array
558 (
559 'type' => 'SYNT_BOOLOP',
560 'subtype' => $stacksecondtop['load'],
a27a02c5
DO
561 'left' => $stackthirdtop['load'],
562 'right' => $stacktop['load']
a1b88eca
DO
563 )
564 )
565 );
566 continue;
567 }
1381b655
DO
568 if
569 (
570 $stacktop['type'] == 'SYNT_EXPR' and
571 $stacksecondtop['type'] == 'LEX_DECISION'
572 )
573 {
574 // reduce!
575 array_pop ($stack);
576 array_pop ($stack);
577 array_push
578 (
579 $stack,
580 array
581 (
582 'type' => 'SYNT_GRANT',
05693a83 583 'lineno' => $stacktop['lineno'],
1381b655 584 'decision' => $stacksecondtop['load'],
a27a02c5 585 'condition' => $stacktop['load']
1381b655
DO
586 )
587 );
588 continue;
589 }
590 if
591 (
592 $stacktop['type'] == 'SYNT_EXPR' and
593 $stacksecondtop['type'] == 'SYNT_DEFINE'
594 )
595 {
596 // reduce!
597 array_pop ($stack);
598 array_pop ($stack);
599 array_push
600 (
601 $stack,
602 array
603 (
604 'type' => 'SYNT_DEFINITION',
05693a83 605 'lineno' => $stacktop['lineno'],
a27a02c5 606 'term' => $stacksecondtop['load'],
bcd37231 607 'definition' => $stacktop['load']
1381b655
DO
608 )
609 );
610 continue;
611 }
612 if
613 (
578d9d02
DO
614 $stacktop['type'] == 'SYNT_EXPR' and
615 $stacksecondtop['type'] == 'LEX_ON' and
616 $stackthirdtop['type'] == 'SYNT_CTXMODLIST' and
617 $stackfourthtop['type'] == 'LEX_CONTEXT'
618 )
619 {
620 // reduce!
621 array_pop ($stack);
622 array_pop ($stack);
623 array_pop ($stack);
624 array_pop ($stack);
625 array_push
626 (
627 $stack,
628 array
629 (
630 'type' => 'SYNT_ADJUSTMENT',
631 'lineno' => $stacktop['lineno'],
632 'modlist' => $stackthirdtop['load'],
633 'condition' => $stacktop['load']
634 )
635 );
636 continue;
637 }
638 if
639 (
640 ($stacktop['type'] == 'SYNT_GRANT' or $stacktop['type'] == 'SYNT_DEFINITION' or $stacktop['type'] == 'SYNT_ADJUSTMENT') and
1381b655
DO
641 $stacksecondtop['type'] == 'SYNT_CODETEXT'
642 )
643 {
644 // reduce!
645 array_pop ($stack);
646 array_pop ($stack);
647 $stacksecondtop['load'][] = $stacktop;
05693a83 648 $stacksecondtop['lineno'] = $stacktop['lineno'];
1381b655
DO
649 array_push
650 (
651 $stack,
652 $stacksecondtop
653 );
654 continue;
655 }
656 if
657 (
a27a02c5 658 $stacktop['type'] == 'SYNT_GRANT' or
578d9d02
DO
659 $stacktop['type'] == 'SYNT_DEFINITION' or
660 $stacktop['type'] == 'SYNT_ADJUSTMENT'
1381b655
DO
661 )
662 {
663 // reduce!
664 array_pop ($stack);
1381b655
DO
665 array_push
666 (
667 $stack,
668 array
669 (
670 'type' => 'SYNT_CODETEXT',
05693a83 671 'lineno' => $stacktop['lineno'],
1381b655
DO
672 'load' => array ($stacktop)
673 )
674 );
675 continue;
676 }
677 }
678 // The fact we execute here means, that no reduction or early shift
679 // has been done. The only way to enter another iteration is to "shift"
a0527aef
DO
680 // more, if possible. If shifting isn't possible due to empty input tape,
681 // we are facing the final "accept"/"reject" dilemma. In this case our
682 // work is done here, so return the whole stack to the calling function
683 // to decide depending on what it is expecting.
1381b655
DO
684 if ($done < $todo)
685 {
686 array_push ($stack, $lexems[$done++]);
687 continue;
a1b88eca 688 }
1381b655 689 // The moment of truth.
a0527aef 690 return $stack;
a1b88eca 691 }
a1b88eca
DO
692}
693
a0527aef 694function eval_expression ($expr, $tagchain, $ptable, $silent = FALSE)
a27a02c5 695{
bcd37231 696 switch ($expr['type'])
a27a02c5 697 {
d42589e4
DO
698 // Return true, if given tag is present on the tag chain.
699 case 'LEX_TAG':
700 case 'LEX_AUTOTAG':
bcd37231
DO
701 foreach ($tagchain as $tagInfo)
702 if ($expr['load'] == $tagInfo['tag'])
703 return TRUE;
a27a02c5 704 return FALSE;
bcd37231
DO
705 case 'LEX_PREDICATE': // Find given predicate in the symbol table and evaluate it.
706 $pname = $expr['load'];
707 if (!isset ($ptable[$pname]))
708 {
a0527aef
DO
709 if (!$silent)
710 showError ("Predicate '${pname}' is referenced before declaration", __FUNCTION__);
711 return NULL;
bcd37231
DO
712 }
713 return eval_expression ($ptable[$pname], $tagchain, $ptable);
85a8ec3f 714 case 'LEX_BOOLCONST': // Evaluate a boolean constant.
bcd37231
DO
715 switch ($expr['load'])
716 {
717 case 'true':
718 return TRUE;
719 case 'false':
720 return FALSE;
721 default:
a0527aef
DO
722 if (!$silent)
723 showError ("Could not parse a boolean constant with value '${expr['load']}'", __FUNCTION__);
724 return NULL; // should failure be harder?
bcd37231
DO
725 }
726 case 'SYNT_NOTEXPR':
a0527aef
DO
727 $tmp = eval_expression ($expr['load'], $tagchain, $ptable);
728 if ($tmp === TRUE)
729 return FALSE;
730 elseif ($tmp === FALSE)
731 return TRUE;
732 else
733 return $tmp;
bcd37231
DO
734 case 'SYNT_BOOLOP':
735 $leftresult = eval_expression ($expr['left'], $tagchain, $ptable);
736 switch ($expr['subtype'])
737 {
738 case 'or':
739 if ($leftresult)
740 return TRUE; // early success
741 return eval_expression ($expr['right'], $tagchain, $ptable);
742 case 'and':
743 if (!$leftresult)
744 return FALSE; // early failure
745 return eval_expression ($expr['right'], $tagchain, $ptable);
746 default:
a0527aef
DO
747 if (!$silent)
748 showError ("Cannot evaluate unknown boolean operation '${boolop['subtype']}'");
749 return NULL;
bcd37231 750 }
a27a02c5 751 default:
a0527aef
DO
752 if (!$silent)
753 showError ("Evaluation error, cannot process expression type '${expr['type']}'", __FUNCTION__);
754 return NULL;
a27a02c5
DO
755 break;
756 }
757}
758
8e80aaa9
DO
759// Process a context adjustment request, update given chain accordingly,
760// return TRUE on any changes done.
761// The request is a sequence of clear/insert/remove requests exactly as cooked
762// for each SYNT_CTXMODLIST node.
00f887f4 763function processAdjustmentSentence ($modlist, &$chain)
a27a02c5 764{
8e80aaa9
DO
765 global $rackCode;
766 $didChanges = FALSE;
00f887f4
DO
767 foreach ($modlist as $mod)
768 switch ($mod['op'])
769 {
770 case 'insert':
8e80aaa9
DO
771 foreach ($chain as $etag)
772 if ($etag['tag'] == $mod['tag']) // already there, next request
773 break 2;
7ddb2c05
DO
774 $search = getTagByName ($mod['tag']);
775 if ($search === NULL) // skip martians silently
776 break;
777 $chain[] = $search;
8e80aaa9 778 $didChanges = TRUE;
00f887f4
DO
779 break;
780 case 'remove':
8e80aaa9
DO
781 foreach ($chain as $key => $etag)
782 if ($etag['tag'] == $mod['tag']) // drop first match and return
783 {
784 unset ($chain[$key]);
785 $didChanges = TRUE;
786 break 2;
787 }
00f887f4
DO
788 break;
789 case 'clear':
8e80aaa9
DO
790 $chain = array();
791 $didChanges = TRUE;
00f887f4
DO
792 break;
793 default: // HCF
794 showError ('Internal error', __FUNCTION__);
795 die;
796 }
8e80aaa9 797 return $didChanges;
00f887f4
DO
798}
799
800// The argument doesn't include explicit and implicit tags. This allows us to derive implicit chain
801// each time we modify the given argument (and work with the modified copy from now on).
802// After the work is done the global $impl_tags is silently modified
803function gotClearanceForTagChain ($const_base)
804{
805 global $rackCode, $expl_tags, $impl_tags;
a27a02c5 806 $ptable = array();
bcd37231 807 foreach ($rackCode as $sentence)
a27a02c5
DO
808 {
809 switch ($sentence['type'])
810 {
811 case 'SYNT_DEFINITION':
812 $ptable[$sentence['term']] = $sentence['definition'];
813 break;
814 case 'SYNT_GRANT':
00f887f4 815 if (eval_expression ($sentence['condition'], array_merge ($const_base, $expl_tags, $impl_tags), $ptable))
a27a02c5
DO
816 switch ($sentence['decision'])
817 {
818 case 'allow':
819 return TRUE;
820 case 'deny':
821 return FALSE;
822 default:
85a8ec3f 823 showError ("Condition match for unknown grant decision '${sentence['decision']}'", __FUNCTION__);
a27a02c5
DO
824 break;
825 }
bcd37231 826 break;
578d9d02 827 case 'SYNT_ADJUSTMENT':
00f887f4
DO
828 if
829 (
830 eval_expression ($sentence['condition'], array_merge ($const_base, $expl_tags, $impl_tags), $ptable) and
831 processAdjustmentSentence ($sentence['modlist'], $expl_tags)
832 ) // recalculate implicit chain only after actual change, not just on matched condition
833 $impl_tags = getImplicitTags ($expl_tags); // recalculate
578d9d02 834 break;
a27a02c5 835 default:
85a8ec3f 836 showError ("Can't process sentence of unknown type '${sentence['type']}'", __FUNCTION__);
a27a02c5
DO
837 break;
838 }
839 }
840 return FALSE;
841}
842
a0527aef
DO
843// Top-level wrapper for most of the code in this file. Get a text, return a parse tree
844// (or error message).
cf25e649 845function getRackCode ($text)
bcd37231 846{
cf25e649
DO
847 if (!mb_strlen ($text))
848 return array ('result' => 'NAK', 'load' => 'The RackCode text was found empty in ' . __FUNCTION__);
849 $text = str_replace ("\r", '', $text) . "\n";
3918aaa3 850 $synt = spotPayload ($text, 'SYNT_CODETEXT');
cf25e649
DO
851 if ($synt['result'] != 'ACK')
852 return $synt;
853 // An empty sentence list is semantically valid, yet senseless,
854 // so checking intermediate result once more won't hurt.
855 if (!count ($synt['load']))
856 return array ('result' => 'NAK', 'load' => 'Empty parse tree found in ' . __FUNCTION__);
857 return semanticFilter ($synt['load']);
bcd37231
DO
858}
859
3082e137
DO
860// Return NULL, if the given expression can be evaluated against the given
861// predicate list. Return the name of the first show stopper otherwise.
862function firstUnrefPredicate ($plist, $expr)
e6a4adb9
DO
863{
864 switch ($expr['type'])
865 {
85a8ec3f 866 case 'LEX_BOOLCONST':
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':
3082e137 873 return firstUnrefPredicate ($plist, $expr['load']);
e6a4adb9 874 case 'SYNT_BOOLOP':
3082e137
DO
875 if (($tmp = firstUnrefPredicate ($plist, $expr['left'])) !== NULL)
876 return $tmp;
877 if (($tmp = firstUnrefPredicate ($plist, $expr['right'])) !== NULL)
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 {
1072 case 'LEX_BOOLCONST':
1073 case 'LEX_PREDICATE':
15555995 1074 case 'LEX_TAG':
d42589e4
DO
1075 return array();
1076 case 'LEX_AUTOTAG':
15555995
DO
1077 switch (TRUE)
1078 {
1079 case (mb_ereg_match ('^\$id_', $expr['load'])):
1080 $recid = mb_ereg_replace ('^\$id_', '', $expr['load']);
a6e91ac2 1081 if (NULL !== spotEntity ('object', $recid))
15555995
DO
1082 return array();
1083 return array (array
1084 (
061452b9 1085 'header' => refRCLineno ($expr['lineno']),
15555995 1086 'class' => 'warning',
52837ce6 1087 'text' => "An object with ID '${recid}' does not exist."
15555995
DO
1088 ));
1089 case (mb_ereg_match ('^\$ipv4netid_', $expr['load'])):
1090 $recid = mb_ereg_replace ('^\$ipv4netid_', '', $expr['load']);
a6e91ac2 1091 if (NULL != spotEntity ('ipv4net', $recid))
15555995
DO
1092 return array();
1093 return array (array
1094 (
061452b9 1095 'header' => refRCLineno ($expr['lineno']),
15555995 1096 'class' => 'warning',
1a315491
DO
1097 'text' => "IPv4 network with ID '${recid}' does not exist."
1098 ));
1099 case (mb_ereg_match ('^\$userid_', $expr['load'])):
1100 $recid = mb_ereg_replace ('^\$userid_', '', $expr['load']);
a6e91ac2 1101 if (NULL !== spotEntity ('user', $recid))
1a315491
DO
1102 return array();
1103 return array (array
1104 (
061452b9 1105 'header' => refRCLineno ($expr['lineno']),
1a315491
DO
1106 'class' => 'warning',
1107 'text' => "User account with ID '${recid}' does not exist."
1108 ));
1109 case (mb_ereg_match ('^\$username_', $expr['load'])):
1a315491 1110 $recid = mb_ereg_replace ('^\$username_', '', $expr['load']);
b82cce3f
DO
1111 global $require_local_account;
1112 if (!$require_local_account)
1113 return array();
1114 if (NULL !== getUserIDByUsername ($recid))
1a315491
DO
1115 return array();
1116 return array (array
1117 (
061452b9 1118 'header' => refRCLineno ($expr['lineno']),
1a315491 1119 'class' => 'warning',
b82cce3f 1120 'text' => "Local user account '${recid}' does not exist."
15555995 1121 ));
743b8274 1122 // FIXME: pull identifier at the same pass, which does the matching
17112e81 1123 case (mb_ereg_match ('^\$page_[[:alnum:]]+$', $expr['load'])):
743b8274
DO
1124 $recid = mb_ereg_replace ('^\$page_', '', $expr['load']);
1125 global $page;
1126 if (isset ($page[$recid]))
1127 return array();
1128 return array (array
1129 (
1130 'header' => refRCLineno ($expr['lineno']),
1131 'class' => 'warning',
1132 'text' => "Page number '${recid}' does not exist."
1133 ));
17112e81
DO
1134 case (mb_ereg_match ('^\$tab_[[:alnum:]]+$', $expr['load'])):
1135 case (mb_ereg_match ('^\$op_[[:alnum:]]+$', $expr['load'])):
dc97ad5e
DO
1136 case (mb_ereg_match ('^\$any_op$', $expr['load'])):
1137 case (mb_ereg_match ('^\$any_rack$', $expr['load'])):
1138 case (mb_ereg_match ('^\$any_object$', $expr['load'])):
1139 case (mb_ereg_match ('^\$any_ip4net$', $expr['load'])):
1140 case (mb_ereg_match ('^\$any_net$', $expr['load'])):
1141 case (mb_ereg_match ('^\$any_ipv4vs$', $expr['load'])):
1142 case (mb_ereg_match ('^\$any_vs$', $expr['load'])):
1143 case (mb_ereg_match ('^\$any_ipv4rsp$', $expr['load'])):
1144 case (mb_ereg_match ('^\$any_rsp$', $expr['load'])):
1145 case (mb_ereg_match ('^\$any_file$', $expr['load'])):
1146 case (mb_ereg_match ('^\$typeid_[[:digit:]]+$', $expr['load'])): // FIXME: check value validity
1147 case (mb_ereg_match ('^\$cn_.+$', $expr['load'])): // FIXME: check name validity and asset existence
1148 case (mb_ereg_match ('^\$lgcn_.+$', $expr['load'])): // FIXME: check name validity
17112e81
DO
1149 case (mb_ereg_match ('^\$fromvlan_[[:digit:]]+$', $expr['load'])):
1150 case (mb_ereg_match ('^\$tovlan_[[:digit:]]+$', $expr['load'])):
3d670bba 1151 case (mb_ereg_match ('^\$unmounted$', $expr['load'])):
15555995 1152 return array();
dc97ad5e
DO
1153 default:
1154 return array (array
1155 (
1156 'header' => refRCLineno ($expr['lineno']),
1157 'class' => 'warning',
1158 'text' => "Martian autotag '${expr['load']}'"
1159 ));
15555995
DO
1160 }
1161 case 'SYNT_NOTEXPR':
b82cce3f 1162 return $self ($expr['load']);
15555995
DO
1163 case 'SYNT_BOOLOP':
1164 return array_merge
1165 (
b82cce3f
DO
1166 $self ($expr['left']),
1167 $self ($expr['right'])
15555995
DO
1168 );
1169 default:
1170 return array (array
1171 (
1172 'header' => 'internal error',
1173 'class' => 'error',
1a4096cb
DO
1174 'text' => "Skipped expression of unknown type '${expr['type']}'"
1175 ));
1176 }
1177}
1178
1179// Idem WRT tags.
1180function findTagWarnings ($expr)
1181{
1a4096cb
DO
1182 switch ($expr['type'])
1183 {
1184 case 'LEX_BOOLCONST':
1185 case 'LEX_PREDICATE':
d42589e4 1186 case 'LEX_AUTOTAG':
1a4096cb
DO
1187 return array();
1188 case 'LEX_TAG':
d94022fe
DO
1189 if (getTagByName ($expr['load']) !== NULL)
1190 return array();
1a4096cb
DO
1191 return array (array
1192 (
061452b9 1193 'header' => refRCLineno ($expr['lineno']),
1a4096cb
DO
1194 'class' => 'warning',
1195 'text' => "Tag '${expr['load']}' does not exist."
1196 ));
1197 case 'SYNT_NOTEXPR':
1198 return findTagWarnings ($expr['load']);
1199 case 'SYNT_BOOLOP':
1200 return array_merge
1201 (
1202 findTagWarnings ($expr['left']),
1203 findTagWarnings ($expr['right'])
1204 );
1205 default:
1206 return array (array
1207 (
1208 'header' => 'internal error',
1209 'class' => 'error',
1210 'text' => "Skipped expression of unknown type '${expr['type']}'"
15555995
DO
1211 ));
1212 }
1213}
1214
d94022fe
DO
1215// Check context modifiers, warn about those, which try referencing non-existent tags.
1216function findCtxModWarnings ($modlist)
1217{
1218 $ret = array();
1219 foreach ($modlist as $mod)
1220 if (($mod['op'] == 'insert' or $mod['op'] == 'remove') and NULL === getTagByName ($mod['tag']))
1221 $ret[] = array
1222 (
1223 'header' => refRCLineno ($mod['lineno']),
1224 'class' => 'warning',
1225 'text' => "Tag '${mod['tag']}' does not exist."
1226 );
1227 return $ret;
1228}
1229
52837ce6
DO
1230// Return true, if the expression makes use of the predicate given.
1231function referencedPredicate ($pname, $expr)
1232{
1233 switch ($expr['type'])
1234 {
1235 case 'LEX_BOOLCONST':
1236 case 'LEX_TAG':
d42589e4 1237 case 'LEX_AUTOTAG':
52837ce6
DO
1238 return FALSE;
1239 case 'LEX_PREDICATE':
1240 return $pname == $expr['load'];
1241 case 'SYNT_NOTEXPR':
1242 return referencedPredicate ($pname, $expr['load']);
1243 case 'SYNT_BOOLOP':
1244 return referencedPredicate ($pname, $expr['left']) or referencedPredicate ($pname, $expr['right']);
1245 default: // This is actually an internal error.
1246 return FALSE;
1247 }
1248}
1249
d05ed211
DO
1250// Return 'always true', 'always false' or any other verdict.
1251function invariantExpression ($expr)
1252{
1253 $self = __FUNCTION__;
1254 switch ($expr['type'])
1255 {
1256 case 'SYNT_GRANT':
1257 return $self ($expr['condition']);
1258 case 'SYNT_DEFINITION':
1259 return $self ($expr['definition']);
1260 case 'LEX_BOOLCONST':
1261 if ($expr['load'] == 'true')
1262 return 'always true';
1263 return 'always false';
1264 case 'LEX_TAG':
d42589e4 1265 case 'LEX_AUTOTAG':
d05ed211
DO
1266 case 'LEX_PREDICATE':
1267 return 'sometimes something';
1268 case 'SYNT_NOTEXPR':
1269 return $self ($expr['load']);
1270 case 'SYNT_BOOLOP':
1271 $leftanswer = $self ($expr['left']);
1272 $rightanswer = $self ($expr['right']);
1273 // "true or anything" is always true and thus const
1274 if ($expr['subtype'] == 'or' and ($leftanswer == 'always true' or $rightanswer == 'always true'))
1275 return 'always true';
1276 // "false and anything" is always false and thus const
1277 if ($expr['subtype'] == 'and' and ($leftanswer == 'always false' or $rightanswer == 'always false'))
1278 return 'always false';
1279 // "true and true" is true
1280 if ($expr['subtype'] == 'and' and ($leftanswer == 'always true' and $rightanswer == 'always true'))
1281 return 'always true';
1282 // "false or false" is false
1283 if ($expr['subtype'] == 'or' and ($leftanswer == 'always false' and $rightanswer == 'always false'))
1284 return 'always false';
1285 return '';
1286 default: // This is actually an internal error.
1287 break;
1288 }
1289}
1290
f63072f5 1291?>