Implement defining functions

This commit is contained in:
Deukhoofd 2018-11-15 15:51:05 +01:00
parent 58b5a7355e
commit 07660b6c46
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
17 changed files with 266 additions and 16 deletions

View File

@ -0,0 +1,19 @@
using System.Collections.Immutable;
using Upsilon.Binder;
namespace Upsilon.BaseTypes
{
public class LuaFunction : LuaType
{
public LuaFunction(ImmutableArray<VariableSymbol> parameters, BoundBlockStatement block)
{
Parameters = parameters;
Block = block;
}
public override Type Type => Type.Function;
public ImmutableArray<VariableSymbol> Parameters { get; }
public BoundBlockStatement Block { get; }
}
}

View File

@ -2,6 +2,7 @@ namespace Upsilon.BaseTypes
{
public enum Type
{
Unknown,
Nil,
Boolean,
Number,

View File

@ -12,10 +12,10 @@ namespace Upsilon.Binder
private readonly Diagnostics _diagnostics;
private BoundScope _scope;
public Binder(BoundScope parentScope, Diagnostics diagnostics)
public Binder(Diagnostics diagnostics)
{
_diagnostics = diagnostics;
_scope = new BoundScope(parentScope);
_scope = new BoundScope(null);
}
public BoundScript BindScript(BlockStatementSyntax e)
@ -24,7 +24,7 @@ namespace Upsilon.Binder
return new BoundScript(bound);
}
public BoundStatement BindStatement(StatementSyntax s)
private BoundStatement BindStatement(StatementSyntax s)
{
switch (s.Kind)
{
@ -36,12 +36,14 @@ namespace Upsilon.Binder
return BindBlockStatement((BlockStatementSyntax) s);
case SyntaxKind.IfStatement:
return BindIfStatement((IfStatementSyntax) s);
case SyntaxKind.FunctionStatement:
return BindFunctionStatement((FunctionStatementSyntax) s);
}
throw new NotImplementedException(s.Kind.ToString());
}
public BoundExpression BindExpression(ExpressionSyntax e)
private BoundExpression BindExpression(ExpressionSyntax e)
{
switch (e.Kind)
{
@ -177,19 +179,21 @@ namespace Upsilon.Binder
private BoundStatement BindBlockStatement(BlockStatementSyntax e)
{
var arr = ImmutableArray.CreateBuilder<BoundStatement>();
var innerBinder = new Binder(_scope, _diagnostics);
foreach (var statementSyntax in e.Statements)
{
var bound = innerBinder.BindStatement(statementSyntax);
var bound = BindStatement(statementSyntax);
arr.Add(bound);
}
return new BoundBlockStatement(arr.ToImmutable());
}
private BoundStatement BindIfStatement(IfStatementSyntax e)
{
_scope = new BoundScope(_scope);
var condition = BindExpressionStatement(e.Condition);
var block = BindBlockStatement(e.Block);
_scope = _scope.ParentScope;
if (e.NextElseIfStatement != null)
{
var nextElseIf = BindIfStatement(e.NextElseIfStatement);
@ -202,12 +206,60 @@ namespace Upsilon.Binder
}
else
{
_scope = new BoundScope(_scope);
var elseBlock = BindBlockStatement(e.ElseStatement.Block);
var elseStatement = new BoundElseStatement((BoundBlockStatement) elseBlock);
_scope = _scope.ParentScope;
return new BoundIfStatement((BoundExpressionStatement) condition, (BoundBlockStatement) block,
elseStatement);
}
}
private BoundStatement BindFunctionStatement(FunctionStatementSyntax e)
{
var name = e.Identifier.Name;
var isLocal = e.LocalToken != null;
var innerScope = new BoundScope(_scope);
var parameters = ImmutableArray.CreateBuilder<VariableSymbol>();
foreach (var identifierToken in e.Parameters)
{
var vari = new VariableSymbol(identifierToken.Name, Type.Unknown, true);
parameters.Add(vari);
innerScope.SetVariable(vari);
}
if (!_scope.TryGetVariable(name, !isLocal, out var variable))
{
variable = new FunctionVariableSymbol(name, Type.Function, isLocal, parameters.ToImmutable());
if (isLocal)
_scope.SetVariable(variable);
else
_scope.SetGlobalVariable(variable);
}
else
{
// don't allow assigning different typed variables to a variable, unless either of them is nil, allow assigning nil to all variables
if (variable.Type != Type.Function)
{
if (variable.Type == Type.Nil )
{
variable.Type = Type.Function;
}
else
{
_diagnostics.LogCannotConvert(Type.Function, variable.Type, e.Span);
return new BoundExpressionStatement(null);
}
}
}
_scope = innerScope;
var block = BindBlockStatement(e.Block);
_scope = _scope.ParentScope;
return new BoundFunctionStatement(variable, parameters.ToImmutable(), (BoundBlockStatement) block);
}
}
}

View File

@ -14,6 +14,7 @@ namespace Upsilon.Binder
BoundExpressionStatement,
BoundBlockStatement,
BoundIfStatement,
BoundElseStatement
BoundElseStatement,
BoundFunctionStatement
}
}

View File

@ -6,20 +6,21 @@ namespace Upsilon.Binder
{
public class BoundScope
{
private readonly BoundScope _parentScope;
public readonly BoundScope ParentScope;
private readonly Dictionary<string, VariableSymbol> _variables;
public BoundScope(BoundScope parentScope)
{
_parentScope = parentScope;
ParentScope = parentScope;
_variables = new Dictionary<string, VariableSymbol>();
}
public BoundScope(Dictionary<VariableSymbol, LuaType> variables, BoundScope parentScope)
{
_parentScope = parentScope;
ParentScope = parentScope;
_variables = variables.ToDictionary(x => x.Key.Name, x => x.Key);
}
public void SetVariable(VariableSymbol var)
{
if (_variables.ContainsKey(var.Name))
@ -30,13 +31,13 @@ namespace Upsilon.Binder
public void SetGlobalVariable(VariableSymbol var)
{
if (_parentScope == null)
if (ParentScope == null)
{
SetVariable(var);
}
else
{
_parentScope.SetGlobalVariable(var);
ParentScope.SetGlobalVariable(var);
}
}
@ -47,9 +48,9 @@ namespace Upsilon.Binder
{
return true;
}
if (_parentScope != null && allowUpperScopes)
if (ParentScope != null && allowUpperScopes)
{
return _parentScope.TryGetVariable(key, true, out result);
return ParentScope.TryGetVariable(key, true, out result);
}
return false;
}

View File

@ -0,0 +1,21 @@
using System.Collections.Immutable;
namespace Upsilon.Binder
{
public class BoundFunctionStatement : BoundStatement
{
public VariableSymbol Identifier { get; }
public ImmutableArray<VariableSymbol> Parameters { get; }
public BoundBlockStatement Block { get; }
public BoundFunctionStatement(VariableSymbol identifier, ImmutableArray<VariableSymbol> parameters,
BoundBlockStatement block)
{
Identifier = identifier;
Parameters = parameters;
Block = block;
}
public override BoundKind Kind => BoundKind.BoundFunctionStatement;
}
}

View File

@ -1,3 +1,4 @@
using System.Collections.Immutable;
using Upsilon.BaseTypes;
namespace Upsilon.Binder
@ -15,4 +16,15 @@ namespace Upsilon.Binder
public bool Local { get; }
public string Name { get; }
}
public class FunctionVariableSymbol : VariableSymbol
{
public ImmutableArray<VariableSymbol> Parameters { get; }
public FunctionVariableSymbol(string name, Type type, bool local, ImmutableArray<VariableSymbol> parameters)
: base(name, type, local)
{
Parameters = parameters;
}
}
}

View File

@ -25,6 +25,11 @@ namespace Upsilon
Log(DiagnosticLevel.Error, message, location);
}
public void LogBadCharacter(TextSpan location, SyntaxKind expectedToken, SyntaxKind currentKind)
{
LogError($"Invalid character found. Expected: '{expectedToken}', Got: '{currentKind}'", location);
}
public void LogBadCharacter(TextSpan location, SyntaxKind expectedToken)
{
LogError($"Invalid character found. Expected: '{expectedToken}'", location);

View File

@ -36,6 +36,9 @@ namespace Upsilon.Evaluator
case BoundKind.BoundIfStatement:
EvaluateBoundIfStatement((BoundIfStatement) e);
break;
case BoundKind.BoundFunctionStatement:
EvaluateBoundFunctionStatement((BoundFunctionStatement) e);
break;
default:
EvaluateExpressionStatement((BoundExpressionStatement) e);
break;
@ -155,5 +158,14 @@ namespace Upsilon.Evaluator
EvaluateBoundBlockStatement(boundBlockStatement.ElseStatement.Block);
}
}
private void EvaluateBoundFunctionStatement(BoundFunctionStatement boundFunctionStatement)
{
var func = new LuaFunction(boundFunctionStatement.Parameters, boundFunctionStatement.Block);
if (boundFunctionStatement.Identifier.Local)
_scope.Set(boundFunctionStatement.Identifier, func);
else
_scope.SetGlobal(boundFunctionStatement.Identifier, func);
}
}
}

View File

@ -3,6 +3,7 @@ using Upsilon.BaseTypes;
using Upsilon.Binder;
using Upsilon.Parser;
using Upsilon.Text;
using Upsilon.Utilities;
namespace Upsilon.Evaluator
{
@ -22,7 +23,7 @@ namespace Upsilon.Evaluator
_parsed = Parser.Parser.Parse(scriptString, Diagnostics);
if (variables == null)
variables = new Dictionary<VariableSymbol, LuaType>();
Binder = new Binder.Binder(new BoundScope(variables, null), Diagnostics);
Binder = new Binder.Binder(Diagnostics);
Scope = new EvaluationScope(variables);
Evaluator = new Evaluator( Diagnostics, Scope);
}
@ -41,5 +42,10 @@ namespace Upsilon.Evaluator
{
return (T)Evaluator.Evaluate(Bind());
}
public string PrettyPrintSyntaxTree()
{
return _parsed.Print();
}
}
}

View File

@ -82,6 +82,8 @@ namespace Upsilon.Parser
return new SyntaxToken(SyntaxKind.OpenParenthesis, _position, "(", null);
case ')':
return new SyntaxToken(SyntaxKind.CloseParenthesis, _position, ")", null);
case ',':
return new SyntaxToken(SyntaxKind.Comma, _position, ",", null);
case '=':
if (Next == '=')
{

View File

@ -45,7 +45,7 @@ namespace Upsilon.Parser
if (Current.Kind == kind)
return NextToken();
_diagnostics.LogBadCharacter(Current.Span, kind);
_diagnostics.LogBadCharacter(Current.Span, kind, Current.Kind);
return new SyntaxToken(kind, Current.Span.Start, "", null);
}
@ -71,6 +71,15 @@ namespace Upsilon.Parser
return ParseIfStatement(SyntaxKind.IfKeyword);
}
if (Current.Kind == SyntaxKind.FunctionKeyword)
{
return ParseFunctionStatement();
}
if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == SyntaxKind.FunctionKeyword)
{
return ParseFunctionStatement();
}
return ParseExpressionStatement();
}
@ -114,6 +123,31 @@ namespace Upsilon.Parser
}
}
private StatementSyntax ParseFunctionStatement()
{
SyntaxToken localToken = null;
if (Current.Kind == SyntaxKind.LocalKeyword)
{
localToken = NextToken();
}
var functionToken = MatchToken(SyntaxKind.FunctionKeyword);
var identifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier);
var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis);
var variableBuilder = ImmutableArray.CreateBuilder<IdentifierToken>();
while (Current.Kind != SyntaxKind.CloseParenthesis)
{
var variableIdentifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier);
variableBuilder.Add(variableIdentifier);
if (Current.Kind == SyntaxKind.Comma)
NextToken();
}
var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis);
var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword});
var endToken = MatchToken(SyntaxKind.EndKeyword);
return new FunctionStatementSyntax(localToken, functionToken, identifier, openParenthesis,
variableBuilder.ToImmutable(), closeParenthesis, (BlockStatementSyntax) block, endToken);
}
private ExpressionStatementSyntax ParseExpressionStatement()
{
var expression = ParseExpression();

View File

@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Upsilon.Parser
{
public class FunctionStatementSyntax : StatementSyntax
{
public SyntaxToken LocalToken { get; }
public SyntaxToken FunctionToken { get; }
public IdentifierToken Identifier { get; }
public SyntaxToken OpenParenthesis { get; }
public ImmutableArray<IdentifierToken> Parameters { get; }
public SyntaxToken CloseParenthesis { get; }
public BlockStatementSyntax Block { get; }
public SyntaxToken EndToken { get; }
public FunctionStatementSyntax(SyntaxToken localToken, SyntaxToken functionToken, IdentifierToken identifier,
SyntaxToken openParenthesis, ImmutableArray<IdentifierToken> parameters, SyntaxToken closeParenthesis,
BlockStatementSyntax block, SyntaxToken endToken)
{
LocalToken = localToken;
FunctionToken = functionToken;
Identifier = identifier;
OpenParenthesis = openParenthesis;
Parameters = parameters;
CloseParenthesis = closeParenthesis;
Block = block;
EndToken = endToken;
}
public override SyntaxKind Kind => SyntaxKind.FunctionStatement;
public override IEnumerable<SyntaxNode> ChildNodes()
{
yield return LocalToken;
yield return FunctionToken;
yield return Identifier;
yield return OpenParenthesis;
foreach (var identifierToken in Parameters)
{
yield return identifierToken;
}
yield return CloseParenthesis;
yield return Block;
yield return EndToken;
}
}
}

View File

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

View File

@ -19,6 +19,7 @@ namespace Upsilon.Parser
EqualsEquals,
Tilde,
TildeEquals,
Comma,
// key words
TrueKeyword,
@ -33,6 +34,7 @@ namespace Upsilon.Parser
ElseIfKeyword,
ElseKeyword,
NilKeyword,
FunctionKeyword,
Identifier,
@ -54,5 +56,6 @@ namespace Upsilon.Parser
IfStatement,
ElseIfStatement,
ElseStatement,
FunctionStatement,
}
}

View File

@ -0,0 +1,30 @@
using Upsilon.BaseTypes;
using Upsilon.BaseTypes.Number;
using Upsilon.Evaluator;
using Xunit;
namespace UpsilonTests
{
public class FunctionTests
{
[Fact]
public void BasicFunctionTest()
{
const string input = @"
function testFunc ()
a = 100
end
a = 50
testFunc()
";
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);
}
}
}

View File

@ -29,6 +29,8 @@ namespace Ycicle
}
var parsed = new Script(input, variables);
Console.WriteLine(parsed.PrettyPrintSyntaxTree());
if (parsed.Diagnostics.Messages.Count > 0)
{
Console.ForegroundColor = ConsoleColor.Red;