Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright (c) 2016 Intel Corporation. |
| 4 | # |
David B. Kinder | ac74d8b | 2017-01-18 17:01:01 -0800 | [diff] [blame] | 5 | # SPDX-License-Identifier: Apache-2.0 |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 6 | |
| 7 | import sys |
| 8 | import os |
| 9 | import copy |
Andrew Boie | 99c0f64 | 2016-06-02 12:22:41 -0700 | [diff] [blame] | 10 | import threading |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 11 | import re |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 12 | |
| 13 | try: |
| 14 | import ply.lex as lex |
| 15 | import ply.yacc as yacc |
| 16 | except ImportError: |
| 17 | print("PLY library for Python 3 not installed.") |
| 18 | print("Please install the python3-ply package using your workstation's") |
| 19 | print("package manager or the 'pip' tool.") |
| 20 | sys.exit(1) |
| 21 | |
| 22 | reserved = { |
| 23 | 'and' : 'AND', |
| 24 | 'or' : 'OR', |
| 25 | 'not' : 'NOT', |
| 26 | 'in' : 'IN', |
| 27 | } |
| 28 | |
| 29 | tokens = [ |
| 30 | "HEX", |
| 31 | "STR", |
| 32 | "INTEGER", |
| 33 | "EQUALS", |
| 34 | "NOTEQUALS", |
| 35 | "LT", |
| 36 | "GT", |
| 37 | "LTEQ", |
| 38 | "GTEQ", |
| 39 | "OPAREN", |
| 40 | "CPAREN", |
| 41 | "OBRACKET", |
| 42 | "CBRACKET", |
| 43 | "COMMA", |
| 44 | "SYMBOL", |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 45 | "COLON", |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 46 | ] + list(reserved.values()) |
| 47 | |
| 48 | def t_HEX(t): |
| 49 | r"0x[0-9a-fA-F]+" |
| 50 | t.value = str(int(t.value, 16)) |
| 51 | return t |
| 52 | |
| 53 | def t_INTEGER(t): |
| 54 | r"\d+" |
| 55 | t.value = str(int(t.value)) |
| 56 | return t |
| 57 | |
| 58 | def t_STR(t): |
Andrew Boie | 5aaf5f1 | 2016-06-02 12:21:41 -0700 | [diff] [blame] | 59 | r'\"([^\\\n]|(\\.))*?\"|\'([^\\\n]|(\\.))*?\'' |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 60 | # nip off the quotation marks |
| 61 | t.value = t.value[1:-1] |
| 62 | return t |
| 63 | |
| 64 | t_EQUALS = r"==" |
| 65 | |
| 66 | t_NOTEQUALS = r"!=" |
| 67 | |
| 68 | t_LT = r"<" |
| 69 | |
| 70 | t_GT = r">" |
| 71 | |
| 72 | t_LTEQ = r"<=" |
| 73 | |
| 74 | t_GTEQ = r">=" |
| 75 | |
| 76 | t_OPAREN = r"[(]" |
| 77 | |
| 78 | t_CPAREN = r"[)]" |
| 79 | |
| 80 | t_OBRACKET = r"\[" |
| 81 | |
| 82 | t_CBRACKET = r"\]" |
| 83 | |
| 84 | t_COMMA = r"," |
| 85 | |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 86 | t_COLON = ":" |
| 87 | |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 88 | def t_SYMBOL(t): |
| 89 | r"[A-Za-z_][0-9A-Za-z_]*" |
| 90 | t.type = reserved.get(t.value, "SYMBOL") |
| 91 | return t |
| 92 | |
| 93 | t_ignore = " \t\n" |
| 94 | |
| 95 | def t_error(t): |
| 96 | raise SyntaxError("Unexpected token '%s'" % t.value) |
| 97 | |
| 98 | lex.lex() |
| 99 | |
| 100 | precedence = ( |
| 101 | ('left', 'OR'), |
| 102 | ('left', 'AND'), |
| 103 | ('right', 'NOT'), |
| 104 | ('nonassoc' , 'EQUALS', 'NOTEQUALS', 'GT', 'LT', 'GTEQ', 'LTEQ', 'IN'), |
| 105 | ) |
| 106 | |
| 107 | def p_expr_or(p): |
| 108 | 'expr : expr OR expr' |
| 109 | p[0] = ("or", p[1], p[3]) |
| 110 | |
| 111 | def p_expr_and(p): |
| 112 | 'expr : expr AND expr' |
| 113 | p[0] = ("and", p[1], p[3]) |
| 114 | |
| 115 | def p_expr_not(p): |
| 116 | 'expr : NOT expr' |
| 117 | p[0] = ("not", p[2]) |
| 118 | |
| 119 | def p_expr_parens(p): |
| 120 | 'expr : OPAREN expr CPAREN' |
| 121 | p[0] = p[2] |
| 122 | |
| 123 | def p_expr_eval(p): |
| 124 | """expr : SYMBOL EQUALS const |
| 125 | | SYMBOL NOTEQUALS const |
| 126 | | SYMBOL GT number |
| 127 | | SYMBOL LT number |
| 128 | | SYMBOL GTEQ number |
| 129 | | SYMBOL LTEQ number |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 130 | | SYMBOL IN list |
| 131 | | SYMBOL COLON STR""" |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 132 | p[0] = (p[2], p[1], p[3]) |
| 133 | |
| 134 | def p_expr_single(p): |
| 135 | """expr : SYMBOL""" |
| 136 | p[0] = ("exists", p[1]) |
| 137 | |
| 138 | def p_list(p): |
| 139 | """list : OBRACKET list_intr CBRACKET""" |
| 140 | p[0] = p[2] |
| 141 | |
| 142 | def p_list_intr_single(p): |
| 143 | """list_intr : const""" |
| 144 | p[0] = [p[1]] |
| 145 | |
| 146 | def p_list_intr_mult(p): |
| 147 | """list_intr : list_intr COMMA const""" |
| 148 | p[0] = copy.copy(p[1]) |
| 149 | p[0].append(p[3]) |
| 150 | |
| 151 | def p_const(p): |
| 152 | """const : STR |
| 153 | | number""" |
| 154 | p[0] = p[1] |
| 155 | |
| 156 | def p_number(p): |
| 157 | """number : INTEGER |
| 158 | | HEX""" |
| 159 | p[0] = p[1] |
| 160 | |
| 161 | def p_error(p): |
| 162 | if p: |
| 163 | raise SyntaxError("Unexpected token '%s'" % p.value) |
| 164 | else: |
| 165 | raise SyntaxError("Unexpected end of expression") |
| 166 | |
| 167 | parser = yacc.yacc() |
| 168 | |
| 169 | def ast_sym(ast, env): |
| 170 | if ast in env: |
| 171 | return str(env[ast]) |
| 172 | return "" |
| 173 | |
| 174 | def ast_sym_int(ast, env): |
| 175 | if ast in env: |
Andrew Boie | 31e772d | 2017-04-18 11:22:50 -0700 | [diff] [blame] | 176 | v = env[ast] |
| 177 | if v.startswith("0x") or v.startswith("0X"): |
| 178 | return int(v, 16) |
| 179 | else: |
| 180 | return int(v, 10) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 181 | return 0 |
| 182 | |
| 183 | def ast_expr(ast, env): |
| 184 | if ast[0] == "not": |
| 185 | return not ast_expr(ast[1], env) |
| 186 | elif ast[0] == "or": |
| 187 | return ast_expr(ast[1], env) or ast_expr(ast[2], env) |
| 188 | elif ast[0] == "and": |
| 189 | return ast_expr(ast[1], env) and ast_expr(ast[2], env) |
| 190 | elif ast[0] == "==": |
| 191 | return ast_sym(ast[1], env) == ast[2] |
| 192 | elif ast[0] == "!=": |
| 193 | return ast_sym(ast[1], env) != ast[2] |
| 194 | elif ast[0] == ">": |
| 195 | return ast_sym_int(ast[1], env) > int(ast[2]) |
| 196 | elif ast[0] == "<": |
| 197 | return ast_sym_int(ast[1], env) < int(ast[2]) |
| 198 | elif ast[0] == ">=": |
| 199 | return ast_sym_int(ast[1], env) >= int(ast[2]) |
| 200 | elif ast[0] == "<=": |
| 201 | return ast_sym_int(ast[1], env) <= int(ast[2]) |
| 202 | elif ast[0] == "in": |
| 203 | return ast_sym(ast[1], env) in ast[2] |
| 204 | elif ast[0] == "exists": |
| 205 | return True if ast_sym(ast[1], env) else False |
Andrew Boie | 7a87f9f | 2016-06-02 12:27:54 -0700 | [diff] [blame] | 206 | elif ast[0] == ":": |
| 207 | return True if re.compile(ast[2]).match(ast_sym(ast[1], env)) else False |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 208 | |
Andrew Boie | 99c0f64 | 2016-06-02 12:22:41 -0700 | [diff] [blame] | 209 | mutex = threading.Lock() |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 210 | |
| 211 | def parse(expr_text, env): |
| 212 | """Given a text representation of an expression in our language, |
| 213 | use the provided environment to determine whether the expression |
| 214 | is true or false""" |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 215 | |
Andrew Boie | 99c0f64 | 2016-06-02 12:22:41 -0700 | [diff] [blame] | 216 | # Like it's C counterpart, state machine is not thread-safe |
| 217 | mutex.acquire() |
| 218 | try: |
| 219 | ast = parser.parse(expr_text) |
| 220 | finally: |
| 221 | mutex.release() |
| 222 | |
| 223 | return ast_expr(ast, env) |
Andrew Boie | b4efd54 | 2016-03-24 13:45:46 -0700 | [diff] [blame] | 224 | |
| 225 | # Just some test code |
| 226 | if __name__ == "__main__": |
| 227 | |
| 228 | local_env = { |
| 229 | "A" : "1", |
| 230 | "C" : "foo", |
| 231 | "D" : "20", |
| 232 | "E" : 0x100, |
| 233 | "F" : "baz" |
| 234 | } |
| 235 | |
| 236 | |
| 237 | for line in open(sys.argv[1]).readlines(): |
| 238 | lex.input(line) |
| 239 | for tok in iter(lex.token, None): |
| 240 | print(tok.type, tok.value) |
| 241 | |
| 242 | parser = yacc.yacc() |
| 243 | print(parser.parse(line)) |
| 244 | |
| 245 | print(parse(line, local_env)) |
| 246 | |
| 247 | |
| 248 | |
| 249 | |