diff --git a/src/main/scala/org/moe/interpreter/Interpreter.scala b/src/main/scala/org/moe/interpreter/Interpreter.scala index c00de4c..b2840c6 100644 --- a/src/main/scala/org/moe/interpreter/Interpreter.scala +++ b/src/main/scala/org/moe/interpreter/Interpreter.scala @@ -206,6 +206,25 @@ class Interpreter { // TODO: handle arguments case SubroutineDeclarationNode(name, params, body) => { scoped { sub_env => + var declared: Set[String] = params.toSet + var closed_over: Set[String] = Set() + walkAST( + body, + { ast: AST => + ast match { + case VariableDeclarationNode(varname, _) => + declared += varname + case VariableAccessNode(varname) => + if (env.has(varname) && !declared(varname)) { + closed_over += varname + } + else if (!declared(varname)) { + throw new MoeErrors.VariableNotFound(varname) + } + case _ => Unit + } + } + ) val sub = new MoeSubroutine( name, args => { diff --git a/src/test/scala/org/moe/interpreter/SubroutineNodeTestSuite.scala b/src/test/scala/org/moe/interpreter/SubroutineNodeTestSuite.scala index 12678a6..8a641ab 100644 --- a/src/test/scala/org/moe/interpreter/SubroutineNodeTestSuite.scala +++ b/src/test/scala/org/moe/interpreter/SubroutineNodeTestSuite.scala @@ -68,4 +68,173 @@ class SubroutineNodeTestSuite extends FunSuite with InterpreterTestUtils { assert(result.asInstanceOf[MoeIntObject].getNativeValue === 3) } + test("... basic test with var declared too late") { + // sub add_foo { $foo++ } + // my $foo = 0; add_foo(); $foo + val ast = wrapSimpleAST( + List( + SubroutineDeclarationNode( + "add_foo", + List(), + StatementsNode( + List( + IncrementNode(VariableAccessNode("$foo")) + ) + ) + ), + VariableDeclarationNode( + "$foo", + IntLiteralNode(0) + ), + SubroutineCallNode("add_foo", List()), + VariableAccessNode("$foo") + ) + ) + intercept[MoeErrors.VariableNotFound] { + interpreter.eval(runtime, runtime.getRootEnv, ast) + } + } + + test("... basic test with sub using closure behavior") { + // { my $n = 0; sub inc { ++$n } + // { inc(); inc(); inc(); } + val ast = wrapSimpleAST( + List( + ScopeNode( + StatementsNode( + List( + VariableDeclarationNode( + "$n", + IntLiteralNode(0) + ), + SubroutineDeclarationNode( + "inc", + List(), + StatementsNode( + List( + IncrementNode(VariableAccessNode("$n")) + ) + ) + ) + ) + ) + ), + ScopeNode( + StatementsNode( + List( + SubroutineCallNode("inc", List()), + SubroutineCallNode("inc", List()), + SubroutineCallNode("inc", List()) + ) + ) + ) + ) + ) + val result = interpreter.eval(runtime, runtime.getRootEnv, ast) + assert(result.asInstanceOf[MoeIntObject].getNativeValue === 3) + } + + test("... basic test with sub checking for avoided closure collision") { + // { my $n = 0; sub inc { ++$n } } + // { my $n = 0; inc(); $n } + val ast = wrapSimpleAST( + List( + ScopeNode( + StatementsNode( + List( + VariableDeclarationNode( + "$n", + IntLiteralNode(0) + ), + SubroutineDeclarationNode( + "inc", + List(), + StatementsNode( + List( + IncrementNode(VariableAccessNode("$n")) + ) + ) + ) + ) + ) + ), + ScopeNode( + StatementsNode( + List( + VariableDeclarationNode( + "$n", + IntLiteralNode(0) + ), + SubroutineCallNode("inc", List()), + VariableAccessNode("$n") + ) + ) + ) + ) + ) + val result = interpreter.eval(runtime, runtime.getRootEnv, ast) + assert(result.asInstanceOf[MoeIntObject].getNativeValue === 0) + } + + test("... basic test checking that param var is used") { + // { my $a = 0; sub f($a) { ++$a } ++$a } + // { my $b = f(f(0)); $b = f(f(0)); $b } + val ast = wrapSimpleAST( + List( + ScopeNode( + StatementsNode( + List( + VariableDeclarationNode( + "$a", + IntLiteralNode(0) + ), + SubroutineDeclarationNode( + "f", + List("$a"), + StatementsNode( + List( + IncrementNode(VariableAccessNode("$a")) + ) + ) + ), + IncrementNode(VariableAccessNode("$a")) + ) + ) + ), + ScopeNode( + StatementsNode( + List( + VariableDeclarationNode( + "$b", + SubroutineCallNode( + "f", + List( + SubroutineCallNode( + "f", + List(IntLiteralNode(0)) + ) + ) + ) + ), + VariableAssignmentNode( + "$b", + SubroutineCallNode( + "f", + List( + SubroutineCallNode( + "f", + List(IntLiteralNode(0)) + ) + ) + ) + ), + VariableAccessNode("$b") + ) + ) + ) + ) + ) + val result = interpreter.eval(runtime, runtime.getRootEnv, ast) + assert(result.asInstanceOf[MoeIntObject].getNativeValue === 2) + } }