mirror of
https://github.com/egormanga/Slang.git
synced 2025-03-01 18:09:30 +03:00
315 lines
11 KiB
Python
315 lines
11 KiB
Python
#!/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
|