/
Command.py
159 lines (145 loc) · 5.51 KB
/
Command.py
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
from pyparsing import quotedString, Word, alphas, alphanums, Suppress,\
Optional, Forward, OneOrMore, ZeroOrMore, nums, ParseFatalException,\
Token
import re, inspect, types, pickle, exceptions
from Utils import enum, find, httpGet
import os.path
TokenType = enum("StrLit", "NumLit", "Var", "Call", "Def", "End")
def getEbnfParser(symbols):
""" Returns an EBNF parser for the command language. """
identifier = Word(alphas + '_', alphanums + '_')
string = quotedString.setParseAction(
lambda t: symbols.append((t[0][1:-1], TokenType.StrLit))
)
integer = Word(nums).setParseAction(
lambda t: symbols.append((int(t[0]), TokenType.NumLit))
)
var = Suppress("$") + identifier
var.setParseAction(
lambda t: symbols.append((t[0], TokenType.Var))
)
literal = var | string | integer
fnid = Suppress(Optional(".")) + identifier
fnid.setParseAction(
lambda t: symbols.append((t[0], TokenType.Call))
)
call = Forward()
callb = fnid + ZeroOrMore(call | literal)
call << ((Suppress("(") + callb + Suppress(")")) | callb)
fndef_head = Suppress("let") + identifier
fndef_head.setParseAction(
lambda t: symbols.append((t[0], TokenType.Def))
)
definition = fndef_head + ZeroOrMore(var) + Suppress("=") + call
cmd = OneOrMore((definition | call) + Word(";").setParseAction(
lambda t: symbols.append((t[0], TokenType.End))
))
msg = OneOrMore(cmd)
return msg
def isLiteral(tk):
""" Checks whether a given token is a literal. """
t = tk[1]
return t == TokenType.StrLit or t == TokenType.NumLit
class Interpreter:
function_table = {
"id" : lambda x: x,
"uc" : lambda x: x.upper(),
"lc" : lambda x: x.lower(),
"add" : lambda x, y: x + y,
"mul" : lambda x, y: x * y,
"div" : lambda x, y: x / y,
"pow" : lambda x, y: x ** y,
"nil" : lambda x: None,
"grep" : lambda ex, s: re.search(ex, s).group(),
"grepi" : lambda ex, s, i: re.search(ex, s).groups()[i],
"get" : lambda url: httpGet(url)
}
def __init__(self, filename):
"""
Constructor, just creates a functions file if it doesn't exist.
"""
self._filename = filename
self.user_function_table = {}
self._tokens = []
if not os.path.isfile(filename):
self.saveUserFunctions()
def fnGetArgc(self, name):
"""
Gets the amount of arguments a function given by its name takes.
"""
if name in self.function_table:
return len(inspect.getargspec(self.function_table[name]).args)
else:
return len(self.user_function_table[name][0])
def getArgTuple(self, arg):
"""
Gets the tuple (value, TokenType) associated with a given argument.
"""
if type(arg) == types.StringType:
return (arg, TokenType.StrLit)
else:
return (arg, TokenType.NumLit)
def fnCall(self, name, args):
"""Calls a function given its name and arguments."""
if name in self.function_table:
return self.function_table[name](*args)
# user defined function: insert code to execute in tokens
fn = self.user_function_table[name]
for x in fn[1]:
if x[1] == TokenType.Var:
index = find(x[0], fn[0])
if index == None:
# TODO: ERROR
pass
self._tokens.insert(0, self.getArgTuple(args[index]))
else:
self._tokens.insert(0, x)
return self.interpretStatement()
def fnDef(self, name):
"""Handles function definition."""
args = []
while self._tokens[0][1] == TokenType.Var:
args.append(self._tokens.pop(0)[0])
code = []
while self._tokens[0][1] != TokenType.End:
code.append(self._tokens.pop(0))
code.reverse()
self.user_function_table[name] = (args, code)
def interpretStatement(self):
""" Interprets a single statement. """
head = self._tokens.pop(0)
if isLiteral(head):
return head[0]
if head[1] == TokenType.Def:
return self.fnDef(head[0])
argc = self.fnGetArgc(head[0])
args = [None] * argc
for j in range(argc):
args[j] = self.interpretStatement()
return self.fnCall(head[0], args)
def saveUserFunctions(self):
""" Saves the list of user-defined functions using pickle. """
with open(self._filename, "wb") as f:
pickle.dump(self.user_function_table, f)
def loadUserFunctions(self):
""" Loads the list of user-defined functions using pickle. """
with open(self._filename, "rb") as f:
self.user_function_table = pickle.load(f)
def interpret(self, tokens):
""" Interprets a (multi-line) program. """
self.loadUserFunctions()
last, end_token = 0, (";", TokenType.End)
find_end = lambda: find(end_token, tokens[last:]) + last + 1
output = []
amount = tokens.count(end_token)
for i in range(amount):
new = find_end()
try:
self._tokens = tokens[last:new]
output.append(self.interpretStatement())
except:
output.append("Oops, seems like something went wrong.")
raise
last, new = new, find_end()
self.saveUserFunctions()
return output