r4366 findAutoTagWarnings(): update for spotEntity() throwing an exception
[racktables] / wwwroot / inc / code.php
1 <?php
2 /*
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.
7 *
8 */
9
10 // Complain about martian char.
11 function lexError1 ($state, $text, $pos, $ln = 'N/A')
12 {
13 return array
14 (
15 'result' => 'NAK',
16 'load' => "Invalid character '" . mb_substr ($text, $pos, 1) . "' near line ${ln}"
17 );
18 }
19
20 // Complain about martian keyword.
21 function lexError2 ($word, $ln = 'N/A')
22 {
23 return array
24 (
25 'result' => 'NAK',
26 'load' => "Invalid keyword '${word}' near line ${ln}"
27 );
28 }
29
30 // Complain about wrong FSM state.
31 function lexError3 ($state, $ln = 'N/A')
32 {
33 return array
34 (
35 'result' => 'NAK',
36 'load' => "Lexical error in scanner state '${state}' near line ${ln}"
37 );
38 }
39
40 function lexError4 ($s, $ln = 'N/A')
41 {
42 return array
43 (
44 'result' => 'NAK',
45 'load' => "Invalid name '${s}' near line ${ln}"
46 );
47 }
48
49 /* Produce a list of lexems (tokens) from the given text. Possible lexems are:
50 *
51 * LEX_LBRACE
52 * LEX_RBRACE
53 * LEX_ALLOW
54 * LEX_DENY
55 * LEX_DEFINE
56 * LEX_TRUE
57 * LEX_FALSE
58 * LEX_NOT
59 * LEX_TAG
60 * LEX_AUTOTAG
61 * LEX_PREDICATE
62 * LEX_AND
63 * LEX_OR
64 * LEX_CONTEXT
65 * LEX_CLEAR
66 * LEX_INSERT
67 * LEX_REMOVE
68 * LEX_ON
69 *
70 * FIXME: would these work better as normal (integer) constants?
71 */
72 function getLexemsFromRawText ($text)
73 {
74 $ret = array();
75 // Add a mock character to aid in synchronization with otherwise correct,
76 // but short or odd-terminated final lines.
77 $text .= ' ';
78 $textlen = mb_strlen ($text);
79 $lineno = 1;
80 $state = 'ESOTSM';
81 for ($i = 0; $i < $textlen; $i++) :
82 $char = mb_substr ($text, $i, 1);
83 $newstate = $state;
84 switch ($state) :
85 case 'ESOTSM':
86 switch (TRUE)
87 {
88 case ($char == '('):
89 $ret[] = array ('type' => 'LEX_LBRACE', 'lineno' => $lineno);
90 break;
91 case ($char == ')'):
92 $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno);
93 break;
94 case ($char == '#'):
95 $newstate = 'skipping comment';
96 break;
97 case (preg_match ('/[\p{L}]/u', $char) == 1):
98 $newstate = 'reading keyword';
99 $buffer = $char;
100 break;
101 case ($char == "\n"):
102 $lineno++; // fall through
103 case ($char == ' '):
104 case ($char == "\t"):
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:
114 return lexError1 ($state, $text, $i, $lineno);
115 }
116 break;
117 case 'reading keyword':
118 switch (TRUE)
119 {
120 case (preg_match ('/[\p{L}]/u', $char) == 1):
121 $buffer .= $char;
122 break;
123 case ($char == "\n"):
124 $lineno++; // fall through
125 case ($char == ' '):
126 case ($char == "\t"):
127 case ($char == ')'): // this will be handled below
128 // got a word, sort it out
129 switch ($buffer)
130 {
131 case 'allow':
132 $ret[] = array ('type' => 'LEX_ALLOW', 'lineno' => $lineno);
133 break;
134 case 'deny':
135 $ret[] = array ('type' => 'LEX_DENY', 'lineno' => $lineno);
136 break;
137 case 'define':
138 $ret[] = array ('type' => 'LEX_DEFINE', 'lineno' => $lineno);
139 break;
140 case 'and':
141 $ret[] = array ('type' => 'LEX_AND', 'lineno' => $lineno);
142 break;
143 case 'or':
144 $ret[] = array ('type' => 'LEX_OR', 'lineno' => $lineno);
145 break;
146 case 'not':
147 $ret[] = array ('type' => 'LEX_NOT', 'lineno' => $lineno);
148 break;
149 case 'true':
150 $ret[] = array ('type' => 'LEX_TRUE', 'lineno' => $lineno);
151 break;
152 case 'false':
153 $ret[] = array ('type' => 'LEX_FALSE', 'lineno' => $lineno);
154 break;
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;
170 default:
171 return lexError2 ($buffer, $lineno);
172 }
173 if ($char == ')')
174 $ret[] = array ('type' => 'LEX_RBRACE', 'lineno' => $lineno);
175 $newstate = 'ESOTSM';
176 break;
177 default:
178 return lexError1 ($state, $text, $i, $lineno);
179 }
180 break;
181 case 'reading tag 1':
182 switch (TRUE)
183 {
184 case ($char == "\n"):
185 $lineno++; // fall through
186 case ($char == ' '):
187 case ($char == "\t"):
188 // nom-nom...
189 break;
190 case (preg_match ('/[\p{L}0-9\$]/u', $char) == 1):
191 $buffer = $char;
192 $newstate = 'reading tag 2';
193 break;
194 default:
195 return lexError1 ($state, $text, $i, $lineno);
196 }
197 break;
198 case 'reading tag 2':
199 switch (TRUE)
200 {
201 case ($char == '}'):
202 $buffer = rtrim ($buffer);
203 if (!validTagName ($buffer, TRUE))
204 return lexError4 ($buffer, $lineno);
205 $ret[] = array ('type' => ($buffer[0] == '$' ? 'LEX_AUTOTAG' : 'LEX_TAG'), 'load' => $buffer, 'lineno' => $lineno);
206 $newstate = 'ESOTSM';
207 break;
208 case (preg_match ('/[\p{L}0-9. _~-]/u', $char) == 1):
209 $buffer .= $char;
210 break;
211 default:
212 return lexError1 ($state, $text, $i, $lineno);
213 }
214 break;
215 case 'reading predicate 1':
216 switch (TRUE)
217 {
218 case ($char == "\n"):
219 $lineno++; // fall through
220 case ($char == ' '):
221 case ($char == "\t"):
222 // nom-nom...
223 break;
224 case (preg_match ('/[\p{L}0-9]/u', $char) == 1):
225 $buffer = $char;
226 $newstate = 'reading predicate 2';
227 break;
228 default:
229 return lexError1 ($state, $text, $i, $lineno);
230 }
231 break;
232 case 'reading predicate 2':
233 switch (TRUE)
234 {
235 case ($char == ']'):
236 $buffer = rtrim ($buffer);
237 if (!validTagName ($buffer))
238 return lexError4 ($buffer, $lineno);
239 $ret[] = array ('type' => 'LEX_PREDICATE', 'load' => $buffer, 'lineno' => $lineno);
240 $newstate = 'ESOTSM';
241 break;
242 case (preg_match ('/[\p{L}0-9. _~-]/u', $char) == 1):
243 $buffer .= $char;
244 break;
245 default:
246 return lexError1 ($state, $text, $i, $lineno);
247 }
248 break;
249 case 'skipping comment':
250 switch ($char)
251 {
252 case "\n":
253 $lineno++;
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;
264 if ($state != 'ESOTSM' and $state != 'skipping comment')
265 return lexError3 ($state, $lineno);
266 return array ('result' => 'ACK', 'load' => $ret);
267 }
268
269 // Parse the given lexems stream into a list of RackCode sentences. Each such
270 // sentence is a syntax tree, suitable for tag sequence evaluation. The final
271 // parse tree may contain the following nodes:
272 // LEX_TAG
273 // LEX_AUTOTAG
274 // LEX_PREDICATE
275 // LEX_TRUE
276 // LEX_FALSE
277 // SYNT_NOT_EXPR (one arg in "load")
278 // SYNT_AND_EXPR (two args in "left" and "right")
279 // SYNT_EXPR (idem), in fact it's boolean OR, but we keep the naming for compatibility
280 // SYNT_DEFINITION (2 args in "term" and "definition")
281 // SYNT_GRANT (2 args in "decision" and "condition")
282 // SYNT_ADJUSTMENT (context modifier with action(s) and condition)
283 // SYNT_CODETEXT (sequence of sentences)
284 //
285 // After parsing the input successfully a list of SYNT_GRANT and SYNT_DEFINITION
286 // trees is returned.
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)
292 {
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
297 // Perform shift-reduce processing. The "accept" actions occurs with an
298 // empty input tape and the stack holding only one symbol (the start
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.
303 while (TRUE)
304 {
305 $stacktop = $stacksecondtop = $stackthirdtop = $stackfourthtop = array ('type' => 'null');
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
313 // on "shift" action to make SYNT_AND_EXPR parsing hungry.
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.
318 if
319 (
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'
322 )
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);
335 if ($stacksize >= 4)
336 {
337 $stackfourthtop = array_pop ($stack);
338 array_push ($stack, $stackfourthtop);
339 }
340 array_push ($stack, $stackthirdtop);
341 }
342 array_push ($stack, $stacksecondtop);
343 }
344 array_push ($stack, $stacktop);
345 // First detect definition start to save the predicate from being reduced into
346 // unary expression.
347 // DEFINE ::= define PREDICATE
348 if
349 (
350 $stacktop['type'] == 'LEX_PREDICATE' and
351 $stacksecondtop['type'] == 'LEX_DEFINE'
352 )
353 {
354 // reduce!
355 array_pop ($stack);
356 array_pop ($stack);
357 array_push
358 (
359 $stack,
360 array
361 (
362 'type' => 'SYNT_DEFINE',
363 'lineno' => $stacktop['lineno'],
364 'load' => $stacktop['load']
365 )
366 );
367 continue;
368 }
369 // CTXMOD ::= clear
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 }
389 // CTXMOD ::= insert TAG
390 if
391 (
392 $stacktop['type'] == 'LEX_TAG' and
393 $stacksecondtop['type'] == 'LEX_INSERT'
394 )
395 {
396 // reduce!
397 array_pop ($stack);
398 array_pop ($stack);
399 array_push
400 (
401 $stack,
402 array
403 (
404 'type' => 'SYNT_CTXMOD',
405 'lineno' => $stacktop['lineno'],
406 'load' => array ('op' => 'insert', 'tag' => $stacktop['load'], 'lineno' => $stacktop['lineno'])
407 )
408 );
409 continue;
410 }
411 // CTXMOD ::= remove TAG
412 if
413 (
414 $stacktop['type'] == 'LEX_TAG' and
415 $stacksecondtop['type'] == 'LEX_REMOVE'
416 )
417 {
418 // reduce!
419 array_pop ($stack);
420 array_pop ($stack);
421 array_push
422 (
423 $stack,
424 array
425 (
426 'type' => 'SYNT_CTXMOD',
427 'lineno' => $stacktop['lineno'],
428 'load' => array ('op' => 'remove', 'tag' => $stacktop['load'], 'lineno' => $stacktop['lineno'])
429 )
430 );
431 continue;
432 }
433 // CTXMODLIST ::= CTXMODLIST CTXMOD
434 if
435 (
436 $stacktop['type'] == 'SYNT_CTXMOD' and
437 $stacksecondtop['type'] == 'SYNT_CTXMODLIST'
438 )
439 {
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'],
449 'load' => array_merge ($stacksecondtop['load'], array ($stacktop['load']))
450 )
451 );
452 continue;
453 }
454 // CTXMODLIST ::= CTXMOD
455 if
456 (
457 $stacktop['type'] == 'SYNT_CTXMOD'
458 )
459 {
460 array_pop ($stack);
461 array_push
462 (
463 $stack,
464 array
465 (
466 'type' => 'SYNT_CTXMODLIST',
467 'lineno' => $stacktop['lineno'],
468 'load' => array ($stacktop['load'])
469 )
470 );
471 continue;
472 }
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 // UNARY_EXPRESSION ::= true | false | TAG | AUTOTAG | PREDICATE
478 if
479 (
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'
485 )
486 {
487 // reduce!
488 array_pop ($stack);
489 array_push
490 (
491 $stack,
492 array
493 (
494 'type' => 'SYNT_UNARY_EXPR',
495 'lineno' => $stacktop['lineno'],
496 'load' => $stacktop
497 )
498 );
499 continue;
500 }
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.
505 if
506 (
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
532 $stacksecondtop['type'] == 'LEX_NOT'
533 )
534 {
535 // reduce!
536 array_pop ($stack);
537 array_pop ($stack);
538 array_push
539 (
540 $stack,
541 array
542 (
543 'type' => 'SYNT_UNARY_EXPR',
544 'lineno' => $stacktop['lineno'],
545 'load' => array
546 (
547 'type' => 'SYNT_NOT_EXPR',
548 'load' => $stacktop['load']
549 )
550 )
551 );
552 continue;
553 }
554 // AND_EXPRESSION ::= AND_EXPRESSION and UNARY_EXPRESSION
555 if
556 (
557 $stacktop['type'] == 'SYNT_UNARY_EXPR' and
558 $stacksecondtop['type'] == 'LEX_AND' and
559 $stackthirdtop['type'] == 'SYNT_AND_EXPR'
560 )
561 {
562 array_pop ($stack);
563 array_pop ($stack);
564 array_pop ($stack);
565 array_push
566 (
567 $stack,
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 )
575 );
576 continue;
577 }
578 // AND_EXPRESSION ::= UNARY_EXPRESSION
579 if
580 (
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
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',
614 'lineno' => $stacktop['lineno'],
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
636 )
637 );
638 continue;
639 }
640 // GRANT ::= allow EXPRESSION | deny EXPRESSION
641 if
642 (
643 $stacktop['type'] == 'SYNT_EXPR' and
644 ($stacksecondtop['type'] == 'LEX_ALLOW' or $stacksecondtop['type'] == 'LEX_DENY')
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',
656 'lineno' => $stacktop['lineno'],
657 'decision' => $stacksecondtop['type'],
658 'condition' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop
659 )
660 );
661 continue;
662 }
663 // DEFINITION ::= DEFINE EXPRESSION
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',
679 'lineno' => $stacktop['lineno'],
680 'term' => $stacksecondtop['load'],
681 'definition' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop
682 )
683 );
684 continue;
685 }
686 // ADJUSTMENT ::= context CTXMODLIST on EXPRESSION
687 if
688 (
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'],
708 'condition' => isset ($stacktop['load']) ? $stacktop['load'] : $stacktop
709 )
710 );
711 continue;
712 }
713 // CODETEXT ::= CODETEXT GRANT | CODETEXT DEFINITION | CODETEXT ADJUSTMENT
714 if
715 (
716 ($stacktop['type'] == 'SYNT_GRANT' or $stacktop['type'] == 'SYNT_DEFINITION' or $stacktop['type'] == 'SYNT_ADJUSTMENT') and
717 $stacksecondtop['type'] == 'SYNT_CODETEXT'
718 )
719 {
720 // reduce!
721 array_pop ($stack);
722 array_pop ($stack);
723 $stacksecondtop['load'][] = $stacktop;
724 $stacksecondtop['lineno'] = $stacktop['lineno'];
725 array_push
726 (
727 $stack,
728 $stacksecondtop
729 );
730 continue;
731 }
732 // CODETEXT ::= GRANT | DEFINITION | ADJUSTMENT
733 if
734 (
735 $stacktop['type'] == 'SYNT_GRANT' or
736 $stacktop['type'] == 'SYNT_DEFINITION' or
737 $stacktop['type'] == 'SYNT_ADJUSTMENT'
738 )
739 {
740 // reduce!
741 array_pop ($stack);
742 array_push
743 (
744 $stack,
745 array
746 (
747 'type' => 'SYNT_CODETEXT',
748 'lineno' => $stacktop['lineno'],
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"
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.
761 if ($done < $todo)
762 {
763 array_push ($stack, $lexems[$done++]);
764 continue;
765 }
766 // The moment of truth.
767 return $stack;
768 }
769 }
770
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)
774 {
775 $self = __FUNCTION__;
776 switch ($expr['type'])
777 {
778 case 'LEX_TRUE':
779 case 'LEX_FALSE':
780 case 'LEX_TAG':
781 case 'LEX_AUTOTAG':
782 return NULL;
783 case 'LEX_PREDICATE':
784 return in_array ($expr['load'], $plist) ? NULL : $expr['load'];
785 case 'SYNT_NOT_EXPR':
786 return $self ($plist, $expr['load']);
787 case 'SYNT_EXPR':
788 case 'SYNT_AND_EXPR':
789 if (($tmp = $self ($plist, $expr['left'])) !== NULL)
790 return $tmp;
791 if (($tmp = $self ($plist, $expr['right'])) !== NULL)
792 return $tmp;
793 return NULL;
794 default:
795 return NULL;
796 }
797 }
798
799 function semanticFilter ($code)
800 {
801 $predicatelist = array();
802 foreach ($code as $sentence)
803 switch ($sentence['type'])
804 {
805 case 'SYNT_DEFINITION':
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.
815 $up = firstUnrefPredicate ($predicatelist, $sentence['definition']);
816 if ($up !== NULL)
817 return array
818 (
819 'result' => 'NAK',
820 'load' => "definition of [${sentence['term']}] refers to [${up}], which is not (yet) defined"
821 );
822 $predicatelist[] = $sentence['term'];
823 break;
824 case 'SYNT_GRANT':
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 );
832 break;
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;
844 default:
845 return array ('result' => 'NAK', 'load' => 'unknown sentence type');
846 }
847 return array ('result' => 'ACK', 'load' => $code);
848 }
849
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
868 function refRCLineno ($ln)
869 {
870 return "<a href='index.php?page=perms&tab=default#line${ln}'>line ${ln}</a>";
871 }
872
873 // Scan the given expression and return any issues found about its autotags.
874 function findAutoTagWarnings ($expr)
875 {
876 $self = __FUNCTION__;
877 switch ($expr['type'])
878 {
879 case 'LEX_TRUE':
880 case 'LEX_FALSE':
881 case 'LEX_PREDICATE':
882 case 'LEX_TAG':
883 return array();
884 case 'LEX_AUTOTAG':
885 switch (1)
886 {
887 case (preg_match ('/^\$id_/', $expr['load'])):
888 $recid = preg_replace ('/^\$id_/', '', $expr['load']);
889 try
890 {
891 spotEntity ('object', $recid);
892 return array();
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 }
903 case (preg_match ('/^\$ipv4netid_/', $expr['load'])):
904 $recid = preg_replace ('/^\$ipv4netid_/', '', $expr['load']);
905 try
906 {
907 spotEntity ('ipv4net', $recid);
908 return array();
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 }
919 case (preg_match ('/^\$userid_/', $expr['load'])):
920 $recid = preg_replace ('/^\$userid_/', '', $expr['load']);
921 try
922 {
923 spotEntity ('user', $recid);
924 return array();
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 }
935 case (preg_match ('/^\$username_/', $expr['load'])):
936 $recid = preg_replace ('/^\$username_/', '', $expr['load']);
937 global $require_local_account;
938 if (!$require_local_account)
939 return array();
940 if (NULL !== getUserIDByUsername ($recid))
941 return array();
942 return array (array
943 (
944 'header' => refRCLineno ($expr['lineno']),
945 'class' => 'warning',
946 'text' => "Local user account '${recid}' does not exist."
947 ));
948 // FIXME: pull identifier at the same pass, which does the matching
949 case (preg_match ('/^\$page_[\p{L}0-9]+$/u', $expr['load'])):
950 $recid = preg_replace ('/^\$page_/', '', $expr['load']);
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 ));
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'])):
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
965 case (preg_match ('/^\$(fromvlan|tovlan)_[[:digit:]]+$/', $expr['load'])):
966 case (preg_match ('/^\$(unmounted|untagged|no_asset_tag|runs_8021Q)$/', $expr['load'])):
967 case (preg_match ('/^\$masklen_(eq|le|ge)_[[:digit:]][[:digit:]]?$/', $expr['load'])):
968 case (preg_match ('/^\$attr_\d+_\d+$/', $expr['load'])):
969 return array();
970 default:
971 return array (array
972 (
973 'header' => refRCLineno ($expr['lineno']),
974 'class' => 'warning',
975 'text' => "Martian autotag '${expr['load']}'"
976 ));
977 }
978 case 'SYNT_NOT_EXPR':
979 return $self ($expr['load']);
980 case 'SYNT_AND_EXPR':
981 case 'SYNT_EXPR':
982 return array_merge
983 (
984 $self ($expr['left']),
985 $self ($expr['right'])
986 );
987 default:
988 return array (array
989 (
990 'header' => "internal error in ${self}",
991 'class' => 'error',
992 'text' => "Skipped expression of unknown type '${expr['type']}'"
993 ));
994 }
995 }
996
997 // Idem WRT tags.
998 function findTagWarnings ($expr)
999 {
1000 $self = __FUNCTION__;
1001 switch ($expr['type'])
1002 {
1003 case 'LEX_TRUE':
1004 case 'LEX_FALSE':
1005 case 'LEX_PREDICATE':
1006 case 'LEX_AUTOTAG':
1007 return array();
1008 case 'LEX_TAG':
1009 if (getTagByName ($expr['load']) !== NULL)
1010 return array();
1011 return array (array
1012 (
1013 'header' => refRCLineno ($expr['lineno']),
1014 'class' => 'warning',
1015 'text' => "Tag '${expr['load']}' does not exist."
1016 ));
1017 case 'SYNT_NOT_EXPR':
1018 return $self ($expr['load']);
1019 case 'SYNT_AND_EXPR':
1020 case 'SYNT_EXPR':
1021 return array_merge
1022 (
1023 $self ($expr['left']),
1024 $self ($expr['right'])
1025 );
1026 default:
1027 return array (array
1028 (
1029 'header' => "internal error in ${self}",
1030 'class' => 'error',
1031 'text' => "Skipped expression of unknown type '${expr['type']}'"
1032 ));
1033 }
1034 }
1035
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
1051 // Return true, if the expression makes use of the predicate given.
1052 function referencedPredicate ($pname, $expr)
1053 {
1054 $self = __FUNCTION__;
1055 switch ($expr['type'])
1056 {
1057 case 'LEX_TRUE':
1058 case 'LEX_FALSE':
1059 case 'LEX_TAG':
1060 case 'LEX_AUTOTAG':
1061 return FALSE;
1062 case 'LEX_PREDICATE':
1063 return $pname == $expr['load'];
1064 case 'SYNT_NOT_EXPR':
1065 return $self ($pname, $expr['load']);
1066 case 'SYNT_AND_EXPR':
1067 case 'SYNT_EXPR':
1068 return $self ($pname, $expr['left']) or $self ($pname, $expr['right']);
1069 default: // This is actually an internal error.
1070 return FALSE;
1071 }
1072 }
1073
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']);
1084 case 'LEX_TRUE':
1085 return 'always true';
1086 case 'LEX_FALSE':
1087 return 'always false';
1088 case 'LEX_TAG':
1089 case 'LEX_AUTOTAG':
1090 case 'LEX_PREDICATE':
1091 return 'sometimes something';
1092 case 'SYNT_NOT_EXPR':
1093 return $self ($expr['load']);
1094 case 'SYNT_AND_EXPR':
1095 $leftanswer = $self ($expr['left']);
1096 $rightanswer = $self ($expr['right']);
1097 // "false and anything" is always false and thus const
1098 if ($leftanswer == 'always false' or $rightanswer == 'always false')
1099 return 'always false';
1100 // "true and true" is true
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')
1109 return 'always true';
1110 // "false or false" is false
1111 if ($leftanswer == 'always false' and $rightanswer == 'always false')
1112 return 'always false';
1113 return '';
1114 default: // This is actually an internal error.
1115 break;
1116 }
1117 }
1118
1119 ?>