diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 2d83daf..5f441cf 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -55,6 +55,8 @@ namespace Upsilon.Binder return BindIfStatement((IfStatementSyntax) s); case SyntaxKind.FunctionStatement: return BindFunctionStatement((FunctionStatementSyntax) s); + case SyntaxKind.ReturnStatement: + return BindReturnStatement((ReturnStatementSyntax) s); } throw new NotImplementedException(s.Kind.ToString()); @@ -338,5 +340,11 @@ namespace Upsilon.Binder } } + private BoundStatement BindReturnStatement(ReturnStatementSyntax e) + { + var expression = BindExpression(e.Expression); + return new BoundReturnStatement(expression); + } + } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index efbfc1f..5585abd 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -17,6 +17,7 @@ namespace Upsilon.Binder BoundIfStatement, BoundElseStatement, BoundFunctionStatement, - BoundPromise + BoundPromise, + BoundReturnStatement } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundReturnStatement.cs b/Upsilon/Binder/BoundStatements/BoundReturnStatement.cs new file mode 100644 index 0000000..a34b563 --- /dev/null +++ b/Upsilon/Binder/BoundStatements/BoundReturnStatement.cs @@ -0,0 +1,13 @@ +namespace Upsilon.Binder +{ + public class BoundReturnStatement : BoundStatement + { + public BoundReturnStatement(BoundExpression expression) + { + Expression = expression; + } + + public BoundExpression Expression { get; } + public override BoundKind Kind => BoundKind.BoundReturnStatement; + } +} \ No newline at end of file diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 65f9d99..4b923f4 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -10,7 +10,8 @@ namespace Upsilon.Evaluator { private readonly Diagnostics _diagnostics; private LuaType _value; - public EvaluationScope Scope { get; private set; } + public EvaluationScope Scope { get; } + private bool HasReturned { get; set; } internal Evaluator(Diagnostics diagnostics, Dictionary variables) { @@ -18,14 +19,51 @@ namespace Upsilon.Evaluator Scope = new EvaluationScope(variables); } + private Evaluator(Diagnostics diagnostics, EvaluationScope parentScope) + { + _diagnostics = diagnostics; + Scope = new EvaluationScope(parentScope); + } + public LuaType Evaluate(BoundScript e) { - EvaluateStatement(e.Statement); + Evaluate(e.Statement); + return _value; + } + + private LuaType Evaluate(BoundNode b) + { + switch (b.Kind) + { + case BoundKind.BoundScript: + EvaluateStatement(((BoundScript)b).Statement); + break; + case BoundKind.BoundLiteralExpression: + case BoundKind.BoundBinaryExpression: + case BoundKind.BoundUnaryExpression: + case BoundKind.VariableExpression: + case BoundKind.BoundFunctionCallExpression: + _value = EvaluateExpression((BoundExpression) b); + break; + case BoundKind.BoundAssignmentStatement: + case BoundKind.BoundExpressionStatement: + case BoundKind.BoundBlockStatement: + case BoundKind.BoundIfStatement: + case BoundKind.BoundElseStatement: + case BoundKind.BoundFunctionStatement: + case BoundKind.BoundPromise: + EvaluateStatement((BoundStatement) b); + break; + default: + throw new ArgumentOutOfRangeException(); + } return _value; } private void EvaluateStatement(BoundStatement e) { + if (HasReturned) + return; switch (e.Kind) { case BoundKind.BoundAssignmentStatement: @@ -43,6 +81,9 @@ namespace Upsilon.Evaluator case BoundKind.BoundPromise: EvaluateUnboundFunctionStatement((UnboundFunctionStatement) e); break; + case BoundKind.BoundReturnStatement: + EvaluateReturnStatement((BoundReturnStatement) e); + break; default: EvaluateExpressionStatement((BoundExpressionStatement) e); break; @@ -140,28 +181,30 @@ namespace Upsilon.Evaluator { foreach (var boundStatement in boundBlockStatement.Statements) { + if (HasReturned) + return; EvaluateStatement(boundStatement); } } private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement) { - Scope = new EvaluationScope(Scope); - var condition = EvaluateExpression(boundBlockStatement.Condition.Expression); + var innerEvaluator = new Evaluator(_diagnostics, Scope); + var condition = innerEvaluator.EvaluateExpression(boundBlockStatement.Condition.Expression); if ((LuaBoolean) condition) { - EvaluateBoundBlockStatement(boundBlockStatement.Block); + innerEvaluator.EvaluateStatement(boundBlockStatement.Block); } else if (boundBlockStatement.NextElseIf != null) { - EvaluateBoundIfStatement(boundBlockStatement.NextElseIf); + innerEvaluator.EvaluateStatement(boundBlockStatement.NextElseIf); } else if (boundBlockStatement.ElseStatement != null) { - EvaluateBoundBlockStatement(boundBlockStatement.ElseStatement.Block); + innerEvaluator.EvaluateStatement(boundBlockStatement.ElseStatement.Block); } - - Scope = Scope.ParentScope; + HasReturned = innerEvaluator.HasReturned; + _value = innerEvaluator._value; } private void EvaluateBoundFunctionStatement(BoundFunctionStatement boundFunctionStatement) @@ -193,18 +236,22 @@ namespace Upsilon.Evaluator var function = (LuaFunction) functionObj; - Scope = new EvaluationScope(Scope); + var innerEvaluator = new Evaluator(_diagnostics, Scope); for (var i = 0; i < function.Parameters.Length; i++) { var parameterVariable = function.Parameters[i]; - var parameterValue = EvaluateExpression(boundFunctionCallExpression.Parameters[i]); - Scope.Set(parameterVariable, parameterValue); + var parameterValue = innerEvaluator.EvaluateExpression(boundFunctionCallExpression.Parameters[i]); + innerEvaluator.Scope.Set(parameterVariable, parameterValue); } - EvaluateBoundBlockStatement(function.Block); - Scope = Scope.ParentScope; + _value = innerEvaluator.Evaluate(function.Block); return _value; } + private void EvaluateReturnStatement(BoundReturnStatement b) + { + _value = Evaluate(b.Expression); + HasReturned = true; + } } } \ No newline at end of file diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 0d573ac..2d34f68 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -79,6 +79,10 @@ namespace Upsilon.Parser { return ParseFunctionStatement(); } + if (Current.Kind == SyntaxKind.ReturnKeyword) + { + return ParseReturnStatement(); + } return ParseExpressionStatement(); } @@ -154,6 +158,13 @@ namespace Upsilon.Parser return new ExpressionStatementSyntax(expression); } + private StatementSyntax ParseReturnStatement() + { + var returnToken = MatchToken(SyntaxKind.ReturnKeyword); + var expression = ParseExpression(); + return new ReturnStatementSyntax(returnToken, expression); + } + private ExpressionSyntax ParseExpression() { return ParseBinaryExpression(); diff --git a/Upsilon/Parser/StatementSyntax/ReturnStatementSyntax.cs b/Upsilon/Parser/StatementSyntax/ReturnStatementSyntax.cs new file mode 100644 index 0000000..b448f6b --- /dev/null +++ b/Upsilon/Parser/StatementSyntax/ReturnStatementSyntax.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class ReturnStatementSyntax : StatementSyntax + { + public ReturnStatementSyntax(SyntaxToken returnToken, ExpressionSyntax expression) + { + ReturnToken = returnToken; + Expression = expression; + } + + public SyntaxToken ReturnToken { get; } + public ExpressionSyntax Expression { get; } + public override SyntaxKind Kind => SyntaxKind.ReturnStatement; + public override IEnumerable ChildNodes() + { + yield return Expression; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKeyWords.cs b/Upsilon/Parser/SyntaxKeyWords.cs index 57a84a2..f9203e1 100644 --- a/Upsilon/Parser/SyntaxKeyWords.cs +++ b/Upsilon/Parser/SyntaxKeyWords.cs @@ -32,6 +32,8 @@ namespace Upsilon.Parser return SyntaxKind.NilKeyword; case "function": return SyntaxKind.FunctionKeyword; + case "return": + return SyntaxKind.ReturnKeyword; default: return SyntaxKind.Identifier; } diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index b75e107..bd6d7ff 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -35,6 +35,7 @@ namespace Upsilon.Parser ElseKeyword, NilKeyword, FunctionKeyword, + ReturnKeyword, Identifier, @@ -58,5 +59,6 @@ namespace Upsilon.Parser ElseIfStatement, ElseStatement, FunctionStatement, + ReturnStatement } } \ No newline at end of file diff --git a/UpsilonTests/FunctionTests.cs b/UpsilonTests/FunctionTests.cs index 398d322..97a1c14 100644 --- a/UpsilonTests/FunctionTests.cs +++ b/UpsilonTests/FunctionTests.cs @@ -81,5 +81,75 @@ end Assert.NotNull(castType.Block); } + [Fact] + public void ReturnFromFunction() + { + const string input = @" +function testFunc () + return 5 +end +a = testFunc() +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.True(script.Scope.TryGet("a", out var result)); + Assert.Equal(5, (long)(NumberLong)result); + } + + [Fact] + public void ReturnFromFunctionOnce() + { + const string input = @" +function testFunc () + return 5 + return 10 +end +a = testFunc() +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.True(script.Scope.TryGet("a", out var result)); + Assert.Equal(5, (long)(NumberLong)result); + } + + [Fact] + public void ReturnFromFunctionNested() + { + const string input = @" +function testFunc () + if true then + return 5 + end + return 10 +end +a = testFunc() +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.True(script.Scope.TryGet("a", out var result)); + Assert.Equal(5, (long)(NumberLong)result); + } + + [Fact] + public void ReturnFromScriptAsFunction() + { + const string input = @" +a = 100 +return 60 +a = 87 +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + var result = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(60, (long)(NumberLong)result); + } + } } \ No newline at end of file