1#!/usr/bin/env python3 2# 3# Copyright (c) 2016 Intel Corporation. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7import copy 8import logging 9import os 10import re 11import sys 12import threading 13 14try: 15 import ply.lex as lex 16 import ply.yacc as yacc 17except ImportError: 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.") 21 22_logger = logging.getLogger('twister') 23 24reserved = { 25 'and' : 'AND', 26 'or' : 'OR', 27 'not' : 'NOT', 28 'in' : 'IN', 29} 30 31tokens = [ 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", 47 "COLON", 48] + list(reserved.values()) 49 50def t_HEX(t): 51 r"0x[0-9a-fA-F]+" 52 t.value = str(int(t.value, 16)) 53 return t 54 55def t_INTEGER(t): 56 r"\d+" 57 t.value = str(int(t.value)) 58 return t 59 60def t_STR(t): 61 r'\"([^\\\n]|(\\.))*?\"|\'([^\\\n]|(\\.))*?\'' 62 # nip off the quotation marks 63 t.value = t.value[1:-1] 64 return t 65 66t_EQUALS = r"==" 67 68t_NOTEQUALS = r"!=" 69 70t_LT = r"<" 71 72t_GT = r">" 73 74t_LTEQ = r"<=" 75 76t_GTEQ = r">=" 77 78t_OPAREN = r"[(]" 79 80t_CPAREN = r"[)]" 81 82t_OBRACKET = r"\[" 83 84t_CBRACKET = r"\]" 85 86t_COMMA = r"," 87 88t_COLON = ":" 89 90def t_SYMBOL(t): 91 r"[A-Za-z_][0-9A-Za-z_]*" 92 t.type = reserved.get(t.value, "SYMBOL") 93 return t 94 95t_ignore = " \t\n" 96 97def t_error(t): 98 raise SyntaxError("Unexpected token '%s'" % t.value) 99 100lex.lex() 101 102precedence = ( 103 ('left', 'OR'), 104 ('left', 'AND'), 105 ('right', 'NOT'), 106 ('nonassoc', 'EQUALS', 'NOTEQUALS', 'GT', 'LT', 'GTEQ', 'LTEQ', 'IN'), 107) 108 109def p_expr_or(p): 110 'expr : expr OR expr' 111 p[0] = ("or", p[1], p[3]) 112 113def p_expr_and(p): 114 'expr : expr AND expr' 115 p[0] = ("and", p[1], p[3]) 116 117def p_expr_not(p): 118 'expr : NOT expr' 119 p[0] = ("not", p[2]) 120 121def p_expr_parens(p): 122 'expr : OPAREN expr CPAREN' 123 p[0] = p[2] 124 125def 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 132 | SYMBOL IN list 133 | SYMBOL COLON STR""" 134 p[0] = (p[2], p[1], p[3]) 135 136def p_expr_single(p): 137 """expr : SYMBOL""" 138 p[0] = ("exists", p[1]) 139 140def p_func(p): 141 """expr : SYMBOL OPAREN arg_intr CPAREN""" 142 p[0] = [p[1]] 143 p[0].append(p[3]) 144 145def p_arg_intr_single(p): 146 """arg_intr : const""" 147 p[0] = [p[1]] 148 149def 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 154def p_list(p): 155 """list : OBRACKET list_intr CBRACKET""" 156 p[0] = p[2] 157 158def p_list_intr_single(p): 159 """list_intr : const""" 160 p[0] = [p[1]] 161 162def 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 167def p_const(p): 168 """const : STR 169 | number""" 170 p[0] = p[1] 171 172def p_number(p): 173 """number : INTEGER 174 | HEX""" 175 p[0] = p[1] 176 177def p_error(p): 178 if p: 179 raise SyntaxError("Unexpected token '%s'" % p.value) 180 else: 181 raise SyntaxError("Unexpected end of expression") 182 183if "PARSETAB_DIR" not in os.environ: 184 parser = yacc.yacc(debug=0) 185else: 186 parser = yacc.yacc(debug=0, outputdir=os.environ["PARSETAB_DIR"]) 187 188def ast_sym(ast, env): 189 if ast in env: 190 return str(env[ast]) 191 return "" 192 193def ast_sym_int(ast, env): 194 if ast in env: 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) 200 return 0 201 202def ast_expr(ast, env, edt): 203 if ast[0] == "not": 204 return not ast_expr(ast[1], env, edt) 205 elif ast[0] == "or": 206 return ast_expr(ast[1], env, edt) or ast_expr(ast[2], env, edt) 207 elif ast[0] == "and": 208 return ast_expr(ast[1], env, edt) and ast_expr(ast[2], env, edt) 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": 224 return bool(ast_sym(ast[1], env)) 225 elif ast[0] == ":": 226 return bool(re.match(ast[2], ast_sym(ast[1], env))) 227 elif ast[0] == "dt_compat_enabled": 228 compat = ast[1][0] 229 for node in edt.nodes: 230 if compat in node.compats and node.status == "okay": 231 return True 232 return False 233 elif ast[0] == "dt_alias_exists": 234 alias = ast[1][0] 235 for node in edt.nodes: 236 if alias in node.aliases and node.status == "okay": 237 return True 238 return False 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) 255 elif ast[0] == "dt_compat_enabled_with_alias": 256 compat = ast[1][0] 257 alias = ast[1][1] 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) 267 elif ast[0] == "dt_label_with_parent_compat_enabled": 268 compat = ast[1][1] 269 label = ast[1][0] 270 node = edt.label2node.get(label) 271 if node is not None: 272 parent = node.parent 273 else: 274 return False 275 return parent is not None and parent.status == 'okay' and parent.matching_compat == compat 276 elif ast[0] == "dt_chosen_enabled": 277 chosen = ast[1][0] 278 node = edt.chosen_node(chosen) 279 if node and node.status == "okay": 280 return True 281 return False 282 elif ast[0] == "dt_nodelabel_enabled": 283 label = ast[1][0] 284 node = edt.label2node.get(label) 285 if node and node.status == "okay": 286 return True 287 return False 288 289def ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, compat): 290 # Helper shared with the now deprecated 291 # dt_compat_enabled_with_alias version. 292 293 for node in edt.nodes: 294 parent = node.parent 295 if parent is None: 296 continue 297 if (node.status == "okay" and alias in node.aliases and 298 parent.matching_compat == compat): 299 return True 300 301 return False 302 303mutex = threading.Lock() 304 305def parse(expr_text, env, edt): 306 """Given a text representation of an expression in our language, 307 use the provided environment to determine whether the expression 308 is true or false""" 309 310 # Like it's C counterpart, state machine is not thread-safe 311 mutex.acquire() 312 try: 313 ast = parser.parse(expr_text) 314 finally: 315 mutex.release() 316 317 return ast_expr(ast, env, edt) 318 319# Just some test code 320if __name__ == "__main__": 321 322 local_env = { 323 "A" : "1", 324 "C" : "foo", 325 "D" : "20", 326 "E" : 0x100, 327 "F" : "baz" 328 } 329 330 331 for line in open(sys.argv[1]).readlines(): 332 lex.input(line) 333 for tok in iter(lex.token, None): 334 print(tok.type, tok.value) 335 336 parser = yacc.yacc() 337 print(parser.parse(line)) 338 339 print(parse(line, local_env, None)) 340