apply jeffs leak-fixing patch from master (manually merge 3 failed hunks)
[iwhd.git] / qparser.y
blobd028098e82299bdaa0ffe4ae7ad6799357b5d669
1 %{
2 #include <config.h>
3 #include <error.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #define YYSTYPE value_t *
8 #include "query.h"
9 #include "iwhd-qparser.h"
11 static void
12 xalloc_die (void)
14 error (EXIT_FAILURE, 0, "%s", "memory exhausted");
16 /* The `noreturn' cannot be given to error, since it may return if
17 its first argument is 0. To help compilers understand the
18 xalloc_die does not return, call abort. Also, the abort is a
19 safety feature if exit_failure is 0 (which shouldn't happen). */
20 abort ();
23 /* Allocate N bytes of memory dynamically, with error checking. */
24 static void *
25 xmalloc (size_t n)
27 void *p = malloc (n);
28 if (!p && n != 0)
29 xalloc_die ();
30 return p;
33 /* Change the size of an allocated block of memory P to N bytes,
34 with error checking. */
35 static void *
36 xrealloc (void *p, size_t n)
38 p = realloc (p, n);
39 if (!p && n != 0)
40 xalloc_die ();
41 return p;
44 /* Clone an object P of size S, with error checking. There's no need
45 for xnmemdup (P, N, S), since xmemdup (P, N * S) works without any
46 need for an arithmetic overflow check. */
47 static void *
48 xmemdup (void const *p, size_t s)
50 return memcpy (xmalloc (s), p, s);
53 /* Clone STRING. */
54 static char *
55 xstrdup (char const *string)
57 return xmemdup (string, strlen (string) + 1);
60 /* TBD: use separate function to parse dates differently */
61 value_t *
62 make_number (const char *text)
64 value_t *tmp = malloc(sizeof(*tmp));
66 if (tmp) {
67 tmp->type = T_NUMBER;
68 tmp->as_num = strtoll(text,NULL,10);
69 tmp->resolved = NULL;
72 return tmp;
75 value_t *
76 make_string (const char *text, type_t t)
78 value_t *tmp = malloc(sizeof(*tmp));
80 if (tmp) {
81 tmp->type = t;
82 tmp->as_str = xstrdup(text);
83 tmp->resolved = NULL;
86 return tmp;
89 value_t *
90 make_tree (type_t t, value_t *left, value_t *right)
92 value_t *tmp = malloc(sizeof(*tmp));
94 if (tmp) {
95 tmp->type = t;
96 tmp->as_tree.left = left;
97 tmp->as_tree.right = right;
98 tmp->resolved = NULL;
101 return tmp;
104 value_t *
105 make_comp (comp_t c, value_t *left, value_t *right)
107 value_t *tmp = make_tree(T_COMP,left,right);
109 if (tmp) {
110 tmp->as_tree.op = c;
113 return tmp;
116 value_t *
117 make_link (value_t *left, char *right)
119 char *copy;
121 copy = xstrdup(right);
122 if (!copy) {
123 return NULL;
126 return make_tree(T_LINK,left,(value_t *)copy);
130 * For some reason the bison-generated code isn't setting up yysv* properly,
131 * so $n doesn't work with terminals. Be very careful to use this only
132 * when the token we want is the last one in the current rule. If the
133 * syntax ever gets complicated enough that we can't get away with that, we'll
134 * just have to wrap all the terminals in singleton non-terminals just so that
135 * $n will work in the real syntax rules.
137 extern char *yytext;
140 * IMO it's wrong for us to get into the bbool_expr=policy rule when there's
141 * a syntax error, but we do. The good news is that it's easy to free the
142 * erroneous tree properly this way. The bad news is that we need to wait
143 * until yyparse is done, then check this flag (which we have to maintain
144 * ourselves) to figure out whether we got a valid tree or not.
145 * No, yynerrs doesn't seem to give the right answer.
147 static int syntax_error = 0;
149 void
150 yyerror (value_t **result, const char *msg)
152 // error (0, 0, "parse error: %s\n", msg);
153 // FIXME do this via param, not file-global
154 syntax_error = 1;
159 %define api.pure
160 %parse-param { value_t **result }
162 %token T_STRING T_DATE T_NUMBER T_ID
163 %token T_DOLLAR T_WAFFLE T_DOT
164 %token T_LPAREN T_RPAREN
165 %token T_LESS T_GREATER T_EQUAL
166 %token T_NOT T_AND T_OR
167 %token T_SPACE T_INVALID
168 %token T_OFIELD T_SFIELD T_COMP T_LINK
170 %start policy
174 policy:
175 bbool_expr {
176 if (syntax_error) {
177 printf("bad policy!\n");
179 else {
180 *result = $1;
184 bbool_expr:
185 ubool_expr {
186 // printf("promoting ubool_expr to bbool_expr\n");
187 $$ = $1;
189 bbool_expr T_AND T_AND ubool_expr {
190 // printf("found AND expression\n");
191 $$ = make_tree(T_AND,$1,$4);
193 bbool_expr T_OR T_OR ubool_expr {
194 // printf("found OR expression\n");
195 $$ = make_tree(T_OR,$1,$4);
197 bbool_expr T_SPACE {
198 $$ = $1;
199 }| T_SPACE bbool_expr {
200 $$ = $2;
203 ubool_expr:
204 comp_expr {
205 // printf("promoting comp_expr to ubool_expr\n");
206 $$ = $1;
208 T_NOT comp_expr {
209 // printf("found NOT expression\n");
210 $$ = make_tree(T_NOT,$2,NULL);
212 ubool_expr T_SPACE {
213 $$ = $1;
214 }| T_SPACE ubool_expr {
215 $$ = $2;
219 comp_expr:
220 atom {
221 // printf("promoting atom to comp_expr\n");
222 $$ = $1;
224 atom T_LESS atom {
225 // printf("found LESS THAN expression\n");
226 $$ = make_comp(C_LESSTHAN,$1,$3);
228 atom T_LESS T_EQUAL atom {
229 // printf("found LESS OR EQUAL expression\n");
230 $$ = make_comp(C_LESSOREQ,$1,$4);
232 atom T_EQUAL T_EQUAL atom {
233 // printf("found EQUAL expression\n");
234 $$ = make_comp(C_EQUAL,$1,$4);
236 atom T_NOT T_EQUAL atom {
237 // printf("found NOT EQUAL expression\n");
238 $$ = make_comp(C_DIFFERENT,$1,$4);
240 atom T_GREATER T_EQUAL atom {
241 // printf("found GREATER OR EQUAL expression\n");
242 $$ = make_comp(C_GREATEROREQ,$1,$4);
244 atom T_GREATER atom {
245 // printf("found GREATER THAN expression\n");
246 $$ = make_comp(C_GREATERTHAN,$1,$3);
248 comp_expr T_SPACE {
249 $$ = $1;
250 }| T_SPACE comp_expr {
251 $$ = $2;
254 atom:
255 link_field {
256 // printf("promoting link_field to atom\n");
257 $$ = $1;
259 literal {
260 // printf("promoting literal to atom\n");
261 $$ = $1;
263 paren_expr {
264 // printf("promoting paren_expr to atom\n");
265 $$ = $1;
267 atom T_SPACE {
268 $$ = $1;
269 }| T_SPACE atom {
270 $$ = $2;
273 link_field:
274 field {
275 // printf("promoting field to link_field\n");
276 $$ = $1;
278 link_field T_DOT T_ID {
279 // printf("found LINK FIELD\n");
280 $$ = make_link($1,yytext);
283 field:
284 T_DOLLAR T_ID {
285 // printf("found DOLLAR FIELD\n");
286 $$ = make_string(yytext,T_OFIELD);
288 T_WAFFLE T_ID {
289 // printf("found WAFFLE FIELD\n");
290 $$ = make_string(yytext,T_SFIELD);
293 literal:
294 T_NUMBER {
295 // printf("found NUMBER %s\n",yytext);
296 $$ = make_number(yytext);
298 T_STRING {
299 // printf("found STRING %s\n",yytext);
300 $$ = make_string(yytext,T_STRING);
302 T_DATE {
303 // printf("found DATE\n");
304 $$ = make_string(yytext,T_DATE);
306 T_ID {
307 // printf("found ID %s\n",yytext);
308 $$ = make_string(yytext,T_ID);
311 paren_expr:
312 T_LPAREN bbool_expr T_RPAREN {
313 // printf("found PAREN expression\n");
314 $$ = $2;
319 #if defined PARSER_UNIT_TEST
320 struct { char *name; char *value; } hacked_obj_fields[] = {
321 /* Fake object fields for generic unit testing. */
322 { "a", "2" }, { "b", "7" }, { "c", "11" },
323 /* This one's here to test links (e.g. $template.owner.name). */
324 { "template", "templates/the_tmpl" },
325 { NULL }
328 /* Fake out the eval code for unit testing. */
329 const char *
330 unit_oget_func (void * notused, const char *text)
332 int i;
334 for (i = 0; hacked_obj_fields[i].name; ++i) {
335 if (!strcmp(hacked_obj_fields[i].name,text)) {
336 return xstrdup(hacked_obj_fields[i].value);
340 return NULL;
342 getter_t unit_oget = { unit_oget_func };
345 * Same as above, but the site-field stuff is so similar to the object-field
346 * stuff that it's not worth exercising too much separately.
348 const char *
349 unit_sget_func (void * notused, const char *text)
351 return "never";
353 getter_t unit_sget = { unit_sget_func };
355 /* Fake links from an object/key tuple to an object/key string. */
356 struct { char *obj; char *key; char *value; } hacked_links[] = {
357 { "templates/the_tmpl", "owner", "users/the_user" },
358 { "users/the_user", "name", "Jeff Darcy" },
359 { NULL }
362 char *
363 follow_link (const char *object, const char *key)
365 unsigned int i;
367 for (i = 0; hacked_links[i].obj; ++i) {
368 if (strcmp(object,hacked_links[i].obj)) {
369 continue;
371 if (strcmp(key,hacked_links[i].key)) {
372 continue;
374 return hacked_links[i].value;
377 return NULL;
379 #else
380 extern char *follow_link (const char *object, const char *key);
381 #endif
383 void
384 _print_value (const value_t *v, int level)
386 if (!v) {
387 printf("%*sNULL\n",level,"");
388 return;
391 switch (v->type) {
392 case T_NUMBER:
393 printf("%*sNUMBER %lld\n",level,"",v->as_num);
394 break;
395 case T_STRING:
396 printf("%*sSTRING %s\n",level,"",v->as_str);
397 break;
398 case T_OFIELD:
399 #if defined PARSER_UNIT_TEST
400 printf("%*sOBJECT FIELD %s (%s)\n",level,"",v->as_str,
401 unit_oget_func(NULL,v->as_str));
402 #else
403 printf("%*sOBJECT FIELD %s\n",level,"",v->as_str);
404 #endif
405 break;
406 case T_SFIELD:
407 #if defined PARSER_UNIT_TEST
408 printf("%*sSERVER FIELD %s (%s)\n",level,"",v->as_str,
409 unit_sget_func(NULL,v->as_str));
410 #else
411 printf("%*sSERVER FIELD %s\n",level,"",v->as_str);
412 #endif
413 break;
414 case T_COMP:
415 printf("%*sCOMPARISON\n",level,"");
416 _print_value(v->as_tree.left,level+2);
417 _print_value(v->as_tree.right,level+2);
418 break;
419 case T_NOT:
420 printf("%*sNOT\n",level,"");
421 _print_value(v->as_tree.left,level+2);
422 break;
423 case T_AND:
424 printf("%*sAND\n",level,"");
425 _print_value(v->as_tree.left,level+2);
426 _print_value(v->as_tree.right,level+2);
427 break;
428 case T_OR:
429 printf("%*sOR\n",level,"");
430 _print_value(v->as_tree.left,level+2);
431 _print_value(v->as_tree.right,level+2);
432 break;
433 case T_LINK:
434 printf("%*sLINK\n",level,"");
435 _print_value(v->as_tree.left,level+2);
436 printf("%*sDEST FIELD %s\n",level+2,"",
437 (char *)v->as_tree.right);
438 break;
439 default:
440 printf("%*sUNKNOWN %d\n",level,"",v->type);
444 void
445 print_value (const value_t *v)
447 _print_value(v,0);
450 void
451 free_value (value_t *v)
453 if (!v) {
454 return;
457 if (v->resolved) {
458 printf("freeing resolved string \"%s\" (%p)\n",
459 v->resolved, v->resolved);
461 free(v->resolved);
463 switch (v->type) {
464 case T_STRING:
465 case T_OFIELD:
466 case T_SFIELD:
467 case T_ID:
468 free(v->as_str);
469 free(v);
470 break;
471 case T_LINK:
472 free_value(v->as_tree.left);
473 free(v->as_tree.right);
474 free(v);
475 break;
476 case T_COMP:
477 case T_AND:
478 case T_OR:
479 free_value(v->as_tree.right);
480 /* Fall through. */
481 case T_NOT:
482 free_value(v->as_tree.left);
483 /* Fall through. */
484 default:
485 free(v);
489 #include "qlexer.c"
491 value_t *
492 parse (const char *text)
494 yy_scan_string(text);
495 value_t *result;
496 value_t *r = yyparse (&result) == 0 ? result : NULL;
497 yylex_destroy();
498 return r;
502 * Return the string value of an expression for comparison or display, iff
503 * all component parts are string-valued themselves. That excludes numbers
504 * and booleans.
506 static const char *
507 string_value (value_t *v, getter_t *oget, getter_t *sget)
509 const char *left;
511 switch (v->type) {
512 case T_STRING:
513 return v->as_str;
514 case T_OFIELD:
515 if (!v->resolved) {
516 v->resolved = oget ? CALL_GETTER(oget,v->as_str) : NULL;
518 return v->resolved;
519 case T_SFIELD:
520 return sget ? CALL_GETTER(sget,v->as_str) : NULL;
521 case T_LINK:
522 left = string_value(v->as_tree.left,oget,sget);
523 if (left) {
524 return follow_link((char *)left,
525 (char *)v->as_tree.right);
527 /* Fall through. */
528 default:
529 return NULL;
534 * Check whether a string looks like a simple decimal number. There's
535 * probably a library function for this somewhere.
538 is_ok_number (const char *a_str)
540 const char *p;
542 if (!a_str) {
543 return 0;
546 for (p = a_str; *p; ++p) {
547 if (!isdigit(*p)) {
548 return 0;
552 return 1;
556 * Comparisons are a bit messy. If both sides are numbers, strings that look
557 * like numbers, or expressions that evaluate to numbers (booleans evaluate
558 * to 0/1), then we do a numeric comparison. Otherwise, if both sides
559 * evaluate to strings, we attempt a string comparison. That's the logic,
560 * but the code is actually structured a different way to allow re-use of
561 * common operator-specific code at the end for both cases.
564 compare (value_t *left, comp_t op, value_t *right,
565 getter_t *oget, getter_t *sget)
567 const char *lstr;
568 const char *rstr;
569 int lval = 0; // solely to placate gcc
570 int rval;
571 int num_ok = 1;
573 lstr = string_value(left,oget,sget);
574 rstr = string_value(right,oget,sget);
576 if (left->type == T_NUMBER) {
577 lval = left->as_num;
579 else if (lstr) {
580 if (is_ok_number(lstr)) {
581 lval = strtoll(lstr,NULL,0);
583 else {
584 num_ok = 0;
587 else {
588 lval = eval(left,oget,sget);
589 if (lval < 0) {
590 return lval;
594 if (right->type == T_NUMBER) {
595 rval = right->as_num;
597 else if (rstr) {
598 if (is_ok_number(rstr)) {
599 rval = strtoll(rstr,NULL,0);
601 else {
602 num_ok = 0;
605 else {
606 rval = eval(right,oget,sget);
607 if (rval < 0) {
608 return rval;
613 * Strcmp returns -1/0/1, but -1 for us would mean an error and
614 * which of 0/1 we return depends on which comparison operatoer
615 * we're dealing with. Therefore, we stick the strcmp result on
616 * the left side and let the switch below do an operator-appropriate
617 * compare against zero on the right.
619 if (!num_ok) {
620 if (!lstr || !rstr) {
621 return -1;
623 lval = strcmp(lstr,rstr);
624 rval = 0;
627 switch (op) {
628 case C_LESSTHAN: return (lval < rval);
629 case C_LESSOREQ: return (lval <= rval);
630 case C_EQUAL: return (lval == rval);
631 case C_DIFFERENT: return (lval != rval);
632 case C_GREATEROREQ: return (lval >= rval);
633 case C_GREATERTHAN: return (lval > rval);
634 default:
635 return -1;
640 * Evaluate an AST in the current context to one of:
641 * true=1
642 * false=0
643 * error=-1
644 * It's up to the caller whether error is functionally the same as false.
645 * Note that even T_NUMBER gets squeezed down to these three values. The
646 * only thing numbers are used for is comparing against other numbers to
647 * yield a boolean for the query or replication-policy code. If you want
648 * something that returns a number, this is the wrong language for it.
652 eval (const value_t *v, getter_t *oget, getter_t *sget)
654 int res;
655 const char *str;
657 switch (v->type) {
658 case T_NUMBER:
659 return v->as_num != 0;
660 case T_STRING:
661 return v->as_str && *v->as_str;
662 case T_OFIELD:
663 str = CALL_GETTER(oget,v->as_str);
664 return str && *str;
665 case T_SFIELD:
666 str = CALL_GETTER(sget,v->as_str);
667 return str && *str;
668 case T_LINK:
669 str = string_value(v->as_tree.left,oget,sget);
670 if (str) {
671 str = follow_link(str,(char *)v->as_tree.right);
673 return str && *str;
674 case T_COMP:
675 return compare(v->as_tree.left,(comp_t)v->as_tree.op,
676 v->as_tree.right, oget, sget);
677 case T_NOT:
678 res = eval(v->as_tree.left,oget,sget);
679 return (res >= 0) ? !res : res;
680 case T_AND:
681 res = eval(v->as_tree.left,oget,sget);
682 if (res > 0) {
683 res = eval(v->as_tree.right,oget,sget);
685 return res;
686 case T_OR:
687 res = eval(v->as_tree.left,oget,sget);
688 if (res > 0) {
689 return res;
691 return eval(v->as_tree.right,oget,sget);
692 default:
693 return -1;
697 #ifdef PARSER_UNIT_TEST
699 main (int argc, char **argv)
701 int fail = 0;
702 unsigned int i;
703 for (i = 1; i < argc; ++i)
705 value_t *expr = parse (argv[i]);
706 if (!expr)
708 printf ("could not parse '%s'\n", argv[i]);
709 fail = 1;
710 continue;
713 print_value (expr);
715 const char *str = string_value (expr, &unit_oget, &unit_sget);
716 if (str)
718 printf ("s= %s\n", str);
719 continue;
721 printf ("d= %d\n", eval (expr, &unit_oget, &unit_sget));
724 return fail;
726 #endif