330 lines
8.0 KiB
Python
330 lines
8.0 KiB
Python
#!/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_compat_enabled_with_label":
|
|
compat = ast[1][0]
|
|
label = ast[1][1]
|
|
node = edt.label2node.get(label)
|
|
return node is not None and node.status == 'okay' and node.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
|
|
|
|
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))
|