-
Notifications
You must be signed in to change notification settings - Fork 0
/
repl.py
314 lines (262 loc) · 10.6 KB
/
repl.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#!/usr/bin/python3
# Slang REPL
import readline
from .ast import *
from .lexer import *
from utils.nolog import *
def get_node_value(node, ns):
while (isinstance(node, ASTNode)):
node = execute_node(node, ns)
if (isiterablenostr(node)):
try: node = type(node)(get_node_value(i, ns) for i in node)
except Exception: pass
return node
@dispatch
def execute_node(node: ASTCodeNode, ns):
r = None
for i in node.nodes:
if (isinstance(i, ASTElseClauseNode) and r is not None): continue
r = execute_node(i, ns)
if (ns.flags.interactive and r not in (None, ...)):
ns.values['_'] = r
print(repr(r))
return r
@dispatch
def execute_node(node: ASTVardefNode, ns):
if (node.value is not None): ns.values[node.name] = get_node_value(node.value, ns)
@dispatch
def execute_node(node: ASTBlockNode, ns):
return execute_node(node.code, ns)
@dispatch
def execute_node(node: ASTFuncdefNode, ns):
ns.values[node.name.identifier] = node
@dispatch
def execute_node(node: ASTAssignmentNode, ns):
ns.values[node.name] = execute_node(ASTBinaryExprNode(node.name, node.inplace_operator, node.value, lineno=node.value.lineno, offset=node.value.offset), ns) if (node.inplace_operator is not None) else get_node_value(node.value, ns)
@dispatch
def execute_node(node: ASTUnaryOperationNode, ns):
def _op(): execute_node(ASTAssignmentNode(node.name, node.isattr, ASTSpecialNode('=', lineno=node.unary_operator.lineno, offset=node.unary_operator.offset), ASTOperatorNode(node.unary_operator.operator[0], lineno=node.unary_operator.lineno, offset=node.unary_operator.offset), ASTLiteralNode(1 if (node.unary_operator.operator[0] in '+-') else get_node_value(node.name, ns), lineno=node.unary_operator.lineno, offset=node.unary_operator.offset), lineno=node.lineno, offset=node.offset), ns)
if (isinstance(node, ASTUnaryPreOperationNode)): _op()
res = ns.values[node.name]
if (isinstance(node, ASTUnaryPostOperationNode)): _op()
return res
@dispatch
def execute_node(node: ASTItemgetNode, ns):
return get_node_value(node.value, ns)[get_node_value(node.key, ns)]
@dispatch
def execute_node(node: ASTAttrgetNode, ns):
if (node.optype.special == '.'):
if (isinstance(node.value.value, ASTIdentifierNode) and node.value.value.identifier == 'stdio'):
if (node.attr.identifier == 'println'): return stdlib.stdio.println
elif (node.attr.identifier == 'map'): return stdlib._map
elif (node.attr.identifier == 'each'): return stdlib._each
raise NotImplementedError(node)
@dispatch
def execute_node(node: ASTFunccallNode, ns):
func = execute_node(node.callable, ns)
if (isinstance(func, type) and issubclass(func, stdlib.Builtin)):
if (func is stdlib.stdio.println): f = print
elif (func is stdlib._map): f = lambda l: [execute_node(ASTFunccallNode(node.callable.value.value, ASTCallargsNode([i], [], lineno=node.callargs.lineno, offset=node.callargs.offset), ASTCallkwargsNode([], [], lineno=node.callkwargs.lineno, offset=node.callkwargs.offset), lineno=node.callable.lineno, offset=node.callable.offset), ns) for i in l]
elif (func is stdlib._each): f = lambda _: [execute_node(ASTFunccallNode(node.callargs.callargs[0].value, ASTCallargsNode([i], [], lineno=node.callargs.lineno, offset=node.callargs.offset), ASTCallkwargsNode([], [], lineno=node.callkwargs.lineno, offset=node.callkwargs.offset), lineno=node.callable.lineno, offset=node.callable.offset), ns) for i in get_node_value(node.callable.value.value, ns)]
else: raise NotImplementedError(func)
callarguments = CallArguments.build(node, ns)
assert (func.compatible_call(callarguments, ns) is not None)
return f(*(get_node_value(i, ns) for i in node.callargs.callargs),
*(get_node_value(j, ns) for i in node.callargs.starargs for j in get_node_value(i, ns)))
code_ns = ns.derive(str(node.callable), append=False)
for ii, i in enumerate(node.callargs.callargs):
code_ns.values[func.argdefs[ii].name] = get_node_value(i, ns)
return execute_node(func.code, code_ns)
@dispatch
def execute_node(node: ASTValueNode, ns):
return execute_node(node.value, ns)
@dispatch
def execute_node(node: ASTIdentifierNode, ns):
if (ns.values.get(node) is None): raise SlValidationError(f"{node} is not initialized", node, scope=ns.scope)
return ns.values[node]
@dispatch
def execute_node(node: ASTLiteralNode, ns):
if (isinstance(node.literal, str)):
try: return eval(node.literal)
except Exception as ex: raise SlReplError(ex, node, scope=ns.scope)
else: return node.literal
@dispatch
def execute_node(node: ASTListNode, ns):
return list(node.values)
@dispatch
def execute_node(node: ASTTupleNode, ns):
return tuple(node.values)
@dispatch
def execute_node(node: ASTKeywordExprNode, ns):
if (node.keyword.keyword == 'return'): return execute_node(node.value, ns)
elif (node.keyword.keyword == 'delete'): ns.delete(node.value)
else: raise NotImplementedError(node.keyword)
@dispatch
def execute_node(node: ASTKeywordDefNode, ns):
if (node.keyword.keyword == 'main'):
execute_node(node.code, ns)
@dispatch
def execute_node(node: ASTConditionalNode, ns):
if (execute_node(node.condition, ns)):
execute_node(node.code, ns)
else: return
return ...
@dispatch
def execute_node(node: ASTForLoopNode, ns):
ns.define(node.name, Signature.build(node.iterable, ns).valtype)
ns.weaken(node.name)
for i in execute_node(node.iterable, ns):
ns.values[node.name] = get_node_value(i, ns)
execute_node(node.code, ns)
else: return
return ...
@dispatch
def execute_node(node: ASTWhileLoopNode, ns):
while (get_node_value(execute_node(node.condition, ns), ns)):
execute_node(node.code, ns)
else: return
return ...
@dispatch
def execute_node(node: ASTElseClauseNode, ns):
execute_node(node.code, ns)
@dispatch
def execute_node(node: ASTUnaryExprNode, ns):
value = get_node_value(node.value, ns)
try: return eval(f"{node.operator.operator} value")
except Exception as ex: raise SlReplError(ex, node, scope=ns.scope)
@dispatch
def execute_node(node: ASTBinaryExprNode, ns):
lvalue = get_node_value(node.lvalue, ns)
rvalue = get_node_value(node.rvalue, ns)
if (node.operator.operator == 'xor'):
try: return eval("(lvalue and not rvalue) or (rvalue and not lvalue)")
except Exception as ex: raise SlReplError(ex, node, scope=ns.scope)
elif (node.operator.operator == 'to'):
try: return range(lvalue, rvalue)
except Exception as ex: raise SlReplError(ex, node, scope=ns.scope)
else:
try: return eval(f"lvalue {node.operator.operator} rvalue")
except Exception as ex: raise SlReplError(ex, node, scope=ns.scope)
class Completer:
def __init__(self, namespace):
self.namespace = namespace
def complete(self, text, state):
if (state == 0):
if ('.' in text): self.matches = self.attr_matches(text)
else: self.matches = self.global_matches(text)
try: return self.matches[state]
except IndexError: return None
def _callable_postfix(self, val, word):
if (isinstance(val, ASTCallableNode)): word += '('
return word
def global_matches(self, text):
matches = list()
seen = set()
n = len(text)
for word in keywords:
if (word[:n] != text): continue
seen.add(word)
matches.append(word+' ')
for word, val in self.namespace.values.items():
if (word[:n] != text or word in seen): continue
seen.add(word)
matches.append(self._callable_postfix(val, word))
return matches
def attr_matches(self, text):
m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
if (m is None): return ()
expr, attr = m.group(1, 3)
try: obj = self.namespace.values[expr] # TODO FIXME
except KeyError: return ()
words = set() # TODO FIXME attrs
matches = list()
n = len(attr)
if (attr == ''): noprefix = '_'
elif (attr == '_'): noprefix = '__'
else: noprefix = None
while (True):
for word in words:
if (word[:n] != attr or (noprefix and word[:n+1] == noprefix)): continue
match = f"{expr}.{word}"
try: val = getattr(obj, word)
except Exception: pass # Include even if attribute not set
else: match = self._callable_postfix(val, match)
matches.append(match)
if (matches or not noprefix): break
if (noprefix == '_'): noprefix = '__'
else: noprefix = None
matches.sort()
return matches
class SlReplError(SlNodeException):
ex: ...
def __init__(self, ex, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ex = ex
def __exline__(self):
return "Repl error"
def __exsubline__(self):
return f"\n\033[1;91mException\033[0m:\n "+'\n '.join(traceback.format_exception_only(type(self.ex), self.ex))
def repl(*, optimize=0):
ns = Namespace('<repl>')
ns.define(ASTIdentifierNode('_', lineno=None, offset=None), stdlib.Any)
ns.flags.interactive = True
completer = Completer(ns)
histfile = os.path.expanduser('~/.sli_history')
try: readline.read_history_file(histfile)
except FileNotFoundError: pass
for i in (
'set colored-completion-prefix on',
'set enable-bracketed-paste on',
#'set horizontal-scroll-mode on',
'set skip-completed-text on',
'tab: complete',
): readline.parse_and_bind(i)
readline.set_completer(completer.complete)
#readline.set_completion_display_matches_hook(completer.display) # TODO
l = list()
tl = list()
try:
while (True):
try:
l.append(input(f"\1\033[1;93m\2{'...' if (tl) else '>>>'}\1\033[0m\2 "))
tll = parse_string(l[-1], lnooff=len(l)-1)
if (not tll): l.pop(); continue
tl += tll
if (tl[-1][-1].token == '\\'): continue
#if (len(tl) >= 2 and tl[-2][-1].token == '\\'): tl[-1] = tl[-2][:-1]+tl.pop() # TODO FIXME?: [['a', '+', '\\'], 'b'] --> [['a', '+', 'b']]
if (tl[0][-1].token == '{' and tl[-1][-1].token != '}'): continue
ast = build_ast(tl, interactive=True)
if (optimize): optimize_ast(ast, validate_ast(ast), optimize)
validate_ast(ast, ns)
execute_node(ast.code, ns)
except KeyboardInterrupt:
buf = readline.get_line_buffer()
print(f"\r\033[2m^C{'> '+buf if (buf) else ' '}\033[0m")
except EOFError:
print(end='\r\033[K')
break
except (SlSyntaxException, SlNodeException) as ex:
if (not ex.srclines): ex.srclines = l
print(ex)
tl.clear()
l.clear()
finally: readline.write_history_file(histfile)
def run_file(file, *, optimize=0):
src = file.read()
try:
tl = parse_string(src)
ast = build_ast(tl, file.name.join('""'))
if (optimize): optimize_ast(ast, validate_ast(ast), optimize)
ns = validate_ast(ast)
execute_node(ast.code, ns)
except (SlSyntaxException, SlNodeException) as ex:
if (not ex.srclines): ex.srclines = src.split('\n')
sys.exit(str(ex))
@apmain
@aparg('file', metavar='file.sl', nargs='?', type=argparse.FileType('r'))
@aparg('-O', metavar='level', help='Code optimization level', type=int, default=DEFAULT_OLEVEL)
def main(cargs):
if (cargs.file is not None): run_file(cargs.file, optimize=cargs.O)
else: repl(optimize=cargs.O)
if (__name__ == '__main__'): main(nolog=True)
# by Sdore, 2020