Slang/repl.py

209 lines
5.9 KiB
Python

#!/usr/bin/python3
# Slang REPL
import readline
from .ast import *
from .lexer import *
from utils.nolog import *
@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 (r not in (None, ...)):
ns.values['_'] = r
print(repr(r))
else: return
return r
@dispatch
def execute_node(node: ASTVardefNode, ns):
ns.values[node.name] = node.value
@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.argdefs, node.code)
@dispatch
def execute_node(node: ASTAssignmentNode, ns):
ns.values[node.name] = execute_node(ASTBinaryExprNode(node.name, node.inplace_operator, node.value, lineno=node.lineno, offset=node.offset), ns) if (node.inplace_operator is not None) else node.value
@dispatch
def execute_node(node: ASTFunccallNode, ns):
code_ns = ns.derive(node.callable.value.identifier)
argdefs, func = ns.values[node.callable]
for ii, i in enumerate(node.callargs.callargs):
code_ns.values[argdefs[ii].name] = i
return execute_node(func, code_ns)
@dispatch
def execute_node(node: ASTValueNode, ns):
return execute_node(node.value, ns)
@dispatch
def execute_node(node: ASTIdentifierNode, ns):
try: return ns.values[node]
except KeyError: raise SlValidationError(f"{node} is not initialized", node, scope=ns.scope)
@dispatch
def execute_node(node: ASTLiteralNode, ns):
return eval(node.literal) if (isinstance(node.literal, str)) else node.literal
@dispatch
def execute_node(node: ASTListNode, ns):
return node.values
@dispatch
def execute_node(node: ASTKeywordExprNode, ns):
#if (node.keyword.keyword == 'return'): return execute_node(node.value, ns) # TODO FIXME???
#el
if (node.keyword.keyword == 'delete'): ns.delete(node.value)
else: raise NotImplementedError(node.keyword)
@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):
for i in execute_node(node.iterable, ns):
execute_node(node.code, ns)
else: return
return ...
@dispatch
def execute_node(node: ASTUnaryExprNode, ns):
value = execute_node(node.value, ns)
return eval(f"{node.operator.operator} value")
@dispatch
def execute_node(node: ASTBinaryExprNode, ns):
lvalue = execute_node(node.lvalue, ns)
rvalue = execute_node(node.rvalue, ns)
if (node.operator.operator == 'xor'): return eval("(lvalue and not rvalue) or (rvalue and not lvalue)")
elif (node.operator.operator == 'to'): return range(lvalue, rvalue)
else: return eval(f"lvalue {node.operator.operator} rvalue")
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
def repl():
ns = Namespace('<repl>')
ns.define(ASTIdentifierNode('_', lineno=None, offset=None), stdlib.Any())
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)
validate_ast(ast, ns)
optimize_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 (SlSyntaxError, SlValidationError) as ex:
ex.line = l[ex.lineno-1]
print(ex)
tl.clear()
l.clear()
finally: readline.write_history_file(histfile)
if (__name__ == '__main__'): repl()
# by Sdore, 2020