at v6.4-rc4 313 lines 7.4 kB view raw
1/* Simple expression parser */ 2%{ 3#define YYDEBUG 1 4#include <assert.h> 5#include <math.h> 6#include <stdlib.h> 7#include "util/debug.h" 8#define IN_EXPR_Y 1 9#include "expr.h" 10%} 11 12%define api.pure full 13 14%parse-param { double *final_val } 15%parse-param { struct expr_parse_ctx *ctx } 16%parse-param { bool compute_ids } 17%parse-param {void *scanner} 18%lex-param {void* scanner} 19 20%union { 21 double num; 22 char *str; 23 struct ids { 24 /* 25 * When creating ids, holds the working set of event ids. NULL 26 * implies the set is empty. 27 */ 28 struct hashmap *ids; 29 /* 30 * The metric value. When not creating ids this is the value 31 * read from a counter, a constant or some computed value. When 32 * creating ids the value is either a constant or BOTTOM. NAN is 33 * used as the special BOTTOM value, representing a "set of all 34 * values" case. 35 */ 36 double val; 37 } ids; 38} 39 40%token ID NUMBER MIN MAX IF ELSE LITERAL D_RATIO SOURCE_COUNT EXPR_ERROR 41%left MIN MAX IF 42%left '|' 43%left '^' 44%left '&' 45%left '<' '>' 46%left '-' '+' 47%left '*' '/' '%' 48%left NEG NOT 49%type <num> NUMBER LITERAL 50%type <str> ID 51%destructor { free ($$); } <str> 52%type <ids> expr if_expr 53%destructor { ids__free($$.ids); } <ids> 54 55%{ 56static void expr_error(double *final_val __maybe_unused, 57 struct expr_parse_ctx *ctx __maybe_unused, 58 bool compute_ids __maybe_unused, 59 void *scanner, 60 const char *s) 61{ 62 pr_debug("%s\n", s); 63} 64 65/* 66 * During compute ids, the special "bottom" value uses NAN to represent the set 67 * of all values. NAN is selected as it isn't a useful constant value. 68 */ 69#define BOTTOM NAN 70 71/* During computing ids, does val represent a constant (non-BOTTOM) value? */ 72static bool is_const(double val) 73{ 74 return isfinite(val); 75} 76 77static struct ids union_expr(struct ids ids1, struct ids ids2) 78{ 79 struct ids result = { 80 .val = BOTTOM, 81 .ids = ids__union(ids1.ids, ids2.ids), 82 }; 83 return result; 84} 85 86static struct ids handle_id(struct expr_parse_ctx *ctx, char *id, 87 bool compute_ids, bool source_count) 88{ 89 struct ids result; 90 91 if (!compute_ids) { 92 /* 93 * Compute the event's value from ID. If the ID isn't known then 94 * it isn't used to compute the formula so set to NAN. 95 */ 96 struct expr_id_data *data; 97 98 result.val = NAN; 99 if (expr__resolve_id(ctx, id, &data) == 0) { 100 result.val = source_count 101 ? expr_id_data__source_count(data) 102 : expr_id_data__value(data); 103 } 104 result.ids = NULL; 105 free(id); 106 } else { 107 /* 108 * Set the value to BOTTOM to show that any value is possible 109 * when the event is computed. Create a set of just the ID. 110 */ 111 result.val = BOTTOM; 112 result.ids = ids__new(); 113 if (!result.ids || ids__insert(result.ids, id)) { 114 pr_err("Error creating IDs for '%s'", id); 115 free(id); 116 } 117 } 118 return result; 119} 120 121/* 122 * If we're not computing ids or $1 and $3 are constants, compute the new 123 * constant value using OP. Its invariant that there are no ids. If computing 124 * ids for non-constants union the set of IDs that must be computed. 125 */ 126#define BINARY_LONG_OP(RESULT, OP, LHS, RHS) \ 127 if (!compute_ids || (is_const(LHS.val) && is_const(RHS.val))) { \ 128 assert(LHS.ids == NULL); \ 129 assert(RHS.ids == NULL); \ 130 if (isnan(LHS.val) || isnan(RHS.val)) { \ 131 RESULT.val = NAN; \ 132 } else { \ 133 RESULT.val = (long)LHS.val OP (long)RHS.val; \ 134 } \ 135 RESULT.ids = NULL; \ 136 } else { \ 137 RESULT = union_expr(LHS, RHS); \ 138 } 139 140#define BINARY_OP(RESULT, OP, LHS, RHS) \ 141 if (!compute_ids || (is_const(LHS.val) && is_const(RHS.val))) { \ 142 assert(LHS.ids == NULL); \ 143 assert(RHS.ids == NULL); \ 144 if (isnan(LHS.val) || isnan(RHS.val)) { \ 145 RESULT.val = NAN; \ 146 } else { \ 147 RESULT.val = LHS.val OP RHS.val; \ 148 } \ 149 RESULT.ids = NULL; \ 150 } else { \ 151 RESULT = union_expr(LHS, RHS); \ 152 } 153 154%} 155%% 156 157start: if_expr 158{ 159 if (compute_ids) 160 ctx->ids = ids__union($1.ids, ctx->ids); 161 162 if (final_val) 163 *final_val = $1.val; 164} 165; 166 167if_expr: expr IF expr ELSE if_expr 168{ 169 if (fpclassify($3.val) == FP_ZERO) { 170 /* 171 * The IF expression evaluated to 0 so treat as false, take the 172 * ELSE and discard everything else. 173 */ 174 $$.val = $5.val; 175 $$.ids = $5.ids; 176 ids__free($1.ids); 177 ids__free($3.ids); 178 } else if (!compute_ids || is_const($3.val)) { 179 /* 180 * If ids aren't computed then treat the expression as true. If 181 * ids are being computed and the IF expr is a non-zero 182 * constant, then also evaluate the true case. 183 */ 184 $$.val = $1.val; 185 $$.ids = $1.ids; 186 ids__free($3.ids); 187 ids__free($5.ids); 188 } else if ($1.val == $5.val) { 189 /* 190 * LHS == RHS, so both are an identical constant. No need to 191 * evaluate any events. 192 */ 193 $$.val = $1.val; 194 $$.ids = NULL; 195 ids__free($1.ids); 196 ids__free($3.ids); 197 ids__free($5.ids); 198 } else { 199 /* 200 * Value is either the LHS or RHS and we need the IF expression 201 * to compute it. 202 */ 203 $$ = union_expr($1, union_expr($3, $5)); 204 } 205} 206| expr 207; 208 209expr: NUMBER 210{ 211 $$.val = $1; 212 $$.ids = NULL; 213} 214| ID { $$ = handle_id(ctx, $1, compute_ids, /*source_count=*/false); } 215| SOURCE_COUNT '(' ID ')' { $$ = handle_id(ctx, $3, compute_ids, /*source_count=*/true); } 216| expr '|' expr { BINARY_LONG_OP($$, |, $1, $3); } 217| expr '&' expr { BINARY_LONG_OP($$, &, $1, $3); } 218| expr '^' expr { BINARY_LONG_OP($$, ^, $1, $3); } 219| expr '<' expr { BINARY_OP($$, <, $1, $3); } 220| expr '>' expr { BINARY_OP($$, >, $1, $3); } 221| expr '+' expr { BINARY_OP($$, +, $1, $3); } 222| expr '-' expr { BINARY_OP($$, -, $1, $3); } 223| expr '*' expr { BINARY_OP($$, *, $1, $3); } 224| expr '/' expr 225{ 226 if (fpclassify($3.val) == FP_ZERO) { 227 pr_debug("division by zero\n"); 228 assert($3.ids == NULL); 229 if (compute_ids) 230 ids__free($1.ids); 231 $$.val = NAN; 232 $$.ids = NULL; 233 } else if (!compute_ids || (is_const($1.val) && is_const($3.val))) { 234 assert($1.ids == NULL); 235 assert($3.ids == NULL); 236 $$.val = $1.val / $3.val; 237 $$.ids = NULL; 238 } else { 239 /* LHS and/or RHS need computing from event IDs so union. */ 240 $$ = union_expr($1, $3); 241 } 242} 243| expr '%' expr 244{ 245 if (fpclassify($3.val) == FP_ZERO) { 246 pr_debug("division by zero\n"); 247 YYABORT; 248 } else if (!compute_ids || (is_const($1.val) && is_const($3.val))) { 249 assert($1.ids == NULL); 250 assert($3.ids == NULL); 251 $$.val = (long)$1.val % (long)$3.val; 252 $$.ids = NULL; 253 } else { 254 /* LHS and/or RHS need computing from event IDs so union. */ 255 $$ = union_expr($1, $3); 256 } 257} 258| D_RATIO '(' expr ',' expr ')' 259{ 260 if (fpclassify($5.val) == FP_ZERO) { 261 /* 262 * Division by constant zero always yields zero and no events 263 * are necessary. 264 */ 265 assert($5.ids == NULL); 266 $$.val = 0.0; 267 $$.ids = NULL; 268 ids__free($3.ids); 269 } else if (!compute_ids || (is_const($3.val) && is_const($5.val))) { 270 assert($3.ids == NULL); 271 assert($5.ids == NULL); 272 $$.val = $3.val / $5.val; 273 $$.ids = NULL; 274 } else { 275 /* LHS and/or RHS need computing from event IDs so union. */ 276 $$ = union_expr($3, $5); 277 } 278} 279| '-' expr %prec NEG 280{ 281 $$.val = -$2.val; 282 $$.ids = $2.ids; 283} 284| '(' if_expr ')' 285{ 286 $$ = $2; 287} 288| MIN '(' expr ',' expr ')' 289{ 290 if (!compute_ids) { 291 $$.val = $3.val < $5.val ? $3.val : $5.val; 292 $$.ids = NULL; 293 } else { 294 $$ = union_expr($3, $5); 295 } 296} 297| MAX '(' expr ',' expr ')' 298{ 299 if (!compute_ids) { 300 $$.val = $3.val > $5.val ? $3.val : $5.val; 301 $$.ids = NULL; 302 } else { 303 $$ = union_expr($3, $5); 304 } 305} 306| LITERAL 307{ 308 $$.val = $1; 309 $$.ids = NULL; 310} 311; 312 313%%