Last active
April 21, 2016 08:37
-
-
Save boncheff/37f451b2ae82cd0ef122895d6b856655 to your computer and use it in GitHub Desktop.
Python grammar parser using pyparsing module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import division | |
import re | |
from pyparsing import ( | |
Word, | |
alphas, | |
ParseException, | |
Literal, | |
CaselessLiteral, | |
Combine, | |
Optional, | |
nums, | |
Forward, | |
ZeroOrMore, | |
StringEnd, | |
alphanums) | |
exprStack = [] | |
varStack = [] | |
def pushFirst(str, loc, toks): | |
exprStack.append(toks[0]) | |
def assignVar(str, loc, toks): | |
varStack.append(toks[0]) | |
# define grammar | |
point = Literal('.') | |
e = CaselessLiteral('E') | |
plusorminus = Literal('+') | Literal('-') | |
number = Word(nums) | |
integer = Combine(Optional(plusorminus) + number) | |
floatnumber = Combine(integer + | |
Optional(point + Optional(number)) + | |
Optional(e + integer)) | |
ident = Word(alphas, alphanums + '_') | |
plus = Literal("+") | |
minus = Literal("-") | |
mult = Literal("*") | |
div = Literal("/") | |
lpar = Literal("(").suppress() | |
rpar = Literal(")").suppress() | |
addop = plus | minus | |
multop = mult | div | |
expop = Literal("^") | |
assign = Literal("=") | |
expr = Forward() | |
atom = ((e | floatnumber | integer | ident).setParseAction(pushFirst) | | |
(lpar + expr.suppress() + rpar) | |
) | |
factor = Forward() | |
factor << atom + ZeroOrMore((expop + factor).setParseAction(pushFirst)) | |
term = factor + ZeroOrMore((multop + factor).setParseAction(pushFirst)) | |
expr << term + ZeroOrMore((addop + term).setParseAction(pushFirst)) | |
bnf = Optional((ident + assign).setParseAction(assignVar)) + expr | |
pattern = bnf + StringEnd() | |
# map operator symbols to corresponding arithmetic operations | |
opn = {"+": (lambda a, b: a + b), | |
"-": (lambda a, b: a - b), | |
"*": (lambda a, b: a * b), | |
"/": (lambda a, b: a / b), | |
"^": (lambda a, b: a ** b)} | |
# Recursive function that evaluates the stack | |
def evaluateStack(s, variables): | |
op = s.pop() | |
if op in "+-*/^": | |
op2 = evaluateStack(s, variables) | |
op1 = evaluateStack(s, variables) | |
return opn[op](op1, op2) | |
elif re.search('^[a-zA-Z][a-zA-Z0-9_]*$', op): | |
if op in variables: | |
return variables[op] | |
# If a variable cannot be located, its value defaults to 0 | |
else: | |
return 0 | |
elif re.search('^[-+]?[0-9]+$', op): | |
return long(op) | |
else: | |
return float(op) | |
def do_parse(expr, variables): | |
""" expr is the conversion expression to evaluate | |
variables is a dictionary containing all the variables to replace | |
""" | |
try: | |
tokens = pattern.parseString(expr) | |
except ParseException, err: | |
tokens = ['Parse Failure', expr] | |
# show result of parsing the expr string | |
if len(tokens) == 0 or tokens[0] != 'Parse Failure': | |
result = evaluateStack(exprStack, variables) | |
else: | |
result = 'Parse Failure %s - %s' % (err.line, err) | |
return result | |
if __name__ == '__main__': | |
print do_parse('100 * cpu_user_ticks / (cpu_user_ticks + ' | |
'cpu_system_ticks + cpu_idle_ticks)', | |
{'cpu_user_ticks': 25, 'cpu_system_ticks': 43, | |
'cpu_idle_ticks': 400}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To run, pass a conversion expression and a dictionary containing the variables to replace and their values