Implement defining functions
This commit is contained in:
parent
58b5a7355e
commit
07660b6c46
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ namespace Upsilon.BaseTypes
|
|||
{
|
||||
public enum Type
|
||||
{
|
||||
Unknown,
|
||||
Nil,
|
||||
Boolean,
|
||||
Number,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ namespace Upsilon.Binder
|
|||
BoundExpressionStatement,
|
||||
BoundBlockStatement,
|
||||
BoundIfStatement,
|
||||
BoundElseStatement
|
||||
BoundElseStatement,
|
||||
BoundFunctionStatement
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 == '=')
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,6 +30,8 @@ namespace Upsilon.Parser
|
|||
return SyntaxKind.ElseKeyword;
|
||||
case "nil":
|
||||
return SyntaxKind.NilKeyword;
|
||||
case "function":
|
||||
return SyntaxKind.FunctionKeyword;
|
||||
default:
|
||||
return SyntaxKind.Identifier;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue