Upsilon/Upsilon/Binder/Binder.cs

352 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Upsilon.BaseTypes;
using Upsilon.BaseTypes.Number;
using Upsilon.Parser;
using Type = Upsilon.BaseTypes.Type;
namespace Upsilon.Binder
{
internal class Binder
{
private readonly Diagnostics _diagnostics;
public BoundScope Scope { get; private set; }
private Dictionary<FunctionVariableSymbol, UnboundFunctionStatement> _unboundFunctions =
new Dictionary<FunctionVariableSymbol, UnboundFunctionStatement>();
public Binder(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> variables)
{
_diagnostics = diagnostics;
Scope = new BoundScope(variables, null);
}
public BoundScript BindScript(BlockStatementSyntax e)
{
var bound = BindStatement(e);
foreach (var unboundFunctionStatement in _unboundFunctions)
{
Scope = new BoundScope(Scope);
foreach (var valueParameter in unboundFunctionStatement.Value.Parameters)
{
Scope.SetVariable(valueParameter);
}
unboundFunctionStatement.Value.Block =
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.Value.UnboundBlock);
Scope = Scope.ParentScope;
unboundFunctionStatement.Key.IsBound = true;
}
_unboundFunctions = new Dictionary<FunctionVariableSymbol, UnboundFunctionStatement>();
return new BoundScript((BoundBlockStatement) bound);
}
private BoundStatement BindStatement(StatementSyntax s)
{
switch (s.Kind)
{
case SyntaxKind.ExpressionStatement:
return BindExpressionStatement((ExpressionStatementSyntax) s);
case SyntaxKind.AssignmentStatement:
return BindAssignmentStatement((AssignmentExpressionSyntax) s);
case SyntaxKind.BlockStatement:
return BindBlockStatement((BlockStatementSyntax) s);
case SyntaxKind.IfStatement:
return BindIfStatement((IfStatementSyntax) s);
case SyntaxKind.FunctionStatement:
return BindFunctionStatement((FunctionStatementSyntax) s);
case SyntaxKind.ReturnStatement:
return BindReturnStatement((ReturnStatementSyntax) s);
}
throw new NotImplementedException(s.Kind.ToString());
}
private BoundExpression BindExpression(ExpressionSyntax e)
{
switch (e.Kind)
{
case SyntaxKind.UnaryExpression:
return BindUnaryExpression((UnaryExpressionSyntax) e);
case SyntaxKind.BinaryExpression:
return BindBinaryExpression((BinaryExpressionSyntax) e);
case SyntaxKind.LiteralExpression:
return BindLiteralExpression((LiteralExpressionSyntax) e);
case SyntaxKind.ParenthesizedExpression:
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:
break;
default:
throw new ArgumentOutOfRangeException();
}
throw new NotImplementedException(e.Kind.ToString());
}
private BoundExpression BindUnaryExpression(UnaryExpressionSyntax e)
{
var inExp = BindExpression(e.Expression);
var op = BoundUnaryOperator.Bind(e.Operator.Kind, inExp.Type);
if (op == null)
{
_diagnostics.LogUnknownUnaryOperator(e.Span, e.Operator.Kind, inExp.Type);
return inExp;
}
return new BoundUnaryExpression(op, inExp, op.OutType);
}
private BoundExpression BindBinaryExpression(BinaryExpressionSyntax e)
{
var left = BindExpression(e.Left);
var right = BindExpression(e.Right);
var op = BoundBinaryOperator.Bind(e.Operator.Kind, left.Type, right.Type);
if (op == null)
{
_diagnostics.LogUnknownBinaryOperator(e.Span, e.Operator.Kind, left.Type, right.Type);
return left;
}
return new BoundBinaryExpression(op, left, right, op.OutType);
}
private BoundExpression BindLiteralExpression(LiteralExpressionSyntax e)
{
var value = e.Value;
LuaType outValue = null;
switch (value)
{
case double d:
outValue = new NumberDouble(d);
break;
case long l:
outValue = new NumberLong(l);
break;
case bool b:
outValue = new LuaBoolean(b);
break;
case null:
outValue = new LuaNull();
break;
default:
_diagnostics.LogUnknownType(e.Span);
break;
}
return new BoundLiteralExpression(outValue);
}
private BoundExpression BindParenthesizedExpression(ParenthesizedExpressionSyntax e)
{
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);
}
if (!function.IsBound)
{
Scope = new BoundScope(Scope);
for (var index = 0; index < function.Parameters.Length; index++)
{
var functionVariable = function.Parameters[index];
var callingVariable = parameters[index];
functionVariable.Type = callingVariable.Type;
Scope.SetVariable(functionVariable);
}
var unboundFunctionStatement = _unboundFunctions[function];
unboundFunctionStatement.Block =
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.UnboundBlock);
Scope = Scope.ParentScope;
function.IsBound = true;
_unboundFunctions.Remove(function);
}
//TODO: validate parameters
return new BoundFunctionCallExpression(function, parameters.ToImmutable());
}
private BoundExpression BindVariableExpression(VariableExpressionSyntax e)
{
var name = e.Identifier.Name;
if (!Scope.TryGetVariable(name, true, out var variable))
{
_diagnostics.LogUnknownVariable(e.Identifier.Span, name);
return new BoundLiteralExpression(new LuaNull());
}
return new BoundVariableExpression(variable);
}
private BoundStatement BindExpressionStatement(ExpressionStatementSyntax s)
{
var exp = BindExpression(s.Expression);
return new BoundExpressionStatement(exp);
}
private BoundStatement BindAssignmentStatement(AssignmentExpressionSyntax e)
{
var name = e.Identifier.Name;
var boundExpression = BindExpression(e.Expression);
var isLocal = e.LocalToken != null;
if (!Scope.TryGetVariable(name, !isLocal, out var variable))
{
variable = new VariableSymbol(name, boundExpression.Type, isLocal);
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 (boundExpression.Type != variable.Type)
{
if (variable.Type == Type.Nil || boundExpression.Type == Type.Nil)
{
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);
return new BoundExpressionStatement(boundExpression);
}
}
}
return new BoundVariableAssignment(variable, boundExpression);
}
private BoundStatement BindBlockStatement(BlockStatementSyntax e)
{
var arr = ImmutableArray.CreateBuilder<BoundStatement>();
foreach (var statementSyntax in e.Statements)
{
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);
return new BoundIfStatement((BoundExpressionStatement) condition, (BoundBlockStatement) block,
(BoundIfStatement) nextElseIf);
}
if (e.ElseStatement == null)
{
return new BoundIfStatement((BoundExpressionStatement) condition, (BoundBlockStatement) block);
}
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);
}
}
}
if (parameters.Count == 0)
{
Scope = innerScope;
var block = BindBlockStatement(e.Block);
Scope = Scope.ParentScope;
((FunctionVariableSymbol) variable).IsBound = true;
var func = new BoundFunctionStatement(variable, parameters.ToImmutable(), (BoundBlockStatement) block);
return func;
}
else
{
var unbound = new UnboundFunctionStatement(variable, parameters.ToImmutable(), e.Block);
_unboundFunctions.Add((FunctionVariableSymbol) variable, unbound);
return unbound;
}
}
private BoundStatement BindReturnStatement(ReturnStatementSyntax e)
{
var expression = BindExpression(e.Expression);
return new BoundReturnStatement(expression);
}
}
}