7 #define YYSTYPE value_t *
9 #include "iwhd-qparser.h"
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). */
23 /* Allocate N bytes of memory dynamically, with error checking. */
33 /* Change the size of an allocated block of memory P to N bytes,
34 with error checking. */
36 xrealloc
(void *p
, size_t n
)
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. */
48 xmemdup
(void const *p
, size_t s
)
50 return memcpy
(xmalloc
(s
), p
, s
);
55 xstrdup
(char const *string)
57 return xmemdup
(string, strlen
(string) + 1);
60 /* TBD: use separate function to parse dates differently */
62 make_number
(const char *text
)
64 value_t
*tmp
= malloc
(sizeof
(*tmp
));
68 tmp
->as_num
= strtoll
(text
,NULL
,10);
76 make_string
(const char *text
, type_t t
)
78 value_t
*tmp
= malloc
(sizeof
(*tmp
));
82 tmp
->as_str
= xstrdup
(text
);
90 make_tree
(type_t t
, value_t
*left
, value_t
*right
)
92 value_t
*tmp
= malloc
(sizeof
(*tmp
));
96 tmp
->as_tree.left
= left
;
97 tmp
->as_tree.right
= right
;
105 make_comp
(comp_t c
, value_t
*left
, value_t
*right
)
107 value_t
*tmp
= make_tree
(T_COMP
,left
,right
);
117 make_link
(value_t
*left
, char *right
)
121 copy
= xstrdup
(right
);
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.
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;
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
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
177 printf
("bad policy!\n");
186 // printf("promoting ubool_expr to bbool_expr\n");
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);
199 }| T_SPACE bbool_expr
{
205 // printf("promoting comp_expr to ubool_expr\n");
209 // printf("found NOT expression\n");
210 $$
= make_tree
(T_NOT
,$2,NULL
);
214 }| T_SPACE ubool_expr
{
221 // printf("promoting atom to comp_expr\n");
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);
250 }| T_SPACE comp_expr
{
256 // printf("promoting link_field to atom\n");
260 // printf("promoting literal to atom\n");
264 // printf("promoting paren_expr to atom\n");
275 // printf("promoting field to link_field\n");
278 link_field T_DOT T_ID
{
279 // printf("found LINK FIELD\n");
280 $$
= make_link
($1,yytext
);
285 // printf("found DOLLAR FIELD\n");
286 $$
= make_string
(yytext
,T_OFIELD
);
289 // printf("found WAFFLE FIELD\n");
290 $$
= make_string
(yytext
,T_SFIELD
);
295 // printf("found NUMBER %s\n",yytext);
296 $$
= make_number
(yytext
);
299 // printf("found STRING %s\n",yytext);
300 $$
= make_string
(yytext
,T_STRING
);
303 // printf("found DATE\n");
304 $$
= make_string
(yytext
,T_DATE
);
307 // printf("found ID %s\n",yytext);
308 $$
= make_string
(yytext
,T_ID
);
312 T_LPAREN bbool_expr T_RPAREN
{
313 // printf("found PAREN expression\n");
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" },
328 /* Fake out the eval code for unit testing. */
330 unit_oget_func
(void * notused
, const char *text
)
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
);
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.
349 unit_sget_func
(void * notused
, const char *text
)
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" },
363 follow_link
(const char *object
, const char *key
)
367 for
(i
= 0; hacked_links
[i
].obj
; ++i
) {
368 if
(strcmp
(object
,hacked_links
[i
].obj
)) {
371 if
(strcmp
(key
,hacked_links
[i
].key
)) {
374 return hacked_links
[i
].value
;
380 extern
char *follow_link
(const char *object
, const char *key
);
384 _print_value
(const value_t
*v
, int level
)
387 printf
("%*sNULL\n",level
,"");
393 printf
("%*sNUMBER %lld\n",level
,"",v
->as_num
);
396 printf
("%*sSTRING %s\n",level
,"",v
->as_str
);
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
));
403 printf
("%*sOBJECT FIELD %s\n",level
,"",v
->as_str
);
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
));
411 printf
("%*sSERVER FIELD %s\n",level
,"",v
->as_str
);
415 printf
("%*sCOMPARISON\n",level
,"");
416 _print_value
(v
->as_tree.left
,level
+2);
417 _print_value
(v
->as_tree.right
,level
+2);
420 printf
("%*sNOT\n",level
,"");
421 _print_value
(v
->as_tree.left
,level
+2);
424 printf
("%*sAND\n",level
,"");
425 _print_value
(v
->as_tree.left
,level
+2);
426 _print_value
(v
->as_tree.right
,level
+2);
429 printf
("%*sOR\n",level
,"");
430 _print_value
(v
->as_tree.left
,level
+2);
431 _print_value
(v
->as_tree.right
,level
+2);
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
);
440 printf
("%*sUNKNOWN %d\n",level
,"",v
->type
);
445 print_value
(const value_t
*v
)
451 free_value
(value_t
*v
)
458 printf
("freeing resolved string \"%s\" (%p)\n",
459 v
->resolved
, v
->resolved
);
472 free_value
(v
->as_tree.left
);
473 free
(v
->as_tree.right
);
479 free_value
(v
->as_tree.right
);
482 free_value
(v
->as_tree.left
);
492 parse
(const char *text
)
494 yy_scan_string
(text
);
496 value_t
*r
= yyparse (&result
) == 0 ? result
: NULL
;
502 * Return the string value of an expression for comparison or display, iff
503 * all component parts are string-valued themselves. That excludes numbers
507 string_value
(value_t
*v
, getter_t
*oget
, getter_t
*sget
)
516 v
->resolved
= oget ? CALL_GETTER
(oget
,v
->as_str
) : NULL
;
520 return sget ? CALL_GETTER
(sget
,v
->as_str
) : NULL
;
522 left
= string_value
(v
->as_tree.left
,oget
,sget
);
524 return follow_link
((char *)left
,
525 (char *)v
->as_tree.right
);
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
)
546 for
(p
= a_str
; *p
; ++p
) {
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
)
569 int lval
= 0; // solely to placate gcc
573 lstr
= string_value
(left
,oget
,sget
);
574 rstr
= string_value
(right
,oget
,sget
);
576 if
(left
->type
== T_NUMBER
) {
580 if
(is_ok_number
(lstr
)) {
581 lval
= strtoll
(lstr
,NULL
,0);
588 lval
= eval
(left
,oget
,sget
);
594 if
(right
->type
== T_NUMBER
) {
595 rval
= right
->as_num
;
598 if
(is_ok_number
(rstr
)) {
599 rval
= strtoll
(rstr
,NULL
,0);
606 rval
= eval
(right
,oget
,sget
);
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.
620 if
(!lstr ||
!rstr
) {
623 lval
= strcmp
(lstr
,rstr
);
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
);
640 * Evaluate an AST in the current context to one of:
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
)
659 return v
->as_num
!= 0;
661 return v
->as_str
&& *v
->as_str
;
663 str
= CALL_GETTER
(oget
,v
->as_str
);
666 str
= CALL_GETTER
(sget
,v
->as_str
);
669 str
= string_value
(v
->as_tree.left
,oget
,sget
);
671 str
= follow_link
(str
,(char *)v
->as_tree.right
);
675 return compare
(v
->as_tree.left
,(comp_t
)v
->as_tree.op
,
676 v
->as_tree.right
, oget
, sget
);
678 res
= eval
(v
->as_tree.left
,oget
,sget
);
679 return
(res
>= 0) ?
!res
: res
;
681 res
= eval
(v
->as_tree.left
,oget
,sget
);
683 res
= eval
(v
->as_tree.right
,oget
,sget
);
687 res
= eval
(v
->as_tree.left
,oget
,sget
);
691 return eval
(v
->as_tree.right
,oget
,sget
);
697 #ifdef PARSER_UNIT_TEST
699 main
(int argc
, char **argv
)
703 for
(i
= 1; i
< argc
; ++i
)
705 value_t
*expr
= parse
(argv
[i
]);
708 printf
("could not parse '%s'\n", argv
[i
]);
715 const char *str
= string_value
(expr
, &unit_oget
, &unit_sget
);
718 printf
("s= %s\n", str
);
721 printf
("d= %d\n", eval
(expr
, &unit_oget
, &unit_sget
));