diff options
| author | Ben Fuhrmannek | 2021-09-01 17:01:00 +0200 |
|---|---|---|
| committer | Ben Fuhrmannek | 2021-09-01 17:01:00 +0200 |
| commit | 782336d8f4b9c411d344c0e7752e87631952f268 (patch) | |
| tree | deba1aed1db8b64a6b2f5a1076914fe8743e6c14 /src/sp_config_scanner.re | |
| parent | 5cdfa697666c0ed347aaa086953b7e9c7f8aa9f9 (diff) | |
implemented proper operand precedence using the shunting yard algorithm
Diffstat (limited to 'src/sp_config_scanner.re')
| -rw-r--r-- | src/sp_config_scanner.re | 118 |
1 files changed, 96 insertions, 22 deletions
diff --git a/src/sp_config_scanner.re b/src/sp_config_scanner.re index b36423b..f911df3 100644 --- a/src/sp_config_scanner.re +++ b/src/sp_config_scanner.re | |||
| @@ -71,20 +71,60 @@ static void str_dtor(zval *zv) { | |||
| 71 | zend_string_release_ex(Z_STR_P(zv), 1); | 71 | zend_string_release_ex(Z_STR_P(zv), 1); |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | static int apply_op(int v1, char op, int v2, int neg) { | 74 | // sy_ functions and macros are helpers for the shunting yard algorithm |
| 75 | if (neg) { v2 = !v2; } | 75 | #define sy_res_push(val) \ |
| 76 | if (cond_res_i >= 100) { cs_error_log("condition too complex on line %d", lineno); goto out; } \ | ||
| 77 | cond_res[cond_res_i++] = val; | ||
| 78 | #define sy_res_pop() cond_res[--cond_res_i] | ||
| 79 | #define sy_op_push(op) \ | ||
| 80 | if (cond_op_i >= 100) { cs_error_log("condition too complex on line %d", lineno); goto out; } \ | ||
| 81 | cond_op[cond_op_i++] = op; | ||
| 82 | #define sy_op_pop() cond_op[--cond_op_i] | ||
| 83 | #define sy_op_peek() cond_op[cond_op_i-1] | ||
| 84 | |||
| 85 | static inline int sy_op_precedence(char op) { | ||
| 76 | switch (op) { | 86 | switch (op) { |
| 77 | case 0: return v2; | 87 | case '!': return 120; |
| 78 | case '&': return (v1 && v2); | 88 | case '<': |
| 79 | case '|': return (v1 || v2); | 89 | case '>': |
| 80 | case '<': return (v1 < v2); | 90 | case 'L': // <= |
| 81 | case '>': return (v1 > v2); | 91 | case 'G': // >= |
| 82 | case '=': return (v1 == v2); | 92 | return 90; |
| 93 | case '&': return 70; | ||
| 94 | case '|': return 60; | ||
| 95 | case '=': return 20; | ||
| 83 | } | 96 | } |
| 84 | return 0; | 97 | return 0; |
| 85 | } | 98 | } |
| 99 | static inline int sy_op_is_left_assoc(char op) { | ||
| 100 | switch (op) { | ||
| 101 | case '!': return 0; | ||
| 102 | } | ||
| 103 | return 1; | ||
| 104 | } | ||
| 105 | static int sy_apply_op(char op, int a, int b) { | ||
| 106 | switch (op) { | ||
| 107 | case '!': return !a; | ||
| 108 | case '&': return (b && a); | ||
| 109 | case '|': return (b || a); | ||
| 110 | case '<': return (b < a); | ||
| 111 | case 'L': return (b <= a); | ||
| 112 | case 'G': return (b >= a); | ||
| 113 | case '>': return (b > a); | ||
| 114 | case '=': return (b == a); | ||
| 115 | } | ||
| 116 | return 0; | ||
| 117 | } | ||
| 118 | |||
| 119 | #define SY_APPLY_OP_FROM_STACK() \ | ||
| 120 | char op = sy_op_pop(); \ | ||
| 121 | int unary = (op == '!'); \ | ||
| 122 | if (cond_res_i < (2 - unary)) { cs_error_log("not enough input on line %d", lineno); goto out; } \ | ||
| 123 | int a = sy_res_pop(); \ | ||
| 124 | int b = unary ? 0 : sy_res_pop(); \ | ||
| 125 | int res = sy_apply_op(op, a, b); \ | ||
| 126 | sy_res_push(res); | ||
| 86 | 127 | ||
| 87 | #define APPLY_OP(v2) cond_res = apply_op(cond_res, cond_op, v2, cond_neg); cond_op = cond_neg = 0; | ||
| 88 | 128 | ||
| 89 | zend_result sp_config_scan(char *data, zend_result (*process_rule)(sp_parsed_keyword*)) | 129 | zend_result sp_config_scan(char *data, zend_result (*process_rule)(sp_parsed_keyword*)) |
| 90 | { | 130 | { |
| @@ -100,10 +140,13 @@ zend_result sp_config_scan(char *data, zend_result (*process_rule)(sp_parsed_key | |||
| 100 | 140 | ||
| 101 | HashTable vars; | 141 | HashTable vars; |
| 102 | zend_hash_init(&vars, 10, NULL, str_dtor, 1); | 142 | zend_hash_init(&vars, 10, NULL, str_dtor, 1); |
| 143 | zend_hash_str_add_ptr(&vars, ZEND_STRL("PHP_VERSION_ID"), zend_string_init(ZEND_STRL(ZEND_TOSTR(PHP_VERSION_ID)), 1)); | ||
| 103 | 144 | ||
| 104 | int cond_res = 1; | 145 | |
| 105 | char cond_op = 0; | 146 | int cond_res[100] = {0}; |
| 106 | int cond_neg = 0; | 147 | int cond_res_i = 0; |
| 148 | char cond_op[100] = {0}; | ||
| 149 | int cond_op_i = 0; | ||
| 107 | 150 | ||
| 108 | int cond = yycinit; | 151 | int cond = yycinit; |
| 109 | long lineno = 1; | 152 | long lineno = 1; |
| @@ -141,14 +184,14 @@ zend_result sp_config_scan(char *data, zend_result (*process_rule)(sp_parsed_key | |||
| 141 | goto yyc_init; | 184 | goto yyc_init; |
| 142 | } | 185 | } |
| 143 | <init> "@condition" ws+ { goto yyc_cond; } | 186 | <init> "@condition" ws+ { goto yyc_cond; } |
| 144 | <init> "@end_condition" ws* ";" { cond_res = 1; goto yyc_init; } | 187 | <init> "@end_condition" ws* ";" { cond_res[0] = 1; goto yyc_init; } |
| 145 | 188 | ||
| 146 | <cond> ws+ { goto yyc_cond; } | 189 | <cond> ws+ { goto yyc_cond; } |
| 147 | <cond> nl { lineno++; goto yyc_cond; } | 190 | <cond> nl { lineno++; goto yyc_cond; } |
| 148 | <cond> @t1 keyword @t2 "(" @t3 string? @t4 ")" { | 191 | <cond> @t1 keyword @t2 "(" @t3 string? @t4 ")" { |
| 149 | if (t4-t3 >= 2 && strlen("extension_loaded") == t2-t1 && strncmp("extension_loaded", t1, t2-t1) == 0) { | 192 | if (t4-t3 >= 2 && strlen("extension_loaded") == t2-t1 && strncmp("extension_loaded", t1, t2-t1) == 0) { |
| 150 | int is_loaded = (zend_hash_str_find_ptr(&module_registry, t3+1, t4-t3-2) != NULL); | 193 | int is_loaded = (zend_hash_str_find_ptr(&module_registry, t3+1, t4-t3-2) != NULL); |
| 151 | APPLY_OP(is_loaded); | 194 | sy_res_push(is_loaded); |
| 152 | } else { | 195 | } else { |
| 153 | cs_error_log("unknown function in condition on line %d", lineno); | 196 | cs_error_log("unknown function in condition on line %d", lineno); |
| 154 | goto out; | 197 | goto out; |
| @@ -161,21 +204,52 @@ zend_result sp_config_scan(char *data, zend_result (*process_rule)(sp_parsed_key | |||
| 161 | cs_error_log("unknown variable in condition on line %d", lineno); | 204 | cs_error_log("unknown variable in condition on line %d", lineno); |
| 162 | goto out; | 205 | goto out; |
| 163 | } | 206 | } |
| 164 | APPLY_OP(atoi(ZSTR_VAL(tmp))); | 207 | sy_res_push(atoi(ZSTR_VAL(tmp))); |
| 165 | goto yyc_cond_op; | 208 | goto yyc_cond_op; |
| 166 | } | 209 | } |
| 167 | <cond> @t1 [0-9]+ @t2 { APPLY_OP(atoi(t1)); goto yyc_cond_op; } | 210 | <cond> @t1 [0-9]+ @t2 { sy_res_push(atoi(t1)); goto yyc_cond_op; } |
| 168 | <cond> @t1 "!" { cond_neg = (cond_neg + 1) % 2; goto yyc_cond; } | 211 | <cond> @t1 "!" { sy_op_push(*t1); goto yyc_cond; } |
| 212 | <cond> @t1 "(" { sy_op_push(*t1); goto yyc_cond; } | ||
| 169 | <cond_op> ws+ { goto yyc_cond_op; } | 213 | <cond_op> ws+ { goto yyc_cond_op; } |
| 170 | <cond_op> nl { lineno++; goto yyc_cond_op; } | 214 | <cond_op> nl { lineno++; goto yyc_cond_op; } |
| 171 | <cond_op> @t1 ( "&&" | "||" | "<" | ">" | "==" ) { cond_op = *t1; goto yyc_cond; } | 215 | <cond_op> @t1 ( "&&" | "||" | "<" | ">" | "==" | "<=" | ">=") @t2 { |
| 172 | <cond_op> ";" { goto yyc_init; } | 216 | char op1 = *t1; |
| 173 | <cond, cond_op> * { cs_error_log("Syntax error in condition on line %d", lineno); return false; } | 217 | if (t2-t1 == 2) { |
| 218 | switch (op1) { | ||
| 219 | case '<': op1 = 'L'; break; // <= | ||
| 220 | case '>': op1 = 'G'; break; // >= | ||
| 221 | } | ||
| 222 | } | ||
| 223 | while (cond_op_i && sy_op_peek() != '(' && ((sy_op_precedence(sy_op_peek()) > sy_op_precedence(*t1)) || (sy_op_precedence(sy_op_peek()) == sy_op_precedence(*t1)) && sy_op_is_left_assoc(*t1))) { | ||
| 224 | SY_APPLY_OP_FROM_STACK(); | ||
| 225 | } | ||
| 226 | sy_op_push(*t1); | ||
| 227 | goto yyc_cond; | ||
| 228 | } | ||
| 229 | <cond_op> ")" { | ||
| 230 | while (cond_op_i && sy_op_peek() != '(') { | ||
| 231 | SY_APPLY_OP_FROM_STACK(); | ||
| 232 | } | ||
| 233 | if (cond_op_i == 0 || sy_op_peek() != '(') { | ||
| 234 | cs_error_log("unbalanced parathesis on line %d", lineno); goto out; | ||
| 235 | } | ||
| 236 | sy_op_pop(); | ||
| 237 | goto yyc_cond_op; | ||
| 238 | } | ||
| 239 | <cond_op> ";" { | ||
| 240 | while (cond_op_i) { | ||
| 241 | if (sy_op_peek() == '(') { cs_error_log("unbalanced parathesis on line %d", lineno); goto out; } | ||
| 242 | SY_APPLY_OP_FROM_STACK(); | ||
| 243 | } | ||
| 244 | if (cond_res_i > 1) { cs_error_log("invalid condition on line %d", lineno); goto out; } | ||
| 245 | goto yyc_init; | ||
| 246 | } | ||
| 247 | <cond, cond_op> * { cs_error_log("Syntax error in condition on line %d", lineno); goto out; } | ||
| 174 | 248 | ||
| 175 | <rule> ws+ { goto yyc_rule; } | 249 | <rule> ws+ { goto yyc_rule; } |
| 176 | <rule> nl / ( nl | ws )* "." { lineno++; goto yyc_rule; } | 250 | <rule> nl / ( nl | ws )* "." { lineno++; goto yyc_rule; } |
| 177 | <rule> "." @t1 keyword @t2 ( "(" @t3 ( string? | keyword ) @t4 ")" )? { | 251 | <rule> "." @t1 keyword @t2 ( "(" @t3 ( string? | keyword ) @t4 ")" )? { |
| 178 | if (!cond_res) { goto yyc_rule; } | 252 | if (!cond_res[0]) { goto yyc_rule; } |
| 179 | if (kw_i == max_keywords) { | 253 | if (kw_i == max_keywords) { |
| 180 | cs_error_log("Too many keywords in rule (more than %d) on line %d", max_keywords, lineno); | 254 | cs_error_log("Too many keywords in rule (more than %d) on line %d", max_keywords, lineno); |
| 181 | goto out; | 255 | goto out; |
| @@ -206,7 +280,7 @@ zend_result sp_config_scan(char *data, zend_result (*process_rule)(sp_parsed_key | |||
| 206 | } | 280 | } |
| 207 | <rule> ";" { | 281 | <rule> ";" { |
| 208 | end_of_rule: | 282 | end_of_rule: |
| 209 | if (!cond_res) { goto yyc_init; } | 283 | if (!cond_res[0]) { goto yyc_init; } |
| 210 | parsed_rule[kw_i++] = (sp_parsed_keyword){0, 0, 0, 0, 0, 0}; | 284 | parsed_rule[kw_i++] = (sp_parsed_keyword){0, 0, 0, 0, 0, 0}; |
| 211 | if (process_rule && process_rule(parsed_rule) != SUCCESS) { | 285 | if (process_rule && process_rule(parsed_rule) != SUCCESS) { |
| 212 | goto out; | 286 | goto out; |
