Add Function Calling

This commit is contained in:
Deukhoofd 2018-11-15 20:13:53 +01:00
parent 07660b6c46
commit d5c8a959fb
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
12 changed files with 224 additions and 30 deletions

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Upsilon.BaseTypes;
using Upsilon.BaseTypes.Number;
@ -57,6 +58,8 @@ namespace Upsilon.Binder
return BindParenthesizedExpression((ParenthesizedExpressionSyntax) e);
case SyntaxKind.VariableExpression:
return BindVariableExpression((VariableExpressionSyntax) e);
case SyntaxKind.FunctionCallExpression:
return BindFunctionCallExpression((FunctionCallExpressionSyntax) e);
case SyntaxKind.BadExpression:
break;
case SyntaxKind.ScriptUnit:
@ -123,6 +126,37 @@ namespace Upsilon.Binder
return BindExpression(e.Expression);
}
private BoundExpression BindFunctionCallExpression(FunctionCallExpressionSyntax e)
{
var name = e.Identifier.Name;
if (!_scope.TryGetVariable(name, true, out var functionObj))
{
_diagnostics.LogUnknownVariable(e.Identifier.Span, name);
return new BoundLiteralExpression(new LuaNull());
}
var function = (FunctionVariableSymbol) functionObj;
var parameters = ImmutableArray.CreateBuilder<BoundExpression>();
foreach (var expressionSyntax in e.Parameters)
{
var bound = BindExpression(expressionSyntax);
parameters.Add(bound);
}
for (var index = 0; index < function.Parameters.Length; index++)
{
var parameter = function.Parameters[index];
if (parameter.Type == Type.Unknown)
{
parameter.Type = parameters[index].Type;
}
}
//TODO: validate parameters
return new BoundFunctionCallExpression((FunctionVariableSymbol) function, parameters.ToImmutable());
}
private BoundExpression BindVariableExpression(VariableExpressionSyntax e)
{
var name = e.Identifier.Name;
@ -164,6 +198,14 @@ namespace Upsilon.Binder
{
variable.Type = boundExpression.Type;
}
else if (variable.Type == Type.Unknown)
{
variable.Type = boundExpression.Type;
}
else if (boundExpression.Type == Type.Unknown && boundExpression is BoundVariableExpression v)
{
v.Variable.Type = variable.Type;
}
else
{
_diagnostics.LogCannotConvert(boundExpression.Type, variable.Type, e.Span);

View File

@ -12,12 +12,12 @@ namespace Upsilon.Binder
Addition, Subtraction, Multiplication, Division, Equality, Inequality
}
public Type LeftType { get; }
public Type RightType { get; }
private Type LeftType { get; }
private Type RightType { get; }
public Type OutType { get; }
public OperatorKind Kind { get; }
public BoundBinaryOperator(OperatorKind kind, Type outType)
private BoundBinaryOperator(OperatorKind kind, Type outType)
{
Kind = kind;
LeftType = outType;
@ -25,7 +25,7 @@ namespace Upsilon.Binder
OutType = outType;
}
public BoundBinaryOperator(OperatorKind kind, Type leftType, Type rightType, Type outType)
private BoundBinaryOperator(OperatorKind kind, Type leftType, Type rightType, Type outType)
{
Kind = kind;
LeftType = leftType;
@ -33,7 +33,7 @@ namespace Upsilon.Binder
OutType = outType;
}
public static BoundBinaryOperator[] Operators = new BoundBinaryOperator[]
private static readonly BoundBinaryOperator[] Operators = new[]
{
// Math operators
new BoundBinaryOperator(OperatorKind.Addition, Type.Number),
@ -77,6 +77,13 @@ namespace Upsilon.Binder
throw new Exception("Unknown binary operator token: " + operatorToken);
}
if (left == Type.Unknown && right == Type.Unknown)
return Operators.FirstOrDefault(op => op.Kind == kind);
if (left == Type.Unknown)
return Operators.FirstOrDefault(op => op.Kind == kind && op.RightType == right);
if (right == Type.Unknown)
return Operators.FirstOrDefault(op => op.Kind == kind && op.LeftType == left);
return Operators.FirstOrDefault(op => op.Kind == kind && op.LeftType == left && op.RightType == right);
}
}

View File

@ -0,0 +1,20 @@
using System.Collections.Immutable;
using Upsilon.BaseTypes;
namespace Upsilon.Binder
{
public class BoundFunctionCallExpression : BoundExpression
{
public FunctionVariableSymbol Identifier { get; }
public ImmutableArray<BoundExpression> Parameters { get; }
public BoundFunctionCallExpression(FunctionVariableSymbol identifier, ImmutableArray<BoundExpression> parameters)
{
Identifier = identifier;
Parameters = parameters;
}
public override BoundKind Kind => BoundKind.BoundFunctionCallExpression;
public override Type Type => Type.Nil;
}
}

View File

@ -8,6 +8,7 @@ namespace Upsilon.Binder
BoundBinaryExpression,
BoundUnaryExpression,
VariableExpression,
BoundFunctionCallExpression,
// Statements
BoundAssignmentStatement,
@ -15,6 +16,6 @@ namespace Upsilon.Binder
BoundBlockStatement,
BoundIfStatement,
BoundElseStatement,
BoundFunctionStatement
BoundFunctionStatement,
}
}

View File

@ -0,0 +1,7 @@
namespace Upsilon.Binder
{
public class UnboundFunction
{
}
}

View File

@ -7,17 +7,17 @@ namespace Upsilon.Evaluator
{
public class EvaluationScope
{
private readonly EvaluationScope _parentScope;
public readonly EvaluationScope ParentScope;
private readonly Dictionary<VariableSymbol, LuaType> _variables;
internal EvaluationScope(EvaluationScope parentScope)
{
_parentScope = parentScope;
ParentScope = parentScope;
_variables = new Dictionary<VariableSymbol, LuaType>();
}
internal EvaluationScope(Dictionary<VariableSymbol, LuaType> vars)
{
_variables = vars;
_variables = new Dictionary<VariableSymbol, LuaType>();
}
public void Set(VariableSymbol symbol, LuaType obj)
@ -34,8 +34,8 @@ namespace Upsilon.Evaluator
public void SetGlobal(VariableSymbol symbol, LuaType obj)
{
if (_parentScope != null)
_parentScope.SetGlobal(symbol, obj);
if (ParentScope != null)
ParentScope.SetGlobal(symbol, obj);
else
{
Set(symbol, obj);
@ -46,8 +46,8 @@ namespace Upsilon.Evaluator
{
if (_variables.TryGetValue(symbol, out obj))
return true;
if (_parentScope != null)
if (_parentScope.TryGet(symbol, out obj))
if (ParentScope != null)
if (ParentScope.TryGet(symbol, out obj))
return true;
return false;
}
@ -62,8 +62,8 @@ namespace Upsilon.Evaluator
return true;
};
}
if (_parentScope != null)
if (_parentScope.TryGet(variable, out obj))
if (ParentScope != null)
if (ParentScope.TryGet(variable, out obj))
return true;
obj = null;
return false;

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Upsilon.BaseTypes;
using Upsilon.BaseTypes.Number;
using Upsilon.Binder;
@ -9,12 +10,12 @@ namespace Upsilon.Evaluator
{
private readonly Diagnostics _diagnostics;
private LuaType _value;
private readonly EvaluationScope _scope;
public EvaluationScope Scope { get; private set; }
internal Evaluator(Diagnostics diagnostics, EvaluationScope parentScope)
internal Evaluator(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> variables)
{
_diagnostics = diagnostics;
_scope = new EvaluationScope(parentScope);
Scope = new EvaluationScope(variables);
}
public LuaType Evaluate(BoundScript e)
@ -63,6 +64,8 @@ namespace Upsilon.Evaluator
return EvaluateUnaryExpression((BoundUnaryExpression) e);
case BoundKind.VariableExpression:
return EvaluateVariableExpression((BoundVariableExpression) e);
case BoundKind.BoundFunctionCallExpression:
return EvaluateBoundFunctionCallExpression((BoundFunctionCallExpression) e);
default:
throw new NotImplementedException();
}
@ -112,18 +115,18 @@ namespace Upsilon.Evaluator
var val = EvaluateExpression(e.BoundExpression);
if (e.Variable.Local)
{
_scope.Set(e.Variable, val);
Scope.Set(e.Variable, val);
}
else
{
_scope.SetGlobal(e.Variable, val);
Scope.SetGlobal(e.Variable, val);
}
_value = val;
}
private LuaType EvaluateVariableExpression(BoundVariableExpression e)
{
if (_scope.TryGet(e.Variable, out var val))
if (Scope.TryGet(e.Variable, out var val))
{
return val;
}
@ -132,18 +135,15 @@ namespace Upsilon.Evaluator
private void EvaluateBoundBlockStatement(BoundBlockStatement boundBlockStatement)
{
var innerEvaluator = new Evaluator(_diagnostics, _scope);
foreach (var boundStatement in boundBlockStatement.Statements)
{
innerEvaluator.EvaluateStatement(boundStatement);
EvaluateStatement(boundStatement);
}
if (innerEvaluator._value != null)
_value = innerEvaluator._value;
}
private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement)
{
Scope = new EvaluationScope(Scope);
var condition = EvaluateExpression(boundBlockStatement.Condition.Expression);
if ((LuaBoolean) condition)
{
@ -157,15 +157,39 @@ namespace Upsilon.Evaluator
{
EvaluateBoundBlockStatement(boundBlockStatement.ElseStatement.Block);
}
Scope = Scope.ParentScope;
}
private void EvaluateBoundFunctionStatement(BoundFunctionStatement boundFunctionStatement)
{
var func = new LuaFunction(boundFunctionStatement.Parameters, boundFunctionStatement.Block);
if (boundFunctionStatement.Identifier.Local)
_scope.Set(boundFunctionStatement.Identifier, func);
Scope.Set(boundFunctionStatement.Identifier, func);
else
_scope.SetGlobal(boundFunctionStatement.Identifier, func);
Scope.SetGlobal(boundFunctionStatement.Identifier, func);
}
private LuaType EvaluateBoundFunctionCallExpression(BoundFunctionCallExpression boundFunctionCallExpression)
{
var name = boundFunctionCallExpression.Identifier;
if (!Scope.TryGet(name, out var functionObj))
{
throw new Exception("Cannot find function: " + name.Name);
}
var function = (LuaFunction) functionObj;
Scope = new EvaluationScope(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);
}
EvaluateBoundBlockStatement(function.Block);
Scope = Scope.ParentScope;
return _value;
}
}
}

View File

@ -24,8 +24,8 @@ namespace Upsilon.Evaluator
if (variables == null)
variables = new Dictionary<VariableSymbol, LuaType>();
Binder = new Binder.Binder(Diagnostics);
Scope = new EvaluationScope(variables);
Evaluator = new Evaluator( Diagnostics, Scope);
Evaluator = new Evaluator( Diagnostics, variables);
Scope = Evaluator.Scope;
}
public BoundScript Bind()

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Upsilon.Parser
{
public class FunctionCallExpressionSyntax : ExpressionSyntax
{
public IdentifierToken Identifier { get; }
public SyntaxToken OpenParenthesis { get; }
public ImmutableArray<ExpressionSyntax> Parameters { get; }
public SyntaxToken CloseParenthesis { get; }
public FunctionCallExpressionSyntax(IdentifierToken identifier, SyntaxToken openParenthesis,
ImmutableArray<ExpressionSyntax> parameters, SyntaxToken closeParenthesis)
{
Identifier = identifier;
OpenParenthesis = openParenthesis;
Parameters = parameters;
CloseParenthesis = closeParenthesis;
}
public override SyntaxKind Kind => SyntaxKind.FunctionCallExpression;
public override IEnumerable<SyntaxNode> ChildNodes()
{
yield return Identifier;
yield return OpenParenthesis;
foreach (var expressionSyntax in Parameters)
{
yield return expressionSyntax;
}
yield return CloseParenthesis;
}
}
}

View File

@ -213,6 +213,8 @@ namespace Upsilon.Parser
case SyntaxKind.FalseKeyword:
return ParseBoolean();
case SyntaxKind.Identifier:
if (Next.Kind == SyntaxKind.OpenParenthesis)
return ParseFunctionCallExpression();
var token = MatchToken(SyntaxKind.Identifier);
return new VariableExpressionSyntax((IdentifierToken) token);
case SyntaxKind.NilKeyword:
@ -225,6 +227,24 @@ namespace Upsilon.Parser
}
}
private ExpressionSyntax ParseFunctionCallExpression()
{
var identifier = MatchToken(SyntaxKind.Identifier);
var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis);
var parameters = ImmutableArray.CreateBuilder<ExpressionSyntax>();
while (Current.Kind != SyntaxKind.CloseParenthesis)
{
var exp = ParseExpression();
parameters.Add(exp);
if (Current.Kind == SyntaxKind.Comma)
NextToken();
}
var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis);
return new FunctionCallExpressionSyntax((IdentifierToken) identifier, openParenthesis,
parameters.ToImmutable(), closeParenthesis);
}
private ExpressionSyntax ParseParenthesizedExpression()
{
var l = MatchToken(SyntaxKind.OpenParenthesis);

View File

@ -45,6 +45,7 @@ namespace Upsilon.Parser
ParenthesizedExpression,
AssignmentStatement,
VariableExpression,
FunctionCallExpression,
BadExpression,
// script unit

View File

@ -26,5 +26,43 @@ testFunc()
Assert.True(script.Scope.TryGet("a", out var a));
Assert.Equal(100, (long)(NumberLong)a);
}
[Fact]
public void ParameterTest()
{
const string input = @"
function testFunc (var1)
a = var1
end
a = 50
testFunc(100)
";
var script = new Script(input);
Assert.Empty(script.Diagnostics.Messages);
script.Evaluate();
Assert.Empty(script.Diagnostics.Messages);
Assert.True(script.Scope.TryGet("testFunc", out var func));
Assert.IsType<LuaFunction>(func);
Assert.True(script.Scope.TryGet("a", out var a));
Assert.Equal(100, (long)(NumberLong)a);
}
[Fact]
public void ParameterBindTest()
{
const string input = @"
function testFunc (var1)
b = var1 == true
end
testFunc(100)
";
var script = new Script(input);
Assert.Empty(script.Diagnostics.Messages);
script.Evaluate();
Assert.Empty(script.Diagnostics.Messages);
Assert.True(script.Scope.TryGet("testFunc", out var func));
Assert.IsType<LuaFunction>(func);
}
}
}