Handle function returns

This commit is contained in:
Deukhoofd 2018-11-16 13:45:03 +01:00
parent eff60375ea
commit 7c6d847adb
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
9 changed files with 190 additions and 15 deletions

View File

@ -55,6 +55,8 @@ namespace Upsilon.Binder
return BindIfStatement((IfStatementSyntax) s); return BindIfStatement((IfStatementSyntax) s);
case SyntaxKind.FunctionStatement: case SyntaxKind.FunctionStatement:
return BindFunctionStatement((FunctionStatementSyntax) s); return BindFunctionStatement((FunctionStatementSyntax) s);
case SyntaxKind.ReturnStatement:
return BindReturnStatement((ReturnStatementSyntax) s);
} }
throw new NotImplementedException(s.Kind.ToString()); 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);
}
} }
} }

View File

@ -17,6 +17,7 @@ namespace Upsilon.Binder
BoundIfStatement, BoundIfStatement,
BoundElseStatement, BoundElseStatement,
BoundFunctionStatement, BoundFunctionStatement,
BoundPromise BoundPromise,
BoundReturnStatement
} }
} }

View File

@ -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;
}
}

View File

@ -10,7 +10,8 @@ namespace Upsilon.Evaluator
{ {
private readonly Diagnostics _diagnostics; private readonly Diagnostics _diagnostics;
private LuaType _value; private LuaType _value;
public EvaluationScope Scope { get; private set; } public EvaluationScope Scope { get; }
private bool HasReturned { get; set; }
internal Evaluator(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> variables) internal Evaluator(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> variables)
{ {
@ -18,14 +19,51 @@ namespace Upsilon.Evaluator
Scope = new EvaluationScope(variables); Scope = new EvaluationScope(variables);
} }
private Evaluator(Diagnostics diagnostics, EvaluationScope parentScope)
{
_diagnostics = diagnostics;
Scope = new EvaluationScope(parentScope);
}
public LuaType Evaluate(BoundScript e) 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; return _value;
} }
private void EvaluateStatement(BoundStatement e) private void EvaluateStatement(BoundStatement e)
{ {
if (HasReturned)
return;
switch (e.Kind) switch (e.Kind)
{ {
case BoundKind.BoundAssignmentStatement: case BoundKind.BoundAssignmentStatement:
@ -43,6 +81,9 @@ namespace Upsilon.Evaluator
case BoundKind.BoundPromise: case BoundKind.BoundPromise:
EvaluateUnboundFunctionStatement((UnboundFunctionStatement) e); EvaluateUnboundFunctionStatement((UnboundFunctionStatement) e);
break; break;
case BoundKind.BoundReturnStatement:
EvaluateReturnStatement((BoundReturnStatement) e);
break;
default: default:
EvaluateExpressionStatement((BoundExpressionStatement) e); EvaluateExpressionStatement((BoundExpressionStatement) e);
break; break;
@ -140,28 +181,30 @@ namespace Upsilon.Evaluator
{ {
foreach (var boundStatement in boundBlockStatement.Statements) foreach (var boundStatement in boundBlockStatement.Statements)
{ {
if (HasReturned)
return;
EvaluateStatement(boundStatement); EvaluateStatement(boundStatement);
} }
} }
private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement) private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement)
{ {
Scope = new EvaluationScope(Scope); var innerEvaluator = new Evaluator(_diagnostics, Scope);
var condition = EvaluateExpression(boundBlockStatement.Condition.Expression); var condition = innerEvaluator.EvaluateExpression(boundBlockStatement.Condition.Expression);
if ((LuaBoolean) condition) if ((LuaBoolean) condition)
{ {
EvaluateBoundBlockStatement(boundBlockStatement.Block); innerEvaluator.EvaluateStatement(boundBlockStatement.Block);
} }
else if (boundBlockStatement.NextElseIf != null) else if (boundBlockStatement.NextElseIf != null)
{ {
EvaluateBoundIfStatement(boundBlockStatement.NextElseIf); innerEvaluator.EvaluateStatement(boundBlockStatement.NextElseIf);
} }
else if (boundBlockStatement.ElseStatement != null) else if (boundBlockStatement.ElseStatement != null)
{ {
EvaluateBoundBlockStatement(boundBlockStatement.ElseStatement.Block); innerEvaluator.EvaluateStatement(boundBlockStatement.ElseStatement.Block);
} }
HasReturned = innerEvaluator.HasReturned;
Scope = Scope.ParentScope; _value = innerEvaluator._value;
} }
private void EvaluateBoundFunctionStatement(BoundFunctionStatement boundFunctionStatement) private void EvaluateBoundFunctionStatement(BoundFunctionStatement boundFunctionStatement)
@ -193,18 +236,22 @@ namespace Upsilon.Evaluator
var function = (LuaFunction) functionObj; var function = (LuaFunction) functionObj;
Scope = new EvaluationScope(Scope); var innerEvaluator = new Evaluator(_diagnostics, Scope);
for (var i = 0; i < function.Parameters.Length; i++) for (var i = 0; i < function.Parameters.Length; i++)
{ {
var parameterVariable = function.Parameters[i]; var parameterVariable = function.Parameters[i];
var parameterValue = EvaluateExpression(boundFunctionCallExpression.Parameters[i]); var parameterValue = innerEvaluator.EvaluateExpression(boundFunctionCallExpression.Parameters[i]);
Scope.Set(parameterVariable, parameterValue); innerEvaluator.Scope.Set(parameterVariable, parameterValue);
} }
EvaluateBoundBlockStatement(function.Block); _value = innerEvaluator.Evaluate(function.Block);
Scope = Scope.ParentScope;
return _value; return _value;
} }
private void EvaluateReturnStatement(BoundReturnStatement b)
{
_value = Evaluate(b.Expression);
HasReturned = true;
}
} }
} }

View File

@ -79,6 +79,10 @@ namespace Upsilon.Parser
{ {
return ParseFunctionStatement(); return ParseFunctionStatement();
} }
if (Current.Kind == SyntaxKind.ReturnKeyword)
{
return ParseReturnStatement();
}
return ParseExpressionStatement(); return ParseExpressionStatement();
} }
@ -154,6 +158,13 @@ namespace Upsilon.Parser
return new ExpressionStatementSyntax(expression); return new ExpressionStatementSyntax(expression);
} }
private StatementSyntax ParseReturnStatement()
{
var returnToken = MatchToken(SyntaxKind.ReturnKeyword);
var expression = ParseExpression();
return new ReturnStatementSyntax(returnToken, expression);
}
private ExpressionSyntax ParseExpression() private ExpressionSyntax ParseExpression()
{ {
return ParseBinaryExpression(); return ParseBinaryExpression();

View File

@ -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<SyntaxNode> ChildNodes()
{
yield return Expression;
}
}
}

View File

@ -32,6 +32,8 @@ namespace Upsilon.Parser
return SyntaxKind.NilKeyword; return SyntaxKind.NilKeyword;
case "function": case "function":
return SyntaxKind.FunctionKeyword; return SyntaxKind.FunctionKeyword;
case "return":
return SyntaxKind.ReturnKeyword;
default: default:
return SyntaxKind.Identifier; return SyntaxKind.Identifier;
} }

View File

@ -35,6 +35,7 @@ namespace Upsilon.Parser
ElseKeyword, ElseKeyword,
NilKeyword, NilKeyword,
FunctionKeyword, FunctionKeyword,
ReturnKeyword,
Identifier, Identifier,
@ -58,5 +59,6 @@ namespace Upsilon.Parser
ElseIfStatement, ElseIfStatement,
ElseStatement, ElseStatement,
FunctionStatement, FunctionStatement,
ReturnStatement
} }
} }

View File

@ -81,5 +81,75 @@ end
Assert.NotNull(castType.Block); 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<NumberLong>();
Assert.Empty(script.Diagnostics.Messages);
Assert.Equal(60, (long)(NumberLong)result);
}
} }
} }