Add Function Calling
This commit is contained in:
parent
07660b6c46
commit
d5c8a959fb
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace Upsilon.Binder
|
||||
{
|
||||
public class UnboundFunction
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -45,6 +45,7 @@ namespace Upsilon.Parser
|
|||
ParenthesizedExpression,
|
||||
AssignmentStatement,
|
||||
VariableExpression,
|
||||
FunctionCallExpression,
|
||||
BadExpression,
|
||||
|
||||
// script unit
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue