Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright (c) 2016 Intel Corporation. |
| 4 | # |
David B. Kinder | ac74d8b | 2017-01-18 17:01:01 -0800 | [diff] [blame] | 5 | # SPDX-License-Identifier: Apache-2.0 |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 6 | |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 7 | import copy |
Martí Bolívar | 51f55b4 | 2021-02-26 07:50:27 -0800 | [diff] [blame] | 8 | import logging |
Martí Bolívar | d802fc3 | 2021-02-26 08:09:22 -0800 | [diff] [blame] | 9 | import os |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 10 | import re |
Martí Bolívar | d802fc3 | 2021-02-26 08:09:22 -0800 | [diff] [blame] | 11 | import sys |
| 12 | import threading |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 13 | |
| 14 | try: |
| 15 | import ply.lex as lex |
| 16 | import ply.yacc as yacc |
| 17 | except ImportError: |
Ulf Magnusson | 50b9b12 | 2019-09-07 14:41:01 +0200 | [diff] [blame] | 18 | sys.exit("PLY library for Python 3 not installed.\n" |
| 19 | "Please install the ply package using your workstation's\n" |
| 20 | "package manager or the 'pip' tool.") |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 21 | |
Martí Bolívar | 51f55b4 | 2021-02-26 07:50:27 -0800 | [diff] [blame] | 22 | _logger = logging.getLogger('twister') |
| 23 | |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 24 | reserved = { |
| 25 | 'and' : 'AND', |
| 26 | 'or' : 'OR', |
| 27 | 'not' : 'NOT', |
| 28 | 'in' : 'IN', |
| 29 | } |
| 30 | |
| 31 | tokens = [ |
| 32 | "HEX", |
| 33 | "STR", |
| 34 | "INTEGER", |
| 35 | "EQUALS", |
| 36 | "NOTEQUALS", |
| 37 | "LT", |
| 38 | "GT", |
| 39 | "LTEQ", |
| 40 | "GTEQ", |
| 41 | "OPAREN", |
| 42 | "CPAREN", |
| 43 | "OBRACKET", |
| 44 | "CBRACKET", |
| 45 | "COMMA", |
| 46 | "SYMBOL", |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 47 | "COLON", |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 48 | ] + list(reserved.values()) |
| 49 | |
| 50 | def t_HEX(t): |
| 51 | r"0x[0-9a-fA-F]+" |
| 52 | t.value = str(int(t.value, 16)) |
| 53 | return t |
| 54 | |
| 55 | def t_INTEGER(t): |
| 56 | r"\d+" |
| 57 | t.value = str(int(t.value)) |
| 58 | return t |
| 59 | |
| 60 | def t_STR(t): |
Andrew Boie | 5aaf5f1 | 2016-06-02 12:21:41 -0700 | [diff] [blame] | 61 | r'\"([^\\\n]|(\\.))*?\"|\'([^\\\n]|(\\.))*?\'' |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 62 | # nip off the quotation marks |
| 63 | t.value = t.value[1:-1] |
| 64 | return t |
| 65 | |
| 66 | t_EQUALS = r"==" |
| 67 | |
| 68 | t_NOTEQUALS = r"!=" |
| 69 | |
| 70 | t_LT = r"<" |
| 71 | |
| 72 | t_GT = r">" |
| 73 | |
| 74 | t_LTEQ = r"<=" |
| 75 | |
| 76 | t_GTEQ = r">=" |
| 77 | |
| 78 | t_OPAREN = r"[(]" |
| 79 | |
| 80 | t_CPAREN = r"[)]" |
| 81 | |
| 82 | t_OBRACKET = r"\[" |
| 83 | |
| 84 | t_CBRACKET = r"\]" |
| 85 | |
| 86 | t_COMMA = r"," |
| 87 | |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 88 | t_COLON = ":" |
| 89 | |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 90 | def t_SYMBOL(t): |
| 91 | r"[A-Za-z_][0-9A-Za-z_]*" |
| 92 | t.type = reserved.get(t.value, "SYMBOL") |
| 93 | return t |
| 94 | |
| 95 | t_ignore = " \t\n" |
| 96 | |
| 97 | def t_error(t): |
| 98 | raise SyntaxError("Unexpected token '%s'" % t.value) |
| 99 | |
| 100 | lex.lex() |
| 101 | |
| 102 | precedence = ( |
| 103 | ('left', 'OR'), |
| 104 | ('left', 'AND'), |
| 105 | ('right', 'NOT'), |
Ulf Magnusson | 0d39a10 | 2019-09-06 11:13:19 +0200 | [diff] [blame] | 106 | ('nonassoc', 'EQUALS', 'NOTEQUALS', 'GT', 'LT', 'GTEQ', 'LTEQ', 'IN'), |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 107 | ) |
| 108 | |
| 109 | def p_expr_or(p): |
| 110 | 'expr : expr OR expr' |
| 111 | p[0] = ("or", p[1], p[3]) |
| 112 | |
| 113 | def p_expr_and(p): |
| 114 | 'expr : expr AND expr' |
| 115 | p[0] = ("and", p[1], p[3]) |
| 116 | |
| 117 | def p_expr_not(p): |
| 118 | 'expr : NOT expr' |
| 119 | p[0] = ("not", p[2]) |
| 120 | |
| 121 | def p_expr_parens(p): |
| 122 | 'expr : OPAREN expr CPAREN' |
| 123 | p[0] = p[2] |
| 124 | |
| 125 | def p_expr_eval(p): |
| 126 | """expr : SYMBOL EQUALS const |
| 127 | | SYMBOL NOTEQUALS const |
| 128 | | SYMBOL GT number |
| 129 | | SYMBOL LT number |
| 130 | | SYMBOL GTEQ number |
| 131 | | SYMBOL LTEQ number |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 132 | | SYMBOL IN list |
| 133 | | SYMBOL COLON STR""" |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 134 | p[0] = (p[2], p[1], p[3]) |
| 135 | |
| 136 | def p_expr_single(p): |
| 137 | """expr : SYMBOL""" |
| 138 | p[0] = ("exists", p[1]) |
| 139 | |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 140 | def p_func(p): |
| 141 | """expr : SYMBOL OPAREN arg_intr CPAREN""" |
| 142 | p[0] = [p[1]] |
| 143 | p[0].append(p[3]) |
| 144 | |
| 145 | def p_arg_intr_single(p): |
| 146 | """arg_intr : const""" |
| 147 | p[0] = [p[1]] |
| 148 | |
| 149 | def p_arg_intr_mult(p): |
| 150 | """arg_intr : arg_intr COMMA const""" |
| 151 | p[0] = copy.copy(p[1]) |
| 152 | p[0].append(p[3]) |
| 153 | |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 154 | def p_list(p): |
| 155 | """list : OBRACKET list_intr CBRACKET""" |
| 156 | p[0] = p[2] |
| 157 | |
| 158 | def p_list_intr_single(p): |
| 159 | """list_intr : const""" |
| 160 | p[0] = [p[1]] |
| 161 | |
| 162 | def p_list_intr_mult(p): |
| 163 | """list_intr : list_intr COMMA const""" |
| 164 | p[0] = copy.copy(p[1]) |
| 165 | p[0].append(p[3]) |
| 166 | |
| 167 | def p_const(p): |
| 168 | """const : STR |
| 169 | | number""" |
| 170 | p[0] = p[1] |
| 171 | |
| 172 | def p_number(p): |
| 173 | """number : INTEGER |
| 174 | | HEX""" |
| 175 | p[0] = p[1] |
| 176 | |
| 177 | def p_error(p): |
| 178 | if p: |
| 179 | raise SyntaxError("Unexpected token '%s'" % p.value) |
| 180 | else: |
| 181 | raise SyntaxError("Unexpected end of expression") |
| 182 | |
Anas Nashif | 9c974b9 | 2017-12-06 10:10:55 -0500 | [diff] [blame] | 183 | if "PARSETAB_DIR" not in os.environ: |
| 184 | parser = yacc.yacc(debug=0) |
| 185 | else: |
| 186 | parser = yacc.yacc(debug=0, outputdir=os.environ["PARSETAB_DIR"]) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 187 | |
| 188 | def ast_sym(ast, env): |
| 189 | if ast in env: |
| 190 | return str(env[ast]) |
| 191 | return "" |
| 192 | |
| 193 | def ast_sym_int(ast, env): |
| 194 | if ast in env: |
Andrew Boie | 31e772d | 2017-04-18 11:22:50 -0700 | [diff] [blame] | 195 | v = env[ast] |
| 196 | if v.startswith("0x") or v.startswith("0X"): |
| 197 | return int(v, 16) |
| 198 | else: |
| 199 | return int(v, 10) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 200 | return 0 |
| 201 | |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 202 | def ast_expr(ast, env, edt): |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 203 | if ast[0] == "not": |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 204 | return not ast_expr(ast[1], env, edt) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 205 | elif ast[0] == "or": |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 206 | return ast_expr(ast[1], env, edt) or ast_expr(ast[2], env, edt) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 207 | elif ast[0] == "and": |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 208 | return ast_expr(ast[1], env, edt) and ast_expr(ast[2], env, edt) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 209 | elif ast[0] == "==": |
| 210 | return ast_sym(ast[1], env) == ast[2] |
| 211 | elif ast[0] == "!=": |
| 212 | return ast_sym(ast[1], env) != ast[2] |
| 213 | elif ast[0] == ">": |
| 214 | return ast_sym_int(ast[1], env) > int(ast[2]) |
| 215 | elif ast[0] == "<": |
| 216 | return ast_sym_int(ast[1], env) < int(ast[2]) |
| 217 | elif ast[0] == ">=": |
| 218 | return ast_sym_int(ast[1], env) >= int(ast[2]) |
| 219 | elif ast[0] == "<=": |
| 220 | return ast_sym_int(ast[1], env) <= int(ast[2]) |
| 221 | elif ast[0] == "in": |
| 222 | return ast_sym(ast[1], env) in ast[2] |
| 223 | elif ast[0] == "exists": |
Ulf Magnusson | 47ef9ba | 2019-09-05 18:50:18 +0200 | [diff] [blame] | 224 | return bool(ast_sym(ast[1], env)) |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 225 | elif ast[0] == ":": |
Ulf Magnusson | 47ef9ba | 2019-09-05 18:50:18 +0200 | [diff] [blame] | 226 | return bool(re.match(ast[2], ast_sym(ast[1], env))) |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 227 | elif ast[0] == "dt_compat_enabled": |
| 228 | compat = ast[1][0] |
| 229 | for node in edt.nodes: |
Martí Bolívar | 8165008 | 2020-10-05 20:02:13 -0700 | [diff] [blame] | 230 | if compat in node.compats and node.status == "okay": |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 231 | return True |
| 232 | return False |
| 233 | elif ast[0] == "dt_alias_exists": |
| 234 | alias = ast[1][0] |
| 235 | for node in edt.nodes: |
Martí Bolívar | 8165008 | 2020-10-05 20:02:13 -0700 | [diff] [blame] | 236 | if alias in node.aliases and node.status == "okay": |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 237 | return True |
| 238 | return False |
Martí Bolívar | 51f55b4 | 2021-02-26 07:50:27 -0800 | [diff] [blame] | 239 | elif ast[0] == "dt_enabled_alias_with_parent_compat": |
| 240 | # Checks if the DT has an enabled alias node whose parent has |
| 241 | # a given compatible. For matching things like gpio-leds child |
| 242 | # nodes, which do not have compatibles themselves. |
| 243 | # |
| 244 | # The legacy "dt_compat_enabled_with_alias" form is still |
| 245 | # accepted but is now deprecated and causes a warning. This is |
| 246 | # meant to give downstream users some time to notice and |
| 247 | # adjust. Its argument order only made sense under the (bad) |
| 248 | # assumption that the gpio-leds child node has the same compatible |
| 249 | |
| 250 | alias = ast[1][0] |
| 251 | compat = ast[1][1] |
| 252 | |
| 253 | return ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, |
| 254 | compat) |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 255 | elif ast[0] == "dt_compat_enabled_with_alias": |
| 256 | compat = ast[1][0] |
| 257 | alias = ast[1][1] |
Martí Bolívar | 51f55b4 | 2021-02-26 07:50:27 -0800 | [diff] [blame] | 258 | |
| 259 | _logger.warning('dt_compat_enabled_with_alias("%s", "%s"): ' |
| 260 | 'this is deprecated, use ' |
| 261 | 'dt_enabled_alias_with_parent_compat("%s", "%s") ' |
| 262 | 'instead', |
| 263 | compat, alias, alias, compat) |
| 264 | |
| 265 | return ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, |
| 266 | compat) |
Johann Fischer | bb47497 | 2020-12-03 21:19:53 +0100 | [diff] [blame] | 267 | elif ast[0] == "dt_compat_enabled_with_label": |
| 268 | compat = ast[1][0] |
| 269 | label = ast[1][1] |
Johann Fischer | 44a02c5 | 2021-03-16 14:20:46 +0100 | [diff] [blame] | 270 | node = edt.label2node.get(label) |
| 271 | return node is not None and node.status == 'okay' and node.matching_compat == compat |
Henrik Brix Andersen | c08d1e0 | 2020-11-20 17:31:57 +0100 | [diff] [blame] | 272 | elif ast[0] == "dt_chosen_enabled": |
| 273 | chosen = ast[1][0] |
| 274 | node = edt.chosen_node(chosen) |
| 275 | if node and node.status == "okay": |
| 276 | return True |
| 277 | return False |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 278 | |
Martí Bolívar | 51f55b4 | 2021-02-26 07:50:27 -0800 | [diff] [blame] | 279 | def ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, compat): |
| 280 | # Helper shared with the now deprecated |
| 281 | # dt_compat_enabled_with_alias version. |
| 282 | |
| 283 | for node in edt.nodes: |
| 284 | parent = node.parent |
| 285 | if parent is None: |
| 286 | continue |
| 287 | if (node.status == "okay" and alias in node.aliases and |
| 288 | parent.matching_compat == compat): |
| 289 | return True |
| 290 | |
| 291 | return False |
| 292 | |
Andrew Boie | 99c0f64 | 2016-06-02 12:22:41 -0700 | [diff] [blame] | 293 | mutex = threading.Lock() |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 294 | |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 295 | def parse(expr_text, env, edt): |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 296 | """Given a text representation of an expression in our language, |
| 297 | use the provided environment to determine whether the expression |
| 298 | is true or false""" |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 299 | |
Andrew Boie | 99c0f64 | 2016-06-02 12:22:41 -0700 | [diff] [blame] | 300 | # Like it's C counterpart, state machine is not thread-safe |
| 301 | mutex.acquire() |
| 302 | try: |
| 303 | ast = parser.parse(expr_text) |
| 304 | finally: |
| 305 | mutex.release() |
| 306 | |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 307 | return ast_expr(ast, env, edt) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 308 | |
| 309 | # Just some test code |
| 310 | if __name__ == "__main__": |
| 311 | |
| 312 | local_env = { |
| 313 | "A" : "1", |
| 314 | "C" : "foo", |
| 315 | "D" : "20", |
| 316 | "E" : 0x100, |
| 317 | "F" : "baz" |
| 318 | } |
| 319 | |
| 320 | |
| 321 | for line in open(sys.argv[1]).readlines(): |
| 322 | lex.input(line) |
| 323 | for tok in iter(lex.token, None): |
| 324 | print(tok.type, tok.value) |
| 325 | |
| 326 | parser = yacc.yacc() |
| 327 | print(parser.parse(line)) |
| 328 | |
Kumar Gala | 7733b94 | 2019-09-12 17:08:43 -0500 | [diff] [blame] | 329 | print(parse(line, local_env, None)) |