| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2016 Intel Corporation. |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import copy |
| import logging |
| import os |
| import re |
| import sys |
| import threading |
| |
| try: |
| import ply.lex as lex |
| import ply.yacc as yacc |
| except ImportError: |
| sys.exit("PLY library for Python 3 not installed.\n" |
| "Please install the ply package using your workstation's\n" |
| "package manager or the 'pip' tool.") |
| |
| _logger = logging.getLogger('twister') |
| |
| reserved = { |
| 'and' : 'AND', |
| 'or' : 'OR', |
| 'not' : 'NOT', |
| 'in' : 'IN', |
| } |
| |
| tokens = [ |
| "HEX", |
| "STR", |
| "INTEGER", |
| "EQUALS", |
| "NOTEQUALS", |
| "LT", |
| "GT", |
| "LTEQ", |
| "GTEQ", |
| "OPAREN", |
| "CPAREN", |
| "OBRACKET", |
| "CBRACKET", |
| "COMMA", |
| "SYMBOL", |
| "COLON", |
| ] + list(reserved.values()) |
| |
| def t_HEX(t): |
| r"0x[0-9a-fA-F]+" |
| t.value = str(int(t.value, 16)) |
| return t |
| |
| def t_INTEGER(t): |
| r"\d+" |
| t.value = str(int(t.value)) |
| return t |
| |
| def t_STR(t): |
| r'\"([^\\\n]|(\\.))*?\"|\'([^\\\n]|(\\.))*?\'' |
| # nip off the quotation marks |
| t.value = t.value[1:-1] |
| return t |
| |
| t_EQUALS = r"==" |
| |
| t_NOTEQUALS = r"!=" |
| |
| t_LT = r"<" |
| |
| t_GT = r">" |
| |
| t_LTEQ = r"<=" |
| |
| t_GTEQ = r">=" |
| |
| t_OPAREN = r"[(]" |
| |
| t_CPAREN = r"[)]" |
| |
| t_OBRACKET = r"\[" |
| |
| t_CBRACKET = r"\]" |
| |
| t_COMMA = r"," |
| |
| t_COLON = ":" |
| |
| def t_SYMBOL(t): |
| r"[A-Za-z_][0-9A-Za-z_]*" |
| t.type = reserved.get(t.value, "SYMBOL") |
| return t |
| |
| t_ignore = " \t\n" |
| |
| def t_error(t): |
| raise SyntaxError("Unexpected token '%s'" % t.value) |
| |
| lex.lex() |
| |
| precedence = ( |
| ('left', 'OR'), |
| ('left', 'AND'), |
| ('right', 'NOT'), |
| ('nonassoc', 'EQUALS', 'NOTEQUALS', 'GT', 'LT', 'GTEQ', 'LTEQ', 'IN'), |
| ) |
| |
| def p_expr_or(p): |
| 'expr : expr OR expr' |
| p[0] = ("or", p[1], p[3]) |
| |
| def p_expr_and(p): |
| 'expr : expr AND expr' |
| p[0] = ("and", p[1], p[3]) |
| |
| def p_expr_not(p): |
| 'expr : NOT expr' |
| p[0] = ("not", p[2]) |
| |
| def p_expr_parens(p): |
| 'expr : OPAREN expr CPAREN' |
| p[0] = p[2] |
| |
| def p_expr_eval(p): |
| """expr : SYMBOL EQUALS const |
| | SYMBOL NOTEQUALS const |
| | SYMBOL GT number |
| | SYMBOL LT number |
| | SYMBOL GTEQ number |
| | SYMBOL LTEQ number |
| | SYMBOL IN list |
| | SYMBOL COLON STR""" |
| p[0] = (p[2], p[1], p[3]) |
| |
| def p_expr_single(p): |
| """expr : SYMBOL""" |
| p[0] = ("exists", p[1]) |
| |
| def p_func(p): |
| """expr : SYMBOL OPAREN arg_intr CPAREN""" |
| p[0] = [p[1]] |
| p[0].append(p[3]) |
| |
| def p_arg_intr_single(p): |
| """arg_intr : const""" |
| p[0] = [p[1]] |
| |
| def p_arg_intr_mult(p): |
| """arg_intr : arg_intr COMMA const""" |
| p[0] = copy.copy(p[1]) |
| p[0].append(p[3]) |
| |
| def p_list(p): |
| """list : OBRACKET list_intr CBRACKET""" |
| p[0] = p[2] |
| |
| def p_list_intr_single(p): |
| """list_intr : const""" |
| p[0] = [p[1]] |
| |
| def p_list_intr_mult(p): |
| """list_intr : list_intr COMMA const""" |
| p[0] = copy.copy(p[1]) |
| p[0].append(p[3]) |
| |
| def p_const(p): |
| """const : STR |
| | number""" |
| p[0] = p[1] |
| |
| def p_number(p): |
| """number : INTEGER |
| | HEX""" |
| p[0] = p[1] |
| |
| def p_error(p): |
| if p: |
| raise SyntaxError("Unexpected token '%s'" % p.value) |
| else: |
| raise SyntaxError("Unexpected end of expression") |
| |
| if "PARSETAB_DIR" not in os.environ: |
| parser = yacc.yacc(debug=0) |
| else: |
| parser = yacc.yacc(debug=0, outputdir=os.environ["PARSETAB_DIR"]) |
| |
| def ast_sym(ast, env): |
| if ast in env: |
| return str(env[ast]) |
| return "" |
| |
| def ast_sym_int(ast, env): |
| if ast in env: |
| v = env[ast] |
| if v.startswith("0x") or v.startswith("0X"): |
| return int(v, 16) |
| else: |
| return int(v, 10) |
| return 0 |
| |
| def ast_expr(ast, env, edt): |
| if ast[0] == "not": |
| return not ast_expr(ast[1], env, edt) |
| elif ast[0] == "or": |
| return ast_expr(ast[1], env, edt) or ast_expr(ast[2], env, edt) |
| elif ast[0] == "and": |
| return ast_expr(ast[1], env, edt) and ast_expr(ast[2], env, edt) |
| elif ast[0] == "==": |
| return ast_sym(ast[1], env) == ast[2] |
| elif ast[0] == "!=": |
| return ast_sym(ast[1], env) != ast[2] |
| elif ast[0] == ">": |
| return ast_sym_int(ast[1], env) > int(ast[2]) |
| elif ast[0] == "<": |
| return ast_sym_int(ast[1], env) < int(ast[2]) |
| elif ast[0] == ">=": |
| return ast_sym_int(ast[1], env) >= int(ast[2]) |
| elif ast[0] == "<=": |
| return ast_sym_int(ast[1], env) <= int(ast[2]) |
| elif ast[0] == "in": |
| return ast_sym(ast[1], env) in ast[2] |
| elif ast[0] == "exists": |
| return bool(ast_sym(ast[1], env)) |
| elif ast[0] == ":": |
| return bool(re.match(ast[2], ast_sym(ast[1], env))) |
| elif ast[0] == "dt_compat_enabled": |
| compat = ast[1][0] |
| for node in edt.nodes: |
| if compat in node.compats and node.status == "okay": |
| return True |
| return False |
| elif ast[0] == "dt_alias_exists": |
| alias = ast[1][0] |
| for node in edt.nodes: |
| if alias in node.aliases and node.status == "okay": |
| return True |
| return False |
| elif ast[0] == "dt_enabled_alias_with_parent_compat": |
| # Checks if the DT has an enabled alias node whose parent has |
| # a given compatible. For matching things like gpio-leds child |
| # nodes, which do not have compatibles themselves. |
| # |
| # The legacy "dt_compat_enabled_with_alias" form is still |
| # accepted but is now deprecated and causes a warning. This is |
| # meant to give downstream users some time to notice and |
| # adjust. Its argument order only made sense under the (bad) |
| # assumption that the gpio-leds child node has the same compatible |
| |
| alias = ast[1][0] |
| compat = ast[1][1] |
| |
| return ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, |
| compat) |
| elif ast[0] == "dt_compat_enabled_with_alias": |
| compat = ast[1][0] |
| alias = ast[1][1] |
| |
| _logger.warning('dt_compat_enabled_with_alias("%s", "%s"): ' |
| 'this is deprecated, use ' |
| 'dt_enabled_alias_with_parent_compat("%s", "%s") ' |
| 'instead', |
| compat, alias, alias, compat) |
| |
| return ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, |
| compat) |
| elif ast[0] == "dt_label_with_parent_compat_enabled": |
| compat = ast[1][1] |
| label = ast[1][0] |
| node = edt.label2node.get(label) |
| if node is not None: |
| parent = node.parent |
| else: |
| return False |
| return parent is not None and parent.status == 'okay' and parent.matching_compat == compat |
| elif ast[0] == "dt_chosen_enabled": |
| chosen = ast[1][0] |
| node = edt.chosen_node(chosen) |
| if node and node.status == "okay": |
| return True |
| return False |
| elif ast[0] == "dt_nodelabel_enabled": |
| label = ast[1][0] |
| node = edt.label2node.get(label) |
| if node and node.status == "okay": |
| return True |
| return False |
| |
| def ast_handle_dt_enabled_alias_with_parent_compat(edt, alias, compat): |
| # Helper shared with the now deprecated |
| # dt_compat_enabled_with_alias version. |
| |
| for node in edt.nodes: |
| parent = node.parent |
| if parent is None: |
| continue |
| if (node.status == "okay" and alias in node.aliases and |
| parent.matching_compat == compat): |
| return True |
| |
| return False |
| |
| mutex = threading.Lock() |
| |
| def parse(expr_text, env, edt): |
| """Given a text representation of an expression in our language, |
| use the provided environment to determine whether the expression |
| is true or false""" |
| |
| # Like it's C counterpart, state machine is not thread-safe |
| mutex.acquire() |
| try: |
| ast = parser.parse(expr_text) |
| finally: |
| mutex.release() |
| |
| return ast_expr(ast, env, edt) |
| |
| # Just some test code |
| if __name__ == "__main__": |
| |
| local_env = { |
| "A" : "1", |
| "C" : "foo", |
| "D" : "20", |
| "E" : 0x100, |
| "F" : "baz" |
| } |
| |
| |
| for line in open(sys.argv[1]).readlines(): |
| lex.input(line) |
| for tok in iter(lex.token, None): |
| print(tok.type, tok.value) |
| |
| parser = yacc.yacc() |
| print(parser.parse(line)) |
| |
| print(parse(line, local_env, None)) |