#!/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('') 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