#!/usr/bin/env python3 # # Copyright (c) 2016 Intel Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import os import copy import threading import re try: import ply.lex as lex import ply.yacc as yacc except ImportError: print("PLY library for Python 3 not installed.") print("Please install the python3-ply package using your workstation's") print("package manager or the 'pip' tool.") sys.exit(1) 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_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") parser = yacc.yacc() def ast_sym(ast, env): if ast in env: return str(env[ast]) return "" def ast_sym_int(ast, env): if ast in env: return int(env[ast]) return 0 def ast_expr(ast, env): if ast[0] == "not": return not ast_expr(ast[1], env) elif ast[0] == "or": return ast_expr(ast[1], env) or ast_expr(ast[2], env) elif ast[0] == "and": return ast_expr(ast[1], env) and ast_expr(ast[2], env) 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 True if ast_sym(ast[1], env) else False elif ast[0] == ":": return True if re.compile(ast[2]).match(ast_sym(ast[1], env)) else False mutex = threading.Lock() def parse(expr_text, env): """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) # 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))