From d10df10282ff4c365881239a01d65b27ee7c5916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Wed, 10 Jul 2019 12:18:13 +0200 Subject: [PATCH] Add call stack to fix 'return' statement issue --- smnp/environment/environment.py | 20 +++++++++++++++++++- smnp/error/runtime.py | 3 ++- smnp/module/system/function/debug.py | 3 +++ smnp/runtime/evaluator.py | 3 +++ smnp/runtime/evaluators/function.py | 19 +++++++++++++------ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/smnp/environment/environment.py b/smnp/environment/environment.py index 1c60587..115bf81 100644 --- a/smnp/environment/environment.py +++ b/smnp/environment/environment.py @@ -11,6 +11,7 @@ class Environment(): self.methods = methods self.customFunctions = [] self.customMethods = [] + self.callStack = [] def invokeMethod(self, object, name, args): builtinMethodResult = self._invokeBuiltinMethod(object, name, args) @@ -26,7 +27,9 @@ class Environment(): def _invokeBuiltinMethod(self, object, name, args): for method in self.methods: if method.name == name: + self.callStack.append(CallStackItem(name)) ret = method.call(self, [object, *args]) + self.callStack.pop(-1) if ret is not None: return (True, ret) @@ -39,7 +42,9 @@ class Environment(): if signatureCheckresult[0]: self.scopes.append({argName: argValue for argName, argValue in zip(method.arguments, list(signatureCheckresult[1:]))}) self.scopes[-1][method.alias] = object + self.callStack.append(CallStackItem(name)) result = BodyEvaluator.evaluate(method.body, self).value # TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult + self.callStack.pop(-1) self.scopes.pop(-1) return (True, result) raise IllegalFunctionInvocationException(f"{method.name}{method.signature.string}", @@ -60,7 +65,9 @@ class Environment(): def _invokeBuiltinFunction(self, name, args): for function in self.functions: if function.name == name: + self.callStack.append(CallStackItem(name)) ret = function.call(self, args) + self.callStack.pop(-1) if ret is not None: return (True, ret) @@ -72,7 +79,9 @@ class Environment(): signatureCheckresult = function.signature.check(args) if signatureCheckresult[0]: self.scopes.append({ argName: argValue for argName, argValue in zip(function.arguments, list(signatureCheckresult[1:])) }) + self.callStack.append(CallStackItem(name)) result = BodyEvaluator.evaluate(function.body, self).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult + self.callStack.pop(-1) self.scopes.pop(-1) return (True, result) raise IllegalFunctionInvocationException(f"{function.name}{function.signature.string}", f"{name}{types(args)}") @@ -138,6 +147,9 @@ class Environment(): def customMethodsToString(self): return "Custom Methods:\n" + ("\n".join([ f" {function.name}(...)" for function in self.customMethods ])) + def callStackToString(self): + return "Call Stack:\n" + ("\n".join([ f" [{i}]: {function.function}(...)" for i, function in reversed(list(enumerate(self.callStack))) ])) + def extend(self, environment): self.scopes[0].update(environment.scopes[0]) self.customFunctions.extend(environment.customFunctions) @@ -145,12 +157,18 @@ class Environment(): def __str__(self): - return f"{self.scopesToString()}\n{self.functionsToString()}\n{self.methodsToString()}\n{self.customFunctionsToString()}\n{self.customMethodsToString()}" + return f"{self.scopesToString()}\n{self.functionsToString()}\n{self.methodsToString()}\n{self.customFunctionsToString()}\n{self.customMethodsToString()}\n{self.callStackToString()}" def __repr__(self): return self.__str__() +class CallStackItem: + def __init__(self, function): + self.function = function + self.value = None + + class CustomFunction: def __init__(self, name, signature, arguments, body): self.name = name diff --git a/smnp/error/runtime.py b/smnp/error/runtime.py index 852b397..80c8715 100644 --- a/smnp/error/runtime.py +++ b/smnp/error/runtime.py @@ -2,8 +2,9 @@ from smnp.error.base import SmnpException class RuntimeException(SmnpException): - def __init__(self, msg, pos): + def __init__(self, msg, pos, environment=None): super().__init__(msg, pos) + self.environment = environment def _title(self): return "Runtime Error" diff --git a/smnp/module/system/function/debug.py b/smnp/module/system/function/debug.py index 6a94091..100ddfd 100644 --- a/smnp/module/system/function/debug.py +++ b/smnp/module/system/function/debug.py @@ -18,6 +18,9 @@ def _function(env, parameter): elif parameter.value == "methods": print(env.methodsToString()) return + elif parameter.value == "callstack": + print(env.callStackToString()) + return raise IllegalArgumentException(f"Unknown parameter '{parameter.value}'") diff --git a/smnp/runtime/evaluator.py b/smnp/runtime/evaluator.py index d0e3019..f83f1e6 100644 --- a/smnp/runtime/evaluator.py +++ b/smnp/runtime/evaluator.py @@ -3,6 +3,7 @@ from smnp.ast.node.extend import ExtendNode from smnp.ast.node.function import FunctionDefinitionNode from smnp.ast.node.imports import ImportNode from smnp.ast.node.program import Program +from smnp.ast.node.ret import ReturnNode from smnp.error.runtime import RuntimeException from smnp.type.model import Type @@ -73,12 +74,14 @@ def evaluate(node, environment): from smnp.runtime.evaluators.extend import ExtendEvaluator from smnp.runtime.evaluators.block import BlockEvaluator from smnp.runtime.evaluators.imports import ImportEvaluator + from smnp.runtime.evaluators.function import ReturnEvaluator result = Evaluator.oneOf( Evaluator.forNodes(ProgramEvaluator.evaluate, Program), Evaluator.forNodes(ImportEvaluator.evaluate, ImportNode), Evaluator.forNodes(FunctionDefinitionEvaluator.evaluate, FunctionDefinitionNode), Evaluator.forNodes(ExtendEvaluator.evaluate, ExtendNode), Evaluator.forNodes(BlockEvaluator.evaluate, BlockNode), + Evaluator.forNodes(ReturnEvaluator.evaluate, ReturnNode), expressionEvaluator() )(node, environment) diff --git a/smnp/runtime/evaluators/function.py b/smnp/runtime/evaluators/function.py index ca18562..97e9e14 100644 --- a/smnp/runtime/evaluators/function.py +++ b/smnp/runtime/evaluators/function.py @@ -1,4 +1,3 @@ -from smnp.ast.node.ret import ReturnNode from smnp.error.runtime import RuntimeException from smnp.runtime.evaluator import Evaluator, evaluate from smnp.runtime.evaluators.expression import expressionEvaluator @@ -38,9 +37,17 @@ class BodyEvaluator(Evaluator): @classmethod def evaluator(cls, node, environment): for child in node.children: - if type(child) == ReturnNode: - x = expressionEvaluator(doAssert=True)(child.value, environment).value #TODO check if it isn't necessary to verify 'result' attr of EvaluatioNResult - return x - else: - evaluate(child, environment) + evaluate(child, environment) + if environment.callStack[-1].value is not None: + return environment.callStack[-1].value + +class ReturnEvaluator(Evaluator): + + @classmethod + def evaluator(cls, node, environment): + if len(environment.callStack) > 0: + returnValue = expressionEvaluator(doAssert=True)(node.value, environment) + environment.callStack[-1].value = returnValue.value + else: + raise RuntimeException("Cannot use 'return' statement outside a function or method", node.pos, environment)