| 1 | /** |
| 2 | * re2c lexer for Netdata's expression evaluator |
| 3 | * |
| 4 | * This implementation uses re2c for lexical analysis and lemon for parsing. |
| 5 | * It is fully integrated with Netdata's existing EVAL_NODE structure. |
| 6 | */ |
| 7 | |
| 8 | #include "../eval-internal.h" |
| 9 | #include "parser_internal.h" |
| 10 | |
| 11 | // Scanner functions implementation |
| 12 | void scanner_init(Scanner *s, const char *input) { |
| 13 | if (!input) { |
| 14 | // Handle NULL input safely |
| 15 | s->cursor = ""; |
| 16 | s->marker = s->cursor; |
| 17 | s->token = s->cursor; |
| 18 | s->limit = s->cursor; |
| 19 | s->line = 1; |
| 20 | s->error = 1; // Set error flag for NULL input |
| 21 | return; |
| 22 | } |
| 23 | |
| 24 | s->cursor = input; |
| 25 | s->marker = s->cursor; |
| 26 | s->token = s->cursor; |
| 27 | s->limit = s->cursor + strlen(s->cursor); |
| 28 | s->line = 1; |
| 29 | s->error = 0; // Initialize error flag |
| 30 | } |
| 31 | |
| 32 | int scan(Scanner *s, YYSTYPE *lval) { |
| 33 | const char *YYMARKER; |
| 34 | const char *YYCURSOR = s->cursor; |
| 35 | char variable_buffer[EVAL_MAX_VARIABLE_NAME_LENGTH + 1] = {0}; |
| 36 | |
| 37 | // Skip whitespace |
| 38 | while (1) { |
| 39 | s->token = YYCURSOR; |
| 40 | |
| 41 | /*!re2c |
| 42 | re2c:define:YYCTYPE = char; |
| 43 | re2c:yyfill:enable = 0; |
| 44 | |
| 45 | // Skip whitespace |
| 46 | [ \t\r\n]+ { continue; } |
| 47 | |
| 48 | // Special numeric literals - more comprehensive handling for various capitalizations |
| 49 | // Support NaN with all case variations |
| 50 | // Matching str2ndd behavior in inlined.h which accepts "nan" (any case) and also "null" |
| 51 | [nN][aA][nN] | [nN][uU][lL][lL] { |
| 52 | lval->dval = NAN; |
| 53 | s->cursor = YYCURSOR; |
| 54 | return TOK_NUMBER; |
| 55 | } |
| 56 | |
| 57 | // Support Infinity with all case variations |
| 58 | // Matching str2ndd behavior in inlined.h which accepts "inf" in any case |
| 59 | [iI][nN][fF]([iI][nN][iI][tT][yY])? { |
| 60 | lval->dval = INFINITY; |
| 61 | s->cursor = YYCURSOR; |
| 62 | return TOK_NUMBER; |
| 63 | } |
| 64 | |
| 65 | // Numbers |
| 66 | [0-9]+ | |
| 67 | [0-9]+"."[0-9]* | |
| 68 | "."[0-9]+ | |
| 69 | [0-9]+[eE][+-]?[0-9]+ | |
| 70 | [0-9]+"."[0-9]*[eE][+-]?[0-9]+ | |
| 71 | "."[0-9]+[eE][+-]?[0-9]+ { |
| 72 | char *endptr; |
| 73 | lval->dval = str2ndd(s->token, &endptr); |
| 74 | s->cursor = YYCURSOR; |
| 75 | return TOK_NUMBER; |
| 76 | } |
| 77 | |
| 78 | // Variables - can contain any characters that aren't operators or closing brackets |
| 79 | // The original parser allows any character that passes !is_operator_first_symbol_or_space(s) && s != ')' && s != '}' |
| 80 | // Note that % is not explicitly excluded by is_operator_first_symbol_or_space in the original parser |
| 81 | "$"[^\000 \t\r\n&|!><=%+\-*/?()}{]+ { |
| 82 | size_t len = YYCURSOR - s->token - 1; // -1 to skip the $ |
| 83 | if (len >= EVAL_MAX_VARIABLE_NAME_LENGTH) { |
| 84 | len = EVAL_MAX_VARIABLE_NAME_LENGTH - 1; |
| 85 | } |
| 86 | memcpy(variable_buffer, s->token + 1, len); |
| 87 | variable_buffer[len] = '\0'; |
| 88 | lval->strval = strdupz(variable_buffer); |
| 89 | s->cursor = YYCURSOR; |
| 90 | return TOK_VARIABLE; |
| 91 | } |
| 92 | |
| 93 | // Empty variable with braces - treat as error |
| 94 | "${}" { s->cursor = YYCURSOR; s->error = 1; return 0; } |
| 95 | |
| 96 | // Variables with braces - can contain any character except } and \0 |
| 97 | "${" [^}\000]* "}" { |
| 98 | // Calculate length, excluding the ${ prefix and the } suffix |
| 99 | size_t len = YYCURSOR - s->token - 3; // -3 to skip ${ and } |
| 100 | if (len >= EVAL_MAX_VARIABLE_NAME_LENGTH) { |
| 101 | len = EVAL_MAX_VARIABLE_NAME_LENGTH - 1; |
| 102 | } |
| 103 | memcpy(variable_buffer, s->token + 2, len); |
| 104 | variable_buffer[len] = '\0'; |
| 105 | lval->strval = strdupz(variable_buffer); |
| 106 | s->cursor = YYCURSOR; |
| 107 | return TOK_VARIABLE; |
| 108 | } |
| 109 | |
| 110 | // Operators |
| 111 | "+" { s->cursor = YYCURSOR; return TOK_PLUS; } |
| 112 | "-" { s->cursor = YYCURSOR; return TOK_MINUS; } |
| 113 | "*" { s->cursor = YYCURSOR; return TOK_MULTIPLY; } |
| 114 | "/" { s->cursor = YYCURSOR; return TOK_DIVIDE; } |
| 115 | "%" { s->cursor = YYCURSOR; return TOK_MODULO; } |
| 116 | |
| 117 | // Logical operators - full case-insensitive handling for AND, OR, NOT |
| 118 | // Exactly matching the original parser's behavior from parse_and, parse_or, and parse_not |
| 119 | "&&" | [aA][nN][dD] { |
| 120 | s->cursor = YYCURSOR; |
| 121 | return TOK_AND; |
| 122 | } |
| 123 | |
| 124 | "||" | [oO][rR] { |
| 125 | s->cursor = YYCURSOR; |
| 126 | return TOK_OR; |
| 127 | } |
| 128 | |
| 129 | "!" | [nN][oO][tT] { |
| 130 | s->cursor = YYCURSOR; |
| 131 | return TOK_NOT; |
| 132 | } |
| 133 | |
| 134 | // Comparison operators |
| 135 | "==" | "=" { s->cursor = YYCURSOR; return TOK_EQ; } |
| 136 | "!=" | "<>" { s->cursor = YYCURSOR; return TOK_NE; } |
| 137 | "<" { s->cursor = YYCURSOR; return TOK_LT; } |
| 138 | "<=" { s->cursor = YYCURSOR; return TOK_LE; } |
| 139 | ">" { s->cursor = YYCURSOR; return TOK_GT; } |
| 140 | ">=" { s->cursor = YYCURSOR; return TOK_GE; } |
| 141 | |
| 142 | // Ternary operator |
| 143 | "?" { s->cursor = YYCURSOR; return TOK_QMARK; } |
| 144 | ":" { s->cursor = YYCURSOR; return TOK_COLON; } |
| 145 | |
| 146 | // Parentheses |
| 147 | "(" { s->cursor = YYCURSOR; return TOK_LPAREN; } |
| 148 | ")" { s->cursor = YYCURSOR; return TOK_RPAREN; } |
| 149 | |
| 150 | // Function names - case-insensitive support |
| 151 | // Exactly matching the original parser's behavior from parse_function |
| 152 | [aA][bB][sS] { s->cursor = YYCURSOR; return TOK_FUNCTION_ABS; } |
| 153 | |
| 154 | // Empty variable placeholders - these should be errors |
| 155 | "${" { s->cursor = YYCURSOR; s->error = 1; return 0; } |
| 156 | |
| 157 | // End of input |
| 158 | "\000" { s->cursor = YYCURSOR; return 0; } |
| 159 | |
| 160 | // Any other character is an error - set error flag and return 0 to stop parsing |
| 161 | . { |
| 162 | s->cursor = YYCURSOR; |
| 163 | s->error = 1; // Set error flag |
| 164 | return 0; // Return 0 to stop parsing |
| 165 | } |
| 166 | */ |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | // Function to parse an expression with re2c/lemon |
| 171 | EVAL_NODE *parse_expression_with_re2c_lemon(const char *string, const char **failed_at, int *error) { |
| 172 | Scanner scanner; |
| 173 | scanner_init(&scanner, string); |
| 174 | |
| 175 | if(failed_at) |
| 176 | *failed_at = NULL; |
| 177 | |
| 178 | // Use ParseAlloc with mallocz instead of malloc - mallocz will handle allocation failures |
| 179 | void *parser = ParseAlloc(mallocz); |
| 180 | |
| 181 | EVAL_NODE *result = NULL; |
| 182 | |
| 183 | YYSTYPE token_value; |
| 184 | int token_type; |
| 185 | |
| 186 | // Initialize error code |
| 187 | if (error) *error = EVAL_ERROR_OK; |
| 188 | |
| 189 | // Save the token start position for error reporting |
| 190 | const char *error_pos = scanner.cursor; |
| 191 | |
| 192 | // Variable to track if we need to free token_value.strval |
| 193 | int free_strval = 0; |
| 194 | |
| 195 | while ((token_type = scan(&scanner, &token_value)) > 0) { |
| 196 | // If the token is a variable, remember to free it if there's an error |
| 197 | free_strval = (token_type == TOK_VARIABLE); |
| 198 | |
| 199 | Parse(parser, token_type, token_value, &result); |
| 200 | |
| 201 | // Save position before potential error |
| 202 | error_pos = scanner.token; |
| 203 | |
| 204 | // Check for syntax errors after each token |
| 205 | if (result && result->operator == EVAL_OPERATOR_NOP && result->count == 0) { |
| 206 | // This is an error marker |
| 207 | if (error) *error = EVAL_ERROR_SYNTAX; |
| 208 | if (failed_at) { |
| 209 | *failed_at = error_pos; |
| 210 | } |
| 211 | |
| 212 | // Clean up |
| 213 | eval_node_free(result); |
| 214 | ParseFree(parser, freez); |
| 215 | |
| 216 | // If we just scanned a variable, free its strval |
| 217 | if (free_strval && token_value.strval) { |
| 218 | freez(token_value.strval); |
| 219 | } |
| 220 | |
| 221 | return NULL; |
| 222 | } |
| 223 | |
| 224 | // Reset free_strval since the parser has taken ownership of the string |
| 225 | free_strval = 0; |
| 226 | } |
| 227 | |
| 228 | // If the last token was a variable and scanning stopped due to an error, |
| 229 | // we need to free the token_value.strval |
| 230 | if (free_strval && token_value.strval) { |
| 231 | freez(token_value.strval); |
| 232 | token_value.strval = NULL; |
| 233 | } |
| 234 | |
| 235 | // Finish parsing |
| 236 | Parse(parser, 0, token_value, &result); |
| 237 | |
| 238 | // Clean up the parser |
| 239 | ParseFree(parser, freez); |
| 240 | |
| 241 | // Check for lexer errors |
| 242 | if (scanner.error) { |
| 243 | if (error) *error = EVAL_ERROR_UNKNOWN_OPERAND; |
| 244 | if (failed_at) { |
| 245 | *failed_at = error_pos; |
| 246 | } |
| 247 | |
| 248 | // Clean up result if it was created |
| 249 | if (result) { |
| 250 | eval_node_free(result); |
| 251 | } |
| 252 | |
| 253 | return NULL; |
| 254 | } |
| 255 | |
| 256 | if (!result) { |
| 257 | if (error) *error = EVAL_ERROR_SYNTAX; |
| 258 | if (failed_at) { |
| 259 | *failed_at = error_pos; |
| 260 | } |
| 261 | return NULL; |
| 262 | } |
| 263 | |
| 264 | if (failed_at) |
| 265 | *failed_at = NULL; |
| 266 | |
| 267 | return result; |
| 268 | } |