1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#######################################################################
# Name: calc.py
# Purpose: Simple expression evaluator example
# Author: Igor R. Dejanovic <igor DOT dejanovic AT gmail DOT com>
# Copyright: (c) 2009 Igor R. Dejanovic <igor DOT dejanovic AT gmail DOT com>
# License: MIT License
#
# This example demonstrates grammar definition using python constructs as
# well as using semantic actions to evaluate simple expression in infix
# notation.
#######################################################################
from arpeggio import *
from arpeggio.export import PMDOTExport, PTDOTExport
from arpeggio import RegExMatch as _
import logging
def number(): return _(r'\d*\.\d*|\d+')
def factor(): return [number, ("(", expression, ")")]
def term(): return factor, ZeroOrMore(["*","/"], factor)
def expression(): return Optional(["+","-"]), term, ZeroOrMore(["+", "-"], term)
def calc(): return expression, EndOfFile
# Semantic actions
class ToFloat(SemanticAction):
'''Converts node value to float.'''
def first_pass(self, parser, node, nodes):
logging.debug("Converting %s." % node.value)
return float(node.value)
class Factor(SemanticAction):
'''Removes parenthesis if exists and returns what was contained inside.'''
def first_pass(self, parser, node, nodes):
logging.debug("Factor %s" % nodes)
if nodes[0] == "(":
return nodes[1]
else:
return nodes[0]
class Term(SemanticAction):
'''
Divides or multiplies factors.
Factor nodes will be already evaluated.
'''
def first_pass(self, parser, node, nodes):
logging.debug("Term %s" % nodes)
term = nodes[0]
for i in range(2, len(nodes), 2):
if nodes[i-1]=="*":
term *= nodes[i]
else:
term /= nodes[i]
logging.debug("Term = %f" % term)
return term
class Expr(SemanticAction):
'''
Adds or substracts terms.
Term nodes will be already evaluated.
'''
def first_pass(self, parser, node, nodes):
logging.debug("Expression %s" % nodes)
expr = 0
start = 0
# Check for unary + or - operator
if str(nodes[0]) in "+-":
start = 1
for i in range(start, len(nodes), 2):
if i and nodes[i-1]=="-":
expr -= nodes[i]
else:
expr += nodes[i]
logging.debug("Expression = %f" % expr)
return expr
class Calc(SemanticAction):
def first_pass(self, parser, node, nodes):
return nodes[0]
# Connecting rules with semantic actions
number.sem = ToFloat()
factor.sem = Factor()
term.sem = Term()
expression.sem = Expr()
calc.sem = Calc()
if __name__ == "__main__":
try:
logging.basicConfig(level=logging.DEBUG)
# First we will make a parser - an instance of the calc parser model.
# Parser model is given in the form of python constructs therefore we
# are using ParserPython class.
parser = ParserPython(calc)
# Then we export it to a dot file in order to visualise it. This is
# particulary handy for debugging purposes.
# We can make a jpg out of it using dot (part of graphviz) like this
# dot -O -Tjpg calc_parse_tree_model.dot
PMDOTExport().exportFile(parser.parser_model,
"calc_parse_tree_model.dot")
# An expression we want to evaluate
input = "-(4-1)*5+(2+4.67)+5.89/(.2+7)"
# We create a parse tree or abstract syntax tree out of textual input
parse_tree = parser.parse(input)
# Then we export it to a dot file in order to visualise it.
PTDOTExport().exportFile(parse_tree,
"calc_parse_tree.dot")
# getASG will start semantic analysis.
# In this case semantic analysis will evaluate expression and
# returned value will be the result of the input expression.
print "%s = %f" % (input, parser.getASG())
except NoMatch, e:
print "Expected %s at position %s." % (e.value, str(e.parser.pos_to_linecol(e.position)))