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 public enum Type
{ {
Unknown,
Nil, Nil,
Boolean, Boolean,
Number, Number,

View File

@ -12,10 +12,10 @@ namespace Upsilon.Binder
private readonly Diagnostics _diagnostics; private readonly Diagnostics _diagnostics;
private BoundScope _scope; private BoundScope _scope;
public Binder(BoundScope parentScope, Diagnostics diagnostics) public Binder(Diagnostics diagnostics)
{ {
_diagnostics = diagnostics; _diagnostics = diagnostics;
_scope = new BoundScope(parentScope); _scope = new BoundScope(null);
} }
public BoundScript BindScript(BlockStatementSyntax e) public BoundScript BindScript(BlockStatementSyntax e)
@ -24,7 +24,7 @@ namespace Upsilon.Binder
return new BoundScript(bound); return new BoundScript(bound);
} }
public BoundStatement BindStatement(StatementSyntax s) private BoundStatement BindStatement(StatementSyntax s)
{ {
switch (s.Kind) switch (s.Kind)
{ {
@ -36,12 +36,14 @@ namespace Upsilon.Binder
return BindBlockStatement((BlockStatementSyntax) s); return BindBlockStatement((BlockStatementSyntax) s);
case SyntaxKind.IfStatement: case SyntaxKind.IfStatement:
return BindIfStatement((IfStatementSyntax) s); return BindIfStatement((IfStatementSyntax) s);
case SyntaxKind.FunctionStatement:
return BindFunctionStatement((FunctionStatementSyntax) s);
} }
throw new NotImplementedException(s.Kind.ToString()); throw new NotImplementedException(s.Kind.ToString());
} }
public BoundExpression BindExpression(ExpressionSyntax e) private BoundExpression BindExpression(ExpressionSyntax e)
{ {
switch (e.Kind) switch (e.Kind)
{ {
@ -177,19 +179,21 @@ namespace Upsilon.Binder
private BoundStatement BindBlockStatement(BlockStatementSyntax e) private BoundStatement BindBlockStatement(BlockStatementSyntax e)
{ {
var arr = ImmutableArray.CreateBuilder<BoundStatement>(); var arr = ImmutableArray.CreateBuilder<BoundStatement>();
var innerBinder = new Binder(_scope, _diagnostics);
foreach (var statementSyntax in e.Statements) foreach (var statementSyntax in e.Statements)
{ {
var bound = innerBinder.BindStatement(statementSyntax); var bound = BindStatement(statementSyntax);
arr.Add(bound); arr.Add(bound);
} }
return new BoundBlockStatement(arr.ToImmutable()); return new BoundBlockStatement(arr.ToImmutable());
} }
private BoundStatement BindIfStatement(IfStatementSyntax e) private BoundStatement BindIfStatement(IfStatementSyntax e)
{ {
_scope = new BoundScope(_scope);
var condition = BindExpressionStatement(e.Condition); var condition = BindExpressionStatement(e.Condition);
var block = BindBlockStatement(e.Block); var block = BindBlockStatement(e.Block);
_scope = _scope.ParentScope;
if (e.NextElseIfStatement != null) if (e.NextElseIfStatement != null)
{ {
var nextElseIf = BindIfStatement(e.NextElseIfStatement); var nextElseIf = BindIfStatement(e.NextElseIfStatement);
@ -202,12 +206,60 @@ namespace Upsilon.Binder
} }
else else
{ {
_scope = new BoundScope(_scope);
var elseBlock = BindBlockStatement(e.ElseStatement.Block); var elseBlock = BindBlockStatement(e.ElseStatement.Block);
var elseStatement = new BoundElseStatement((BoundBlockStatement) elseBlock); var elseStatement = new BoundElseStatement((BoundBlockStatement) elseBlock);
_scope = _scope.ParentScope;
return new BoundIfStatement((BoundExpressionStatement) condition, (BoundBlockStatement) block, return new BoundIfStatement((BoundExpressionStatement) condition, (BoundBlockStatement) block,
elseStatement); 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, BoundExpressionStatement,
BoundBlockStatement, BoundBlockStatement,
BoundIfStatement, BoundIfStatement,
BoundElseStatement BoundElseStatement,
BoundFunctionStatement
} }
} }

View File

@ -6,20 +6,21 @@ namespace Upsilon.Binder
{ {
public class BoundScope public class BoundScope
{ {
private readonly BoundScope _parentScope; public readonly BoundScope ParentScope;
private readonly Dictionary<string, VariableSymbol> _variables; private readonly Dictionary<string, VariableSymbol> _variables;
public BoundScope(BoundScope parentScope) public BoundScope(BoundScope parentScope)
{ {
_parentScope = parentScope; ParentScope = parentScope;
_variables = new Dictionary<string, VariableSymbol>(); _variables = new Dictionary<string, VariableSymbol>();
} }
public BoundScope(Dictionary<VariableSymbol, LuaType> variables, BoundScope parentScope) public BoundScope(Dictionary<VariableSymbol, LuaType> variables, BoundScope parentScope)
{ {
_parentScope = parentScope; ParentScope = parentScope;
_variables = variables.ToDictionary(x => x.Key.Name, x => x.Key); _variables = variables.ToDictionary(x => x.Key.Name, x => x.Key);
} }
public void SetVariable(VariableSymbol var) public void SetVariable(VariableSymbol var)
{ {
if (_variables.ContainsKey(var.Name)) if (_variables.ContainsKey(var.Name))
@ -30,13 +31,13 @@ namespace Upsilon.Binder
public void SetGlobalVariable(VariableSymbol var) public void SetGlobalVariable(VariableSymbol var)
{ {
if (_parentScope == null) if (ParentScope == null)
{ {
SetVariable(var); SetVariable(var);
} }
else else
{ {
_parentScope.SetGlobalVariable(var); ParentScope.SetGlobalVariable(var);
} }
} }
@ -47,9 +48,9 @@ namespace Upsilon.Binder
{ {
return true; 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; 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; using Upsilon.BaseTypes;
namespace Upsilon.Binder namespace Upsilon.Binder
@ -15,4 +16,15 @@ namespace Upsilon.Binder
public bool Local { get; } public bool Local { get; }
public string Name { 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); 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) public void LogBadCharacter(TextSpan location, SyntaxKind expectedToken)
{ {
LogError($"Invalid character found. Expected: '{expectedToken}'", location); LogError($"Invalid character found. Expected: '{expectedToken}'", location);

View File

@ -36,6 +36,9 @@ namespace Upsilon.Evaluator
case BoundKind.BoundIfStatement: case BoundKind.BoundIfStatement:
EvaluateBoundIfStatement((BoundIfStatement) e); EvaluateBoundIfStatement((BoundIfStatement) e);
break; break;
case BoundKind.BoundFunctionStatement:
EvaluateBoundFunctionStatement((BoundFunctionStatement) e);
break;
default: default:
EvaluateExpressionStatement((BoundExpressionStatement) e); EvaluateExpressionStatement((BoundExpressionStatement) e);
break; break;
@ -155,5 +158,14 @@ namespace Upsilon.Evaluator
EvaluateBoundBlockStatement(boundBlockStatement.ElseStatement.Block); 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.Binder;
using Upsilon.Parser; using Upsilon.Parser;
using Upsilon.Text; using Upsilon.Text;
using Upsilon.Utilities;
namespace Upsilon.Evaluator namespace Upsilon.Evaluator
{ {
@ -22,7 +23,7 @@ namespace Upsilon.Evaluator
_parsed = Parser.Parser.Parse(scriptString, Diagnostics); _parsed = Parser.Parser.Parse(scriptString, Diagnostics);
if (variables == null) if (variables == null)
variables = new Dictionary<VariableSymbol, LuaType>(); variables = new Dictionary<VariableSymbol, LuaType>();
Binder = new Binder.Binder(new BoundScope(variables, null), Diagnostics); Binder = new Binder.Binder(Diagnostics);
Scope = new EvaluationScope(variables); Scope = new EvaluationScope(variables);
Evaluator = new Evaluator( Diagnostics, Scope); Evaluator = new Evaluator( Diagnostics, Scope);
} }
@ -41,5 +42,10 @@ namespace Upsilon.Evaluator
{ {
return (T)Evaluator.Evaluate(Bind()); 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); return new SyntaxToken(SyntaxKind.OpenParenthesis, _position, "(", null);
case ')': case ')':
return new SyntaxToken(SyntaxKind.CloseParenthesis, _position, ")", null); return new SyntaxToken(SyntaxKind.CloseParenthesis, _position, ")", null);
case ',':
return new SyntaxToken(SyntaxKind.Comma, _position, ",", null);
case '=': case '=':
if (Next == '=') if (Next == '=')
{ {

View File

@ -45,7 +45,7 @@ namespace Upsilon.Parser
if (Current.Kind == kind) if (Current.Kind == kind)
return NextToken(); return NextToken();
_diagnostics.LogBadCharacter(Current.Span, kind); _diagnostics.LogBadCharacter(Current.Span, kind, Current.Kind);
return new SyntaxToken(kind, Current.Span.Start, "", null); return new SyntaxToken(kind, Current.Span.Start, "", null);
} }
@ -71,6 +71,15 @@ namespace Upsilon.Parser
return ParseIfStatement(SyntaxKind.IfKeyword); return ParseIfStatement(SyntaxKind.IfKeyword);
} }
if (Current.Kind == SyntaxKind.FunctionKeyword)
{
return ParseFunctionStatement();
}
if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == SyntaxKind.FunctionKeyword)
{
return ParseFunctionStatement();
}
return ParseExpressionStatement(); 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() private ExpressionStatementSyntax ParseExpressionStatement()
{ {
var expression = ParseExpression(); 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; return SyntaxKind.ElseKeyword;
case "nil": case "nil":
return SyntaxKind.NilKeyword; return SyntaxKind.NilKeyword;
case "function":
return SyntaxKind.FunctionKeyword;
default: default:
return SyntaxKind.Identifier; return SyntaxKind.Identifier;
} }

View File

@ -19,6 +19,7 @@ namespace Upsilon.Parser
EqualsEquals, EqualsEquals,
Tilde, Tilde,
TildeEquals, TildeEquals,
Comma,
// key words // key words
TrueKeyword, TrueKeyword,
@ -33,6 +34,7 @@ namespace Upsilon.Parser
ElseIfKeyword, ElseIfKeyword,
ElseKeyword, ElseKeyword,
NilKeyword, NilKeyword,
FunctionKeyword,
Identifier, Identifier,
@ -54,5 +56,6 @@ namespace Upsilon.Parser
IfStatement, IfStatement,
ElseIfStatement, ElseIfStatement,
ElseStatement, 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); var parsed = new Script(input, variables);
Console.WriteLine(parsed.PrettyPrintSyntaxTree());
if (parsed.Diagnostics.Messages.Count > 0) if (parsed.Diagnostics.Messages.Count > 0)
{ {
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.Red;