improve CodeMirror integration
[racktables] / wwwroot / inc / code.php
CommitLineData
f63072f5 1<?php
cddbb9fd
DO
2
3# This file is a part of RackTables, a datacenter and server room management
4# framework. See accompanying file "COPYING" for the full copyright and
5# licensing information.
6
a1b88eca 7/*
ef0503fc
DO
8 * This file implements lexical scanner and syntax analyzer for the
9 * RackCode language. These functions are necessary for parsing and
10 * analysis, which don't happen at every script invocation, and this
11 * file is only included when necessary.
a1b88eca 12 *
a1b88eca 13 */
f63072f5 14
f3685414 15
4b266fe7 16class RCParserError extends Exception
f63072f5 17{
a9719fde
AA
18 public $lineno;
19 function __construct ($message, $lineno = 0)
be9e1818 20 {
a9719fde
AA
21 $this->message = $message;
22 $this->lineno = $lineno;
be9e1818 23 }
f3685414 24};
f63072f5 25
f3685414
AA
26/*
27 * This class implements a recursive descent parser for the RackCode.
28 * It is possible because the RackCode is a LL(1) language.
29 *
30 * Usage:
31 * $parser = new RackCodeParser();
32 * $statements = $parser->parse ($text1);
33 * $expression = $parser->parse ($text2, 'expr');
34 */
35class RackCodeParser
f63072f5 36{
f3685414
AA
37 function parse ($text, $root_nt = 'prog')
38 {
39 $this->defined_preds = array();
40 $this->prog_mode = FALSE;
41 // init lex
42 $this->i = 0;
43 $this->text = $text;
44 $this->text_len = strlen ($text);
45 $this->lineno = 1;
46 $this->lex_value = NULL;
47 try
48 {
49 $this->token = $this->get_token();
50 $ret = $this->$root_nt();
f63072f5 51
f3685414
AA
52 // check that all input characters are consumed by grammar
53 if ($this->token !== NULL)
4b266fe7 54 throw new RCParserError ("unexpected {$this->token}");
f3685414
AA
55 return $ret;
56 }
4b266fe7 57 catch (RCParserError $e)
f3685414 58 {
a9719fde 59 $e->lineno = $this->lineno;
f3685414
AA
60 throw $e;
61 }
62 }
a1b88eca 63
0e69eafc 64 // $sym could either be a string or a list of strings.
4b266fe7 65 // returns the value of the $sym class in the current position or throws the RCParserError.
f3685414
AA
66 function expect ($sym)
67 {
68 if (NULL !== $ret = $this->accept ($sym))
69 return $ret;
4b266fe7 70 throw new RCParserError ("expecting $sym");
f3685414 71 }
9f54e6e9 72
0e69eafc 73 // $sym could either be a string or a list of strings.
f3685414
AA
74 // returns the value of the $sym class in the current position or NULL.
75 function accept ($sym)
76 {
77 $curr = $this->token;
78 $value = $this->lex_value;
79 if
80 (
55eefced
DO
81 $curr !== NULL && (
82 ! isset ($sym) ||
83 is_array ($sym) && in_array ($curr, $sym) ||
84 ! is_array ($sym) && $sym == $curr
f3685414
AA
85 )
86 )
87 {
88 $this->token = $this->get_token();
89 return $value;
90 }
91 }
f63072f5 92
f3685414
AA
93 ##########################################
94 # lexer
95 ##########################################
a1b88eca 96
f3685414
AA
97 // Returns the first char matching $char_mask (if invert_mask = 0).
98 // Supports only single-byte chars in mask
99 // Input text could be in UTF-8
100 function stop_on_char ($char_mask, &$buff, $invert_mask = FALSE)
a1b88eca 101 {
f3685414
AA
102 if ($invert_mask)
103 $offset = strspn ($this->text, $char_mask, $this->i);
104 else
105 $offset = strcspn ($this->text, $char_mask, $this->i);
106 if ($offset)
107 $buff .= substr ($this->text, $this->i, $offset);
108 $this->i += $offset;
109 return $this->get_char();
110 }
111
f3685414 112 // Returns the next available character or string "END"
0e69eafc 113 // Deals with UTF-8 encoded string $this->text.
f3685414
AA
114 function get_char()
115 {
116 if ($this->i >= $this->text_len)
117 return 'END';
118 $c = $this->text[$this->i];
119 if (ord ($c) < 0xc0)
a1b88eca 120 {
f3685414
AA
121 $this->i++;
122 return $c;
123 }
124 $mb_char = mb_substr (substr ($this->text, $this->i, 6), 0, 1);
125 $this->i += strlen ($mb_char);
126 return $mb_char;
127 }
128
129 // Implements the lexer FSM.
0e69eafc 130 // Returns the next input token (or token class) or NULL.
f3685414
AA
131 // Fills $this->lex_value with the string value of a token.
132 const LEX_S_INIT = 0;
133 const LEX_S_COMMENT = 1;
134 const LEX_S_KEYWORD = 2;
135 const LEX_S_TAG = 3;
136 const LEX_S_PREDICATE = 4;
137 function get_token()
138 {
139 $state = self::LEX_S_INIT;
140 $buffer = '';
141 while ($this->i < $this->text_len) :
142 switch ($state) :
143 case self::LEX_S_INIT:
144 $char = $this->stop_on_char(" \t", $buffer, TRUE); // skip spaces
145 switch ($char)
578d9d02 146 {
f3685414
AA
147 case '(':
148 case ')':
149 $this->lex_value = $char;
150 return $char;
151 case '#':
152 $state = self::LEX_S_COMMENT;
153 break;
154 case "\n":
155 $this->lineno++;
156 // skip NL
157 break;
158 case '{':
159 $state = self::LEX_S_TAG;
160 $buffer = '';
161 break;
162 case '[':
163 $state = self::LEX_S_PREDICATE;
164 $buffer = '';
165 break;
166 case 'END':
167 break;
168 default:
169 if (preg_match ('/[\p{L}]/u', $char))
170 {
171 $state = self::LEX_S_KEYWORD;
172 $buffer = $char;
173 }
174 else
4b266fe7 175 throw new RCParserError ("Invalid char '$char'");
578d9d02 176 }
f3685414
AA
177 break;
178 case self::LEX_S_KEYWORD:
179 $char = $this->stop_on_char(") \t\n", $buffer); // collect keyword chars
180 switch ($char)
181 {
182 case ')':
183 case "\n":
184 case ' ':
185 case "\t":
186 $this->i--;
187 // fall-through
188 case 'END':
189 // got a word, sort it out
190 $this->lex_value = $buffer;
191 return $buffer;
192 default:
c47153c8 193 throw new RackTablesError ("Lex FSM error, state == ${state}, char == ${char}");
f3685414
AA
194 }
195 break;
196 case self::LEX_S_TAG:
197 case self::LEX_S_PREDICATE:
198 $tag_mode = ($state == self::LEX_S_TAG);
199 $breaking_char = $tag_mode ? '}' : ']';
200 $char = $this->stop_on_char ("$breaking_char\n", $buffer); // collect tagname chars
201 switch ($char)
202 {
203 case $breaking_char:
204 $buffer = trim ($buffer, "\t ");
205 if (!validTagName ($buffer, $tag_mode))
4b266fe7 206 throw new RCParserError ("Invalid tag name '$buffer'");
f3685414
AA
207 $this->lex_value = $buffer;
208 return $tag_mode ? 'LEX_TAG' : 'LEX_PREDICATE';
209 case "\n":
210 case 'END':
4b266fe7 211 throw new RCParserError ("Expecting '$breaking_char' character");
f3685414 212 default:
c47153c8 213 throw new RackTablesError ("Lex FSM error, state == ${state}, char == ${char}");
f3685414
AA
214 }
215 break;
216 case self::LEX_S_COMMENT:
217 $char = $this->stop_on_char ("\n", $buffer); // collect tagname chars
218 switch ($char)
219 {
220 case "\n":
221 $this->lineno++;
222 // fall-through
223 case 'END':
224 $state = self::LEX_S_INIT;
225 $buffer = '';
226 break;
227 default:
c47153c8 228 throw new RackTablesError ("Lex FSM error, state == ${state}, char == ${char}");
f3685414
AA
229 }
230 break;
231 default:
c47153c8 232 throw new RackTablesError ("Lex FSM error, state == ${state}");
f3685414
AA
233 endswitch;
234 endwhile;
235 return NULL;
236 }
237
238 ##########################################
239 # RackCode syntax functions below
240 ##########################################
241
242 // PROG:
243 // | PROG STATEMENT
244 function prog()
245 {
246 $this->prog_mode = TRUE;
871b0412 247 $statements = array();
f3685414
AA
248 while (NULL !== $this->token)
249 $statements[] = $this->statement();
250 return $statements;
251 }
252
253 // STATEMENT: GRANT
254 // | define PRED EXPR
255 // | context CTXMODLIST on EXPR
256 // GRANT : allow EXPR
257 // | deny EXPR
258 function statement()
259 {
260 switch ($this->token)
261 {
262 case 'allow':
263 case 'deny':
264 $lineno = $this->lineno;
265 $decision = $this->expect (array('allow', 'deny'));
266 return array(
267 'type' => 'SYNT_GRANT',
268 'condition' => $this->expr(),
5225db53 269 'decision' => ($decision == 'allow' ? TRUE : FALSE),
f3685414 270 'lineno' => $lineno,
a1b88eca 271 );
f3685414
AA
272 case 'define':
273 $lineno = $this->lineno;
274 $this->expect ('define');
275 $pred_name = $this->expect ('LEX_PREDICATE');
276 if (isset ($this->defined_preds[$pred_name]))
4b266fe7 277 throw new RCParserError ("Duplicate definition of [$pred_name]");
f3685414
AA
278 $ret = array(
279 'type' => 'SYNT_DEFINITION',
280 'term' => $pred_name,
281 'definition' => $this->expr(),
282 'lineno' => $lineno,
a1b88eca 283 );
f3685414
AA
284 $this->defined_preds[$pred_name] = 1;
285 return $ret;
286 case 'context':
287 $lineno = $this->lineno;
288 $this->expect ('context');
289 $modlist = $this->ctxmodlist();
290 $this->expect ('on');
291 return array(
292 'type' => 'SYNT_ADJUSTMENT',
293 'modlist' => $modlist,
294 'condition' => $this->expr(),
295 'lineno' => $lineno,
521a51a2 296 );
f3685414 297 default:
4b266fe7 298 throw new RCParserError ("unexpected {$this->token}");
f3685414
AA
299 }
300 }
301
302 // CTXMODLIST: CTXMODLIST CTXMOD
303 // | CTXMOD
304 // CTXMOD : 'clear'
305 // | 'insert' TAG
306 // | 'remove' TAG
307 function ctxmodlist()
308 {
309 $list = array();
310 while (TRUE)
311 {
312 $lineno = $this->lineno;
313 if ($op = $this->accept (array('insert', 'remove')))
314 $list[] = array(
315 'op' => $op,
316 'tag' => $this->expect ('LEX_TAG'),
317 'lineno' => $lineno,
521a51a2 318 );
f3685414
AA
319 elseif ($this->accept ('clear'))
320 $list[] = array(
321 'op' => 'clear',
322 'lineno' => $lineno,
a1b88eca 323 );
f3685414 324 elseif (! $list) // we need at least one CTXMOD
4b266fe7 325 throw new RCParserError ("expecting CTXMOD");
f3685414
AA
326 else
327 break;
328 }
329 return $list;
330 }
331
332 // EXPR : AND_EXPR
333 // | AND_EXPR 'or' EXPR
334 // AND_EXPR: UN_EXPR
335 // | UN_EXPR 'and' AND_EXPR
336 function expr ($op = 'or')
337 {
338 switch ($op)
339 {
340 case 'or':
341 $type = 'SYNT_EXPR';
342 $left = $this->expr ('and');
343 break;
344 case 'and':
345 $type = 'SYNT_AND_EXPR';
346 $left = $this->un_expr();
347 break;
348 default:
c47153c8 349 throw new InvalidArgException ('op', $op);
f3685414
AA
350 }
351
352 if ($this->accept ($op))
353 {
354 $tag_args = array();
355 $tag_lines = array();
356 $expr_args = array();
357 if ($left['type'] == 'LEX_TAG')
1381b655 358 {
f3685414
AA
359 $tag_args[] = $left['load'];
360 $tag_lines[] = $left['lineno'];
1381b655 361 }
f3685414
AA
362 else
363 $expr_args[] = $left;
364
365 $right = $this->expr ($op);
366 if ($right['type'] == $type)
1381b655 367 {
f3685414
AA
368 $tag_args = array_merge ($tag_args, $right['tag_args']);
369 $tag_lines = array_merge ($tag_lines, $right['tag_lines']);
370 $expr_args = array_merge ($expr_args, $right['expr_args']);
1381b655 371 }
f3685414 372 elseif ($right['type'] == 'LEX_TAG')
578d9d02 373 {
f3685414
AA
374 $tag_args[] = $right['load'];
375 $tag_lines[] = $right['lineno'];
578d9d02 376 }
f3685414 377 elseif ($right['type'] == 'LEX_BOOL')
1381b655 378 {
f3685414
AA
379 if ($right['load'] == ($op == 'or'))
380 {
381 $right['lineno'] = $left['lineno'];
382 return $right;
383 }
1381b655 384 }
f3685414
AA
385 else
386 $expr_args[] = $right;
387
388 return array(
389 'type' => $type,
390 'tag_args' => $tag_args,
391 'expr_args' => $expr_args,
392 'lineno' => $left['lineno'],
393 'tag_lines' => $tag_lines,
394 );
395 }
396 return $left;
397 }
398
399 // UN_EXPR: '(' EXPR ')'
400 // | 'not' UN_EXPR
401 // | BOOL
402 // | PRED
403 // | TAG
404 function un_expr()
405 {
406 $lineno = $this->lineno;
407 if ($this->accept ('('))
408 {
409 $expr = $this->expr();
410 $this->expect (')');
411 return $expr;
412 }
413 elseif ($this->accept ('not'))
414 {
415 $expr = $this->un_expr();
416 if ($expr['type'] == 'LEX_BOOL')
1381b655 417 {
f3685414
AA
418 $expr['load'] = ! $expr['load'];
419 return $expr;
1381b655 420 }
f3685414
AA
421 return array(
422 'type' => 'SYNT_NOT_EXPR',
423 'load' => $expr,
424 'lineno' => $lineno,
425 );
1381b655 426 }
f3685414
AA
427 elseif ($k = $this->accept (array('true', 'false')))
428 return array(
429 'type' => 'LEX_BOOL',
430 'load' => $k == 'true',
431 'lineno' => $lineno,
432 );
433 elseif ($k = $this->accept ('LEX_PREDICATE'))
1381b655 434 {
55eefced 435 if ($this->prog_mode && ! isset ($this->defined_preds[$k]))
4b266fe7 436 throw new RCParserError ("Undefined predicate [$k] refered");
f3685414
AA
437 return array(
438 'type' => 'LEX_PREDICATE',
439 'load' => $k,
440 'lineno' => $lineno,
441 );
a1b88eca 442 }
f3685414
AA
443 elseif ($k = $this->accept ('LEX_TAG'))
444 return array(
445 'type' => 'LEX_TAG',
446 'load' => $k,
447 'lineno' => $lineno,
448 );
4b266fe7 449 throw new RCParserError ("Unexpected token {$this->token}");
a1b88eca 450 }
a1b88eca
DO
451}
452
061452b9
DO
453function refRCLineno ($ln)
454{
5ceaa179 455 return "<a href='index.php?page=perms&tab=default&line=${ln}'>line ${ln}</a>";
061452b9
DO
456}
457
43c222ab
AA
458// returns warning message or NULL
459function checkAutotagName ($atag_name)
15555995 460{
b904a8e5 461 global $user_defined_atags;
18d14e8e
AA
462 static $entityIDs = array // $Xid_YY auto tags
463 (
464 '' => array ('object', 'Object'),
465 'ipv4net' => array ('ipv4net', 'IPv4 network'),
466 'ipv6net' => array ('ipv6net', 'IPv6 network'),
467 'rack' => array ('rack', 'Rack'),
468 'row' => array ('row', 'Row'),
469 'location' => array ('location', 'Location'),
43c222ab 470 'ipvs' => array ('ipvs', 'Virtual service'),
18d14e8e
AA
471 'ipv4rsp' => array ('ipv4rspool', 'RS pool'),
472 'file' => array ('file', 'File'),
473 'vst' => array ('vst', '802.1Q template'),
474 'user' => array ('user', 'User'),
475 );
44d9fd5c
DO
476 // autotags that don't require a regexp to match
477 $simple_autotags = array
478 (
479 '$aggregate',
480 '$any_file',
481 '$any_ip4net',
482 '$any_ip6net',
483 '$any_ipv4rsp',
484 '$any_ipv4vs',
485 '$any_location',
486 '$any_net',
487 '$any_object',
488 '$any_op',
489 '$any_rack',
490 '$any_row',
491 '$any_rsp',
492 '$any_vs',
493 '$nameless',
494 '$no_asset_tag',
495 '$portless',
496 '$runs_8021Q',
497 '$type_mark',
498 '$type_tcp',
499 '$type_udp',
500 '$unmounted',
501 '$untagged',
502 '$unused',
503 );
43c222ab 504 switch (1)
15555995 505 {
43c222ab
AA
506 case (in_array ($atag_name, $simple_autotags)):
507 break;
508 case preg_match ('/^\$(.*)?id_(\d+)$/', $atag_name, $m) && isset ($entityIDs[$m[1]]):
509 list ($realm, $description) = $entityIDs[$m[1]];
510 $recid = $m[2];
511 try
15555995 512 {
43c222ab 513 spotEntity ($realm, $m[2]);
15555995 514 }
43c222ab
AA
515 catch (EntityNotFoundException $e)
516 {
517 return "$description with ID '${recid}' does not exist.";
518 }
519 break;
520 case (preg_match ('/^\$username_/', $atag_name)):
521 $recid = preg_replace ('/^\$username_/', '', $atag_name);
522 global $require_local_account;
55eefced 523 if ($require_local_account && NULL === getUserIDByUsername ($recid))
43c222ab
AA
524 return "Local user account '${recid}' does not exist.";
525 break;
526 case (preg_match ('/^\$page_([\p{L}0-9]+)$/u', $atag_name, $m)):
527 $recid = $m[1];
528 global $page;
529 if (! isset ($page[$recid]))
530 return "Page number '${recid}' does not exist.";
531 break;
532 case (preg_match ('/^\$(tab|op)_[\p{L}0-9_]+$/u', $atag_name)):
533 case (preg_match ('/^\$typeid_\d+$/', $atag_name)): // FIXME: check value validity
534 case (preg_match ('/^\$cn_.+$/', $atag_name)): // FIXME: check name validity and asset existence
535 case (preg_match ('/^\$lgcn_.+$/', $atag_name)): // FIXME: check name validity
536 case (preg_match ('/^\$(vlan|fromvlan|tovlan)_\d+$/', $atag_name)):
537 case (preg_match ('/^\$(masklen_eq|spare)_\d{1,3}$/', $atag_name)):
538 case (preg_match ('/^\$attr_\d+(_\d+)?$/', $atag_name)):
539 case (preg_match ('/^\$ip4net(-\d{1,3}){5}$/', $atag_name)):
540 case (preg_match ('/^\$(8021Q_domain|8021Q_tpl)_\d+$/', $atag_name)):
541 case (preg_match ('/^\$client_([0-9a-fA-F.:]+)$/', $atag_name)):
542 break;
15555995 543 default:
43c222ab
AA
544 foreach ($user_defined_atags as $regexp)
545 if (preg_match ($regexp, $atag_name))
546 break 2;
547 return "Martian autotag {{$atag_name}}.";
1a4096cb
DO
548 }
549}
550
1a4096cb
DO
551function findTagWarnings ($expr)
552{
e332b979 553 $self = __FUNCTION__;
1a4096cb
DO
554 switch ($expr['type'])
555 {
43c222ab 556 case 'LEX_BOOL':
1a4096cb
DO
557 case 'LEX_PREDICATE':
558 return array();
559 case 'LEX_TAG':
43c222ab
AA
560 $tag = $expr['load'];
561 if ('$' === substr ($tag, 0, 1))
562 {
563 if (NULL !== $msg = checkAutotagName ($tag))
564 return array (array
565 (
566 'header' => refRCLineno ($expr['lineno']),
567 'class' => 'warning',
568 'text' => $msg
569 ));
570 }
571 elseif (getTagByName ($tag) === NULL)
572 return array (array
573 (
574 'header' => refRCLineno ($expr['lineno']),
575 'class' => 'warning',
576 'text' => "Tag {{$tag}} does not exist."
577 ));
578 return array();
521a51a2 579 case 'SYNT_NOT_EXPR':
e332b979 580 return $self ($expr['load']);
99148244
DO
581 case 'SYNT_AND_EXPR':
582 case 'SYNT_EXPR':
43c222ab
AA
583 $ret = array();
584 foreach ($expr['tag_args'] as $i => $tag)
585 if ('$' === substr ($tag, 0, 1))
586 {
587 if (NULL !== $msg = checkAutotagName ($tag))
588 $ret[] = array
589 (
590 'header' => refRCLineno ($expr['tag_lines'][$i]),
591 'class' => 'warning',
592 'text' => $msg
593 );
594 }
595 elseif (getTagByName ($tag) === NULL)
596 $ret[] = array
597 (
598 'header' => refRCLineno ($expr['tag_lines'][$i]),
599 'class' => 'warning',
600 'text' => "Tag {{$tag}} does not exist."
601 );
602 foreach ($expr['expr_args'] as $sub)
603 $ret = array_merge ($ret, $self ($sub));
604 return $ret;
1a4096cb
DO
605 default:
606 return array (array
607 (
99148244 608 'header' => "internal error in ${self}",
1a4096cb
DO
609 'class' => 'error',
610 'text' => "Skipped expression of unknown type '${expr['type']}'"
15555995
DO
611 ));
612 }
613}
614
75e7c0c6 615// Check context modifiers, warn about those that try referencing non-existent tags.
d94022fe
DO
616function findCtxModWarnings ($modlist)
617{
618 $ret = array();
619 foreach ($modlist as $mod)
55eefced 620 if (($mod['op'] == 'insert' || $mod['op'] == 'remove') && NULL === getTagByName ($mod['tag']))
d94022fe
DO
621 $ret[] = array
622 (
623 'header' => refRCLineno ($mod['lineno']),
624 'class' => 'warning',
43c222ab 625 'text' => "Tag {{$mod['tag']}} does not exist."
d94022fe
DO
626 );
627 return $ret;
628}
629
43c222ab
AA
630// Return list of used predicates
631function getReferencedPredicates ($expr)
52837ce6 632{
e332b979 633 $self = __FUNCTION__;
52837ce6
DO
634 switch ($expr['type'])
635 {
43c222ab 636 case 'LEX_BOOL':
52837ce6 637 case 'LEX_TAG':
43c222ab 638 return array();
52837ce6 639 case 'LEX_PREDICATE':
43c222ab 640 return array ($expr['load'] => $expr['load']);
521a51a2 641 case 'SYNT_NOT_EXPR':
43c222ab 642 return $self ($expr['load']);
99148244
DO
643 case 'SYNT_AND_EXPR':
644 case 'SYNT_EXPR':
43c222ab
AA
645 $ret = array();
646 foreach ($expr['expr_args'] as $sub)
647 $ret += $self ($sub);
648 return $ret;
649 default:
650 throw new RackTablesError ("Unknown expr type {$expr['type']}");
52837ce6
DO
651 }
652}
653
d05ed211
DO
654// Return 'always true', 'always false' or any other verdict.
655function invariantExpression ($expr)
656{
657 $self = __FUNCTION__;
658 switch ($expr['type'])
659 {
43c222ab
AA
660 case 'LEX_BOOL':
661 return $expr['load'];
521a51a2 662 case 'SYNT_NOT_EXPR':
43c222ab
AA
663 $ret = $self ($expr['load']);
664 if (isset ($ret))
665 return ! $ret;
666 break;
99148244 667 case 'SYNT_AND_EXPR':
99148244 668 case 'SYNT_EXPR':
43c222ab
AA
669 $stop_on = $expr['type'] == 'SYNT_EXPR';
670 foreach ($expr['expr_args'] as $sub_expr)
671 if ($stop_on === $self ($sub_expr))
672 return $stop_on;
d05ed211
DO
673 break;
674 }
675}
676
e88d6e1e
AA
677function getRackCodeStats ()
678{
43c222ab
AA
679 global $rackCode, $pTable;
680 $grantc = $modc = 0;
e88d6e1e
AA
681 foreach ($rackCode as $s)
682 switch ($s['type'])
683 {
e88d6e1e
AA
684 case 'SYNT_GRANT':
685 $grantc++;
686 break;
43c222ab 687 case 'SYNT_ADJUSTMENT':
e88d6e1e
AA
688 $modc++;
689 break;
690 default:
691 break;
692 }
693 return array
694 (
43c222ab 695 'Definition sentences' => count ($pTable),
e88d6e1e
AA
696 'Grant sentences' => $grantc,
697 'Context mod sentences' => $modc
698 );
699}
700
701function getRackCodeWarnings ()
702{
703 $ret = array();
43c222ab
AA
704 global $rackCode, $pTable;
705
706 $seen_exprs = array();
707 foreach ($pTable as $pname => $expr)
708 $seen_exprs[] = $expr;
709
e88d6e1e
AA
710 foreach ($rackCode as $sentence)
711 switch ($sentence['type'])
712 {
e88d6e1e 713 case 'SYNT_ADJUSTMENT':
43c222ab 714 $seen_exprs[] = $sentence['condition'];
e88d6e1e
AA
715 $ret = array_merge ($ret, findCtxModWarnings ($sentence['modlist']));
716 break;
717 case 'SYNT_GRANT':
43c222ab 718 $seen_exprs[] = $sentence['condition'];
e88d6e1e
AA
719 break;
720 default:
721 $ret[] = array
722 (
723 'header' => 'internal error',
724 'class' => 'error',
725 'text' => "Skipped sentence of unknown type '${sentence['type']}'"
726 );
727 }
43c222ab
AA
728
729 $used_preds = array();
730 foreach ($seen_exprs as $expr)
e88d6e1e 731 {
43c222ab
AA
732 $ret = array_merge ($ret, findTagWarnings ($expr));
733 $used_preds += getReferencedPredicates ($expr);
734 if (NULL !== $const = invariantExpression ($expr))
735 $ret[] = array
736 (
737 'header' => refRCLineno ($sentence['lineno']),
738 'class' => 'warning',
739 'text' => sprintf ("Expression is always %s.", $const ? "true" : "false")
740 );
e88d6e1e 741 }
43c222ab
AA
742
743 foreach ($pTable as $pname => $pexpr)
744 if (! isset ($used_preds[$pname]))
745 $ret[] = array
746 (
747 'header' => refRCLineno ($pexpr['lineno']),
748 'class' => 'neutral',
749 'text' => "Predicate [{$pname}] is defined, but never used."
750 );
751
e88d6e1e
AA
752 // bail out
753 $nwarnings = count ($ret);
754 $ret[] = array
755 (
756 'header' => 'summary',
757 'class' => $nwarnings ? 'error' : 'success',
758 'text' => "Analysis complete, ${nwarnings} issues discovered."
759 );
760 return $ret;
761}
762
f63072f5 763?>