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