diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 7e2d9aa..1a1c762 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -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(); + 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); diff --git a/Upsilon/Binder/BoundBinaryOperator.cs b/Upsilon/Binder/BoundBinaryOperator.cs index 1330bea..783534d 100644 --- a/Upsilon/Binder/BoundBinaryOperator.cs +++ b/Upsilon/Binder/BoundBinaryOperator.cs @@ -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); } } diff --git a/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs b/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs new file mode 100644 index 0000000..9e0e1e2 --- /dev/null +++ b/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs @@ -0,0 +1,20 @@ +using System.Collections.Immutable; +using Upsilon.BaseTypes; + +namespace Upsilon.Binder +{ + public class BoundFunctionCallExpression : BoundExpression + { + public FunctionVariableSymbol Identifier { get; } + public ImmutableArray Parameters { get; } + + public BoundFunctionCallExpression(FunctionVariableSymbol identifier, ImmutableArray parameters) + { + Identifier = identifier; + Parameters = parameters; + } + + public override BoundKind Kind => BoundKind.BoundFunctionCallExpression; + public override Type Type => Type.Nil; + } +} \ No newline at end of file diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index f66929e..de868a9 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -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, } } \ No newline at end of file diff --git a/Upsilon/Binder/UnboundFunction.cs b/Upsilon/Binder/UnboundFunction.cs new file mode 100644 index 0000000..2e8cfa8 --- /dev/null +++ b/Upsilon/Binder/UnboundFunction.cs @@ -0,0 +1,7 @@ +namespace Upsilon.Binder +{ + public class UnboundFunction + { + + } +} \ No newline at end of file diff --git a/Upsilon/Evaluator/EvaluationScope.cs b/Upsilon/Evaluator/EvaluationScope.cs index f49f391..b89e021 100644 --- a/Upsilon/Evaluator/EvaluationScope.cs +++ b/Upsilon/Evaluator/EvaluationScope.cs @@ -7,17 +7,17 @@ namespace Upsilon.Evaluator { public class EvaluationScope { - private readonly EvaluationScope _parentScope; + public readonly EvaluationScope ParentScope; private readonly Dictionary _variables; internal EvaluationScope(EvaluationScope parentScope) { - _parentScope = parentScope; + ParentScope = parentScope; _variables = new Dictionary(); } internal EvaluationScope(Dictionary vars) { - _variables = vars; + _variables = new Dictionary(); } 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; diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 3479958..80e5938 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -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 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; } } } \ No newline at end of file diff --git a/Upsilon/Evaluator/Script.cs b/Upsilon/Evaluator/Script.cs index bfb4236..95721da 100644 --- a/Upsilon/Evaluator/Script.cs +++ b/Upsilon/Evaluator/Script.cs @@ -24,8 +24,8 @@ namespace Upsilon.Evaluator if (variables == null) variables = new Dictionary(); 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() diff --git a/Upsilon/Parser/ExpressionSyntax/FunctionCallExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/FunctionCallExpressionSyntax.cs new file mode 100644 index 0000000..2b29309 --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/FunctionCallExpressionSyntax.cs @@ -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 Parameters { get; } + public SyntaxToken CloseParenthesis { get; } + + public FunctionCallExpressionSyntax(IdentifierToken identifier, SyntaxToken openParenthesis, + ImmutableArray parameters, SyntaxToken closeParenthesis) + { + Identifier = identifier; + OpenParenthesis = openParenthesis; + Parameters = parameters; + CloseParenthesis = closeParenthesis; + } + + public override SyntaxKind Kind => SyntaxKind.FunctionCallExpression; + public override IEnumerable ChildNodes() + { + yield return Identifier; + yield return OpenParenthesis; + foreach (var expressionSyntax in Parameters) + { + yield return expressionSyntax; + } + yield return CloseParenthesis; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index a48e2a2..0d573ac 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -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(); + 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); diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index 45d3f0f..b75e107 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -45,6 +45,7 @@ namespace Upsilon.Parser ParenthesizedExpression, AssignmentStatement, VariableExpression, + FunctionCallExpression, BadExpression, // script unit diff --git a/UpsilonTests/FunctionTests.cs b/UpsilonTests/FunctionTests.cs index fac4f94..6b82787 100644 --- a/UpsilonTests/FunctionTests.cs +++ b/UpsilonTests/FunctionTests.cs @@ -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(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(func); + } + } } \ No newline at end of file