zephyr/scripts/pylib/twister/expr_parser.py
Erwan Gouriou 3e1443ccf9 scripts/pylib: expr_parser.py: Review dt_compat_enabled_with_label
Function used for filtering "dt_compat_enabled_with_label" was not
working as expected as it was not taking into account that we're
looking for a children/parent combination:
Provided "compat" with enabled status should be the one of the parent
of the node matching given label.

Function is then reworked to take this into account.
And to make it's usage clear:
- function name is changed to be clearer on the intention
- args order is reversed to be more logical wrt the intention

Users of the function are also updated to take the change into
account.

Fixes #36093

Signed-off-by: Erwan Gouriou <erwan.gouriou@linaro.org>
2021-06-21 20:31:49 -04:00

330 lines
8 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_label_with_parent_compat_enabled":
compat = ast[1][1]
label = ast[1][0]
node = edt.label2node.get(label)
parent = node.parent
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
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))