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;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using Upsilon.BaseTypes; using Upsilon.BaseTypes;
using Upsilon.BaseTypes.Number; using Upsilon.BaseTypes.Number;
@ -57,6 +58,8 @@ namespace Upsilon.Binder
return BindParenthesizedExpression((ParenthesizedExpressionSyntax) e); return BindParenthesizedExpression((ParenthesizedExpressionSyntax) e);
case SyntaxKind.VariableExpression: case SyntaxKind.VariableExpression:
return BindVariableExpression((VariableExpressionSyntax) e); return BindVariableExpression((VariableExpressionSyntax) e);
case SyntaxKind.FunctionCallExpression:
return BindFunctionCallExpression((FunctionCallExpressionSyntax) e);
case SyntaxKind.BadExpression: case SyntaxKind.BadExpression:
break; break;
case SyntaxKind.ScriptUnit: case SyntaxKind.ScriptUnit:
@ -123,6 +126,37 @@ namespace Upsilon.Binder
return BindExpression(e.Expression); 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) private BoundExpression BindVariableExpression(VariableExpressionSyntax e)
{ {
var name = e.Identifier.Name; var name = e.Identifier.Name;
@ -164,6 +198,14 @@ namespace Upsilon.Binder
{ {
variable.Type = boundExpression.Type; 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 else
{ {
_diagnostics.LogCannotConvert(boundExpression.Type, variable.Type, e.Span); _diagnostics.LogCannotConvert(boundExpression.Type, variable.Type, e.Span);

View File

@ -12,12 +12,12 @@ namespace Upsilon.Binder
Addition, Subtraction, Multiplication, Division, Equality, Inequality Addition, Subtraction, Multiplication, Division, Equality, Inequality
} }
public Type LeftType { get; } private Type LeftType { get; }
public Type RightType { get; } private Type RightType { get; }
public Type OutType { get; } public Type OutType { get; }
public OperatorKind Kind { get; } public OperatorKind Kind { get; }
public BoundBinaryOperator(OperatorKind kind, Type outType) private BoundBinaryOperator(OperatorKind kind, Type outType)
{ {
Kind = kind; Kind = kind;
LeftType = outType; LeftType = outType;
@ -25,7 +25,7 @@ namespace Upsilon.Binder
OutType = outType; OutType = outType;
} }
public BoundBinaryOperator(OperatorKind kind, Type leftType, Type rightType, Type outType) private BoundBinaryOperator(OperatorKind kind, Type leftType, Type rightType, Type outType)
{ {
Kind = kind; Kind = kind;
LeftType = leftType; LeftType = leftType;
@ -33,7 +33,7 @@ namespace Upsilon.Binder
OutType = outType; OutType = outType;
} }
public static BoundBinaryOperator[] Operators = new BoundBinaryOperator[] private static readonly BoundBinaryOperator[] Operators = new[]
{ {
// Math operators // Math operators
new BoundBinaryOperator(OperatorKind.Addition, Type.Number), new BoundBinaryOperator(OperatorKind.Addition, Type.Number),
@ -77,6 +77,13 @@ namespace Upsilon.Binder
throw new Exception("Unknown binary operator token: " + operatorToken); 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); 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, BoundBinaryExpression,
BoundUnaryExpression, BoundUnaryExpression,
VariableExpression, VariableExpression,
BoundFunctionCallExpression,
// Statements // Statements
BoundAssignmentStatement, BoundAssignmentStatement,
@ -15,6 +16,6 @@ namespace Upsilon.Binder
BoundBlockStatement, BoundBlockStatement,
BoundIfStatement, BoundIfStatement,
BoundElseStatement, 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 public class EvaluationScope
{ {
private readonly EvaluationScope _parentScope; public readonly EvaluationScope ParentScope;
private readonly Dictionary<VariableSymbol, LuaType> _variables; private readonly Dictionary<VariableSymbol, LuaType> _variables;
internal EvaluationScope(EvaluationScope parentScope) internal EvaluationScope(EvaluationScope parentScope)
{ {
_parentScope = parentScope; ParentScope = parentScope;
_variables = new Dictionary<VariableSymbol, LuaType>(); _variables = new Dictionary<VariableSymbol, LuaType>();
} }
internal EvaluationScope(Dictionary<VariableSymbol, LuaType> vars) internal EvaluationScope(Dictionary<VariableSymbol, LuaType> vars)
{ {
_variables = vars; _variables = new Dictionary<VariableSymbol, LuaType>();
} }
public void Set(VariableSymbol symbol, LuaType obj) public void Set(VariableSymbol symbol, LuaType obj)
@ -34,8 +34,8 @@ namespace Upsilon.Evaluator
public void SetGlobal(VariableSymbol symbol, LuaType obj) public void SetGlobal(VariableSymbol symbol, LuaType obj)
{ {
if (_parentScope != null) if (ParentScope != null)
_parentScope.SetGlobal(symbol, obj); ParentScope.SetGlobal(symbol, obj);
else else
{ {
Set(symbol, obj); Set(symbol, obj);
@ -46,8 +46,8 @@ namespace Upsilon.Evaluator
{ {
if (_variables.TryGetValue(symbol, out obj)) if (_variables.TryGetValue(symbol, out obj))
return true; return true;
if (_parentScope != null) if (ParentScope != null)
if (_parentScope.TryGet(symbol, out obj)) if (ParentScope.TryGet(symbol, out obj))
return true; return true;
return false; return false;
} }
@ -62,8 +62,8 @@ namespace Upsilon.Evaluator
return true; return true;
}; };
} }
if (_parentScope != null) if (ParentScope != null)
if (_parentScope.TryGet(variable, out obj)) if (ParentScope.TryGet(variable, out obj))
return true; return true;
obj = null; obj = null;
return false; return false;

View File

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

View File

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

View File

@ -26,5 +26,43 @@ testFunc()
Assert.True(script.Scope.TryGet("a", out var a)); Assert.True(script.Scope.TryGet("a", out var a));
Assert.Equal(100, (long)(NumberLong)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);
}
} }
} }