2 /* Copyright (C) 2010 Red Hat, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
32 error (EXIT_FAILURE, 0, "%s", "memory exhausted");
34 /* The `noreturn' cannot be given to error, since it may return if
35 its first argument is 0. To help compilers understand the
36 xalloc_die does not return, call abort. Also, the abort is a
37 safety feature if exit_failure is 0 (which shouldn't happen). */
41 /* Allocate N bytes of memory dynamically, with error checking. */
51 /* Change the size of an allocated block of memory P to N bytes,
52 with error checking. */
54 xrealloc (void *p, size_t n)
62 /* Clone an object P of size S, with error checking. There's no need
63 for xnmemdup (P, N, S), since xmemdup (P, N * S) works without any
64 need for an arithmetic overflow check. */
66 xmemdup (void const *p, size_t s)
68 return memcpy (xmalloc (s), p, s);
73 xstrdup (char const *string)
75 return xmemdup (string, strlen (string) + 1);
78 /* TBD: use separate function to parse dates differently */
80 make_number (const char *text)
82 value_t *tmp = malloc(sizeof(*tmp));
86 tmp->as_num = strtoll(text,NULL,10);
94 make_string (const char *text, type_t t)
96 value_t *tmp = malloc(sizeof(*tmp));
100 tmp->as_str = xstrdup(text);
101 tmp->resolved = NULL;
108 make_tree (type_t t, value_t *left, value_t *right)
110 value_t *tmp = malloc(sizeof(*tmp));
114 tmp->as_tree.left = left;
115 tmp->as_tree.right = right;
116 tmp->resolved = NULL;
123 make_comp (comp_t c, value_t *left, value_t *right)
125 value_t *tmp = make_tree(T_COMP,left,right);
135 make_link (value_t *left, value_t *right)
139 copy = xstrdup((char *)right);
144 return make_tree(T_LINK,left,(value_t *)copy);
147 #if defined(UNIT_TEST)
148 struct { char *name; char *value; } hacked_obj_fields[] = {
149 /* Fake object fields for generic unit testing. */
150 { "a", "2" }, { "b", "7" }, { "c", "11" },
151 /* This one's here to test links (e.g. $template.owner.name). */
152 { "template", "templates/the_tmpl" },
156 /* Fake out the eval code for unit testing. */
158 unit_oget_func (void * notused, const char *text)
162 for (i = 0; hacked_obj_fields[i].name; ++i) {
163 if (!strcmp(hacked_obj_fields[i].name,text)) {
164 return xstrdup(hacked_obj_fields[i].value);
170 getter_t unit_oget = { unit_oget_func };
173 * Same as above, but the site-field stuff is so similar to the object-field
174 * stuff that it's not worth exercising too much separately.
177 unit_sget_func (void * notused, const char *text)
181 getter_t unit_sget = { unit_sget_func };
183 /* Fake links from an object/key tuple to an object/key string. */
184 struct { char *obj; char *key; char *value; } hacked_links[] = {
185 { "templates/the_tmpl", "owner", "users/the_user" },
186 { "users/the_user", "name", "Jeff Darcy" },
191 follow_link (const char *object, const char *key)
195 for (i = 0; hacked_links[i].obj; ++i) {
196 if (strcmp(object,hacked_links[i].obj)) {
199 if (strcmp(key,hacked_links[i].key)) {
202 return hacked_links[i].value;
208 extern char *follow_link (const char *object, const char *key);
212 * Return the string value of an expression for comparison or display, iff
213 * all component parts are string-valued themselves. That excludes numbers
217 string_value (value_t *v, getter_t *oget, getter_t *sget)
227 ? CALL_GETTER(oget,v->as_str) : NULL;
231 return sget ? CALL_GETTER(sget,v->as_str) : NULL;
234 left = string_value(v->as_tree.left,oget,sget);
236 v->resolved = follow_link((char *)left,
237 (char *)v->as_tree.right);
247 * Check whether a string looks like a simple decimal number. There's
248 * probably a library function for this somewhere.
251 is_ok_number (const char *a_str)
259 for (p = a_str; *p; ++p) {
269 * Comparisons are a bit messy. If both sides are numbers, strings that look
270 * like numbers, or expressions that evaluate to numbers (booleans evaluate
271 * to 0/1), then we do a numeric comparison. Otherwise, if both sides
272 * evaluate to strings, we attempt a string comparison. That's the logic,
273 * but the code is actually structured a different way to allow re-use of
274 * common operator-specific code at the end for both cases.
277 compare (value_t *left, comp_t op, value_t *right,
278 getter_t *oget, getter_t *sget)
282 int lval = 0; // solely to placate gcc
286 lstr = string_value(left,oget,sget);
287 rstr = string_value(right,oget,sget);
289 if (left->type == T_NUMBER) {
293 if (is_ok_number(lstr)) {
294 lval = strtoll(lstr,NULL,0);
301 lval = eval(left,oget,sget);
307 if (right->type == T_NUMBER) {
308 rval = right->as_num;
311 if (is_ok_number(rstr)) {
312 rval = strtoll(rstr,NULL,0);
319 rval = eval(right,oget,sget);
326 * Strcmp returns -1/0/1, but -1 for us would mean an error and
327 * which of 0/1 we return depends on which comparison operatoer
328 * we're dealing with. Therefore, we stick the strcmp result on
329 * the left side and let the switch below do an operator-appropriate
330 * compare against zero on the right.
333 if (!lstr || !rstr) {
336 lval = strcmp(lstr,rstr);
341 case C_LESSTHAN: return (lval < rval);
342 case C_LESSOREQ: return (lval <= rval);
343 case C_EQUAL: return (lval == rval);
344 case C_DIFFERENT: return (lval != rval);
345 case C_GREATEROREQ: return (lval >= rval);
346 case C_GREATERTHAN: return (lval > rval);
353 _print_value (const value_t *v, int level)
356 printf("%*sNULL\n",level,"");
362 printf("%*sNUMBER %lld\n",level,"",v->as_num);
365 printf("%*sSTRING %s\n",level,"",v->as_str);
368 #if defined(UNIT_TEST)
369 printf("%*sOBJECT FIELD %s (%s)\n",level,"",v->as_str,
370 unit_oget_func(NULL,v->as_str));
372 printf("%*sOBJECT FIELD %s\n",level,"",v->as_str);
376 #if defined(UNIT_TEST)
377 printf("%*sSERVER FIELD %s (%s)\n",level,"",v->as_str,
378 unit_sget_func(NULL,v->as_str));
380 printf("%*sSERVER FIELD %s\n",level,"",v->as_str);
384 printf("%*sCOMPARISON\n",level,"");
385 _print_value(v->as_tree.left,level+2);
386 _print_value(v->as_tree.right,level+2);
389 printf("%*sNOT\n",level,"");
390 _print_value(v->as_tree.left,level+2);
393 printf("%*sAND\n",level,"");
394 _print_value(v->as_tree.left,level+2);
395 _print_value(v->as_tree.right,level+2);
398 printf("%*sOR\n",level,"");
399 _print_value(v->as_tree.left,level+2);
400 _print_value(v->as_tree.right,level+2);
403 printf("%*sLINK\n",level,"");
404 _print_value(v->as_tree.left,level+2);
405 printf("%*sDEST FIELD %s\n",level+2,"",
406 (char *)v->as_tree.right);
409 printf("%*sUNKNOWN %d\n",level,"",v->type);
414 print_value (const value_t *v)
420 free_value (value_t *v)
426 free((char *)v->resolved);
436 free_value(v->as_tree.left);
437 free(v->as_tree.right);
443 free_value(v->as_tree.right);
446 free_value(v->as_tree.left);
454 * Evaluate an AST in the current context to one of:
458 * It's up to the caller whether error is functionally the same as false.
459 * Note that even T_NUMBER gets squeezed down to these three values. The
460 * only thing numbers are used for is comparing against other numbers to
461 * yield a boolean for the query or replication-policy code. If you want
462 * something that returns a number, this is the wrong language for it.
466 eval (const value_t *v, getter_t *oget, getter_t *sget)
473 return v->as_num != 0;
475 return v->as_str && *v->as_str;
477 str = CALL_GETTER(oget,v->as_str);
480 str = CALL_GETTER(sget,v->as_str);
483 str = string_value(v->as_tree.left,oget,sget);
485 str = follow_link(str,(char *)v->as_tree.right);
489 return compare(v->as_tree.left,(comp_t)v->as_tree.op,
490 v->as_tree.right, oget, sget);
492 res = eval(v->as_tree.left,oget,sget);
493 return (res >= 0) ? !res : res;
495 res = eval(v->as_tree.left,oget,sget);
497 res = eval(v->as_tree.right,oget,sget);
501 res = eval(v->as_tree.left,oget,sget);
505 return eval(v->as_tree.right,oget,sget);
511 #define YY_INPUT(buf,result,max) { \
512 result = (arg_off < arg_len) ? (*buf = arg_buf[arg_off++], 1) \
513 : (arg_off == arg_len) ? (*buf = '\n', ++arg_off, 1) : 0; \
516 #define YYSTYPE value_t *
519 Stmt = - BoolExpr - EOL { *cur_expr = $$; }
523 ( ( - AND - r:NotExpr )
524 { $$ = l = make_tree(T_AND,l,r); }
525 | ( - OR - r:NotExpr )
526 { $$ = l = make_tree(T_OR,l,r); } )*
529 | NOT - e:CompExpr - { $$ = make_tree(T_NOT,e,NULL); }
532 ( ( - LESS - r:Atom { $$ = make_comp(C_LESSTHAN,l,r); } )
533 | ( - LESS EQUAL - r:Atom { $$ = make_comp(C_LESSOREQ,l,r); } )
534 | ( - EQUAL EQUAL - r:Atom { $$ = make_comp(C_EQUAL,l,r); } )
535 | ( - NOT EQUAL - r:Atom { $$ = make_comp(C_DIFFERENT,l,r); } )
536 | ( - GREATER EQUAL - r:Atom { $$ = make_comp(C_GREATEROREQ,l,r); } )
537 | ( - GREATER - r:Atom { $$ = make_comp(C_GREATERTHAN,l,r); } ) )?
539 Atom = ( Literal | LinkField | ParenExpr )
541 Literal = NUMBER | STRING | TIME
543 LinkField = f:Field ( DOT i:ID { $$ = f = make_link(f,i); } )*
545 Field = DOLLAR i:ID { $$ = make_string((char *)i,T_OFIELD); }
546 | WAFFLE i:ID { $$ = make_string((char *)i,T_SFIELD); }
548 ParenExpr = OPEN - v:BoolExpr - CLOSE - { $$ = v; }
550 NUMBER = < [0-9]+ > { $$ = make_number(yytext); }
551 STRING = '"' < [^"]* > '"' { $$ = make_string(yytext,T_STRING); }
552 TIME = '~' < [^~]* > '~' { $$ = make_number(yytext); }
553 ID = < [a-z_]+ > { $$ = (YYSTYPE)yytext; }
567 EOL = '\n' | '\r\n' | '\r' | ';'
571 /* TBD: This isn't thread safe. */
573 parse (const char *text)
575 value_t *expr = NULL;
578 arg_len = strlen(text);
588 #if defined(UNIT_TEST)
590 main (int argc, char **argv)
596 unsigned int failed = 0;
598 for (i = 1; i < argc; ++i) {
599 expr = parse(argv[i]);
601 printf("could not parse '%s'\n",argv[i]);
606 str = string_value(expr,&unit_oget,&unit_sget);
608 printf("s= %s\n",str);
611 res = eval(expr,&unit_oget,&unit_sget);
612 printf("d= %d\n",res);