558 lines
23 KiB
C#
558 lines
23 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 Diagnostics _diagnostics;
|
|
public BoundScope Scope { get; private set; }
|
|
|
|
private Dictionary<string, UnboundFunctionExpression> _unboundFunctions =
|
|
new Dictionary<string, UnboundFunctionExpression>();
|
|
|
|
public Binder(Diagnostics diagnostics, Dictionary<string, VariableSymbol> variables)
|
|
{
|
|
_diagnostics = diagnostics;
|
|
Scope = new BoundScope(variables, null);
|
|
}
|
|
|
|
private Binder(){}
|
|
|
|
internal static Binder CreateWithSetScope(Diagnostics diagnostics, BoundScope scope)
|
|
{
|
|
return new Binder {_diagnostics = diagnostics, Scope = scope};
|
|
}
|
|
|
|
|
|
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.AssignToNearest(valueParameter);
|
|
}
|
|
unboundFunctionStatement.Value.Block =
|
|
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.Value.UnboundBlock);
|
|
Scope = Scope.ParentScope;
|
|
}
|
|
_unboundFunctions = new Dictionary<string, UnboundFunctionExpression>();
|
|
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((AssignmentStatementSyntax) s);
|
|
case SyntaxKind.BlockStatement:
|
|
return BindBlockStatement((BlockStatementSyntax) s);
|
|
case SyntaxKind.IfStatement:
|
|
return BindIfStatement((IfStatementSyntax) s);
|
|
case SyntaxKind.ReturnStatement:
|
|
return BindReturnStatement((ReturnStatementSyntax) s);
|
|
case SyntaxKind.FunctionAssignmentStatement:
|
|
return BindFunctionAssignmentStatement((FunctionAssignmentStatementSyntax) s);
|
|
case SyntaxKind.TableAssignmentStatement:
|
|
return BindTableAssignmentStatement((TableAssigmentStatementSyntax) s);
|
|
case SyntaxKind.MultiAssignmentStatement:
|
|
return BindMultiAssignmentStatement((MultiAssignmentStatementSyntax) s);
|
|
case SyntaxKind.NumericForStatement:
|
|
return BindNumericForStatement((NumericForStatementSyntax) s);
|
|
case SyntaxKind.GenericForStatement:
|
|
return BindGenericForStatement((GenericForStatementSyntax) s);
|
|
|
|
case SyntaxKind.BreakStatement:
|
|
return new BoundBreakStatement();
|
|
}
|
|
|
|
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.TableExpression:
|
|
return BindTableExpression((TableExpressionSyntax) e);
|
|
case SyntaxKind.IndexExpression:
|
|
return BindIndexExpression((IndexExpressionSyntax) e);
|
|
case SyntaxKind.FullStopIndexExpression:
|
|
return BindFullStopIndexExpression((FullStopIndexExpressionSyntax) e);
|
|
case SyntaxKind.FunctionExpression:
|
|
return BindFunctionExpression((FunctionExpressionSyntax) 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;
|
|
ScriptType outValue = null;
|
|
switch (value)
|
|
{
|
|
case double d:
|
|
outValue = new ScriptNumberDouble(d);
|
|
break;
|
|
case long l:
|
|
outValue = new ScriptNumberLong(l);
|
|
break;
|
|
case bool b:
|
|
outValue = new ScriptBoolean(b);
|
|
break;
|
|
case string s:
|
|
outValue = new ScriptString(s);
|
|
break;
|
|
case null:
|
|
outValue = new ScriptNull();
|
|
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 expression = BindExpression(e.Identifier);
|
|
if (expression.Type != Type.Function && expression.Type != Type.Unknown)
|
|
{
|
|
//TODO Better error
|
|
throw new Exception();
|
|
}
|
|
|
|
var parameters = ImmutableArray.CreateBuilder<BoundExpression>();
|
|
foreach (var expressionSyntax in e.Parameters)
|
|
{
|
|
var bound = BindExpression(expressionSyntax);
|
|
parameters.Add(bound);
|
|
}
|
|
|
|
if (expression.Kind == BoundKind.VariableExpression)
|
|
{
|
|
var variableExpression =(BoundVariableExpression) expression;
|
|
var function = (FunctionVariableSymbol)variableExpression.Variable;
|
|
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.DefineLocalVariable(functionVariable);
|
|
}
|
|
|
|
var unboundFunctionStatement = _unboundFunctions[function.Name];
|
|
unboundFunctionStatement.Block =
|
|
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.UnboundBlock);
|
|
Scope = Scope.ParentScope;
|
|
function.IsBound = true;
|
|
_unboundFunctions.Remove(function.Name);
|
|
}
|
|
}
|
|
|
|
//TODO: validate parameters
|
|
return new BoundFunctionCallExpression(expression, 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 ScriptNull());
|
|
}
|
|
|
|
return new BoundVariableExpression(variable);
|
|
}
|
|
|
|
private BoundStatement BindExpressionStatement(ExpressionStatementSyntax s)
|
|
{
|
|
var exp = BindExpression(s.Expression);
|
|
return new BoundExpressionStatement(exp);
|
|
}
|
|
|
|
private VariableSymbol TryBindVariable(string name, bool isLocal, BoundExpression assignment)
|
|
{
|
|
if (name == "_")
|
|
return null;
|
|
if (!Scope.TryGetVariable(name, !isLocal, out var variable))
|
|
{
|
|
if (assignment.Type == Type.Table)
|
|
{
|
|
var tableExpression = (BoundTableExpression) assignment;
|
|
variable = new TableVariableSymbol(name, tableExpression.ValueType, isLocal);
|
|
}
|
|
else
|
|
{
|
|
variable = new VariableSymbol(name, assignment.Type, isLocal);
|
|
}
|
|
|
|
if (isLocal)
|
|
Scope.DefineLocalVariable(variable);
|
|
else
|
|
Scope.AssignToNearest(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 (assignment.Type != variable.Type)
|
|
{
|
|
if (variable.Type == Type.Nil || assignment.Type == Type.Nil)
|
|
{
|
|
variable.Type = assignment.Type;
|
|
}
|
|
else if (variable.Type == Type.Unknown)
|
|
{
|
|
variable.Type = assignment.Type;
|
|
}
|
|
else if (assignment.Type == Type.Unknown && assignment is BoundVariableExpression v)
|
|
{
|
|
v.Variable.Type = variable.Type;
|
|
}
|
|
else if (assignment.Type == Type.Unknown)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
_diagnostics.LogCannotConvert(assignment.Type, variable.Type, assignment.Span);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
return variable;
|
|
}
|
|
|
|
private BoundStatement BindAssignmentStatement(AssignmentStatementSyntax e)
|
|
{
|
|
if (e.Identifier.Kind == SyntaxKind.VariableExpression)
|
|
{
|
|
var variableExpression = (VariableExpressionSyntax) e.Identifier;
|
|
var name = variableExpression.Identifier.Name;
|
|
var boundExpression = BindExpression(e.Expression);
|
|
|
|
var isLocal = e.LocalToken != null;
|
|
var boundVariable = TryBindVariable(name, isLocal, boundExpression);
|
|
if (boundVariable != null)
|
|
{
|
|
return new BoundVariableAssignment(boundVariable, boundExpression, isLocal);
|
|
}
|
|
|
|
}
|
|
|
|
return new BoundExpressionStatement(new BoundLiteralExpression(new ScriptNull()));
|
|
}
|
|
|
|
private BoundStatement BindMultiAssignmentStatement(MultiAssignmentStatementSyntax s)
|
|
{
|
|
var ls = new List<VariableSymbol>();
|
|
var assignment = BindExpression(s.Expression);
|
|
var isLocal = s.LocalKeyword != null;
|
|
|
|
foreach (var identifierToken in s.Identifiers)
|
|
{
|
|
var boundVariable = TryBindVariable(identifierToken.Name, isLocal, assignment);
|
|
ls.Add(boundVariable);
|
|
}
|
|
return new BoundMultiAssignmentStatement(ls.ToImmutableArray(), assignment);
|
|
}
|
|
|
|
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 BoundExpression BindFunctionExpression(FunctionExpressionSyntax e, string variableSymbol = 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.DefineLocalVariable(vari);
|
|
}
|
|
|
|
if (parameters.Count == 0)
|
|
{
|
|
Scope = innerScope;
|
|
var block = BindBlockStatement(e.Block);
|
|
Scope = Scope.ParentScope;
|
|
var func = new BoundFunctionExpression(parameters.ToImmutable(), (BoundBlockStatement) block);
|
|
return func;
|
|
}
|
|
else
|
|
{
|
|
var unbound = new UnboundFunctionExpression(parameters.ToImmutable(), e.Block);
|
|
if (variableSymbol == null)
|
|
{
|
|
_unboundFunctions.Add( Guid.NewGuid().ToString(), unbound);
|
|
}
|
|
else
|
|
{
|
|
_unboundFunctions.Add(variableSymbol, unbound);
|
|
}
|
|
return unbound;
|
|
}
|
|
}
|
|
|
|
private BoundStatement BindFunctionAssignmentStatement(FunctionAssignmentStatementSyntax e)
|
|
{
|
|
var name = e.Identifier.Name;
|
|
var isLocal = e.LocalToken != null;
|
|
var innerScope = new BoundScope(Scope);
|
|
|
|
var func = (BoundFunctionExpression)BindFunctionExpression(e.FunctionExpression, name);
|
|
var parameters = ImmutableArray.CreateBuilder<VariableSymbol>();
|
|
|
|
foreach (var identifierToken in func.Parameters)
|
|
{
|
|
var vari = new VariableSymbol(identifierToken.Name, Type.Unknown, true);
|
|
parameters.Add(vari);
|
|
innerScope.DefineLocalVariable(vari);
|
|
}
|
|
|
|
|
|
if (!Scope.TryGetVariable(name, !isLocal, out var variable))
|
|
{
|
|
variable = new FunctionVariableSymbol(name, Type.Function, isLocal, parameters.ToImmutable());
|
|
((FunctionVariableSymbol) variable).IsBound = !(func is UnboundFunctionExpression);
|
|
if (isLocal)
|
|
Scope.DefineLocalVariable(variable);
|
|
else
|
|
Scope.AssignToNearest(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(new BoundLiteralExpression(new ScriptNull()));
|
|
}
|
|
}
|
|
}
|
|
|
|
return new BoundFunctionAssignmentStatement(variable, func);
|
|
}
|
|
|
|
private BoundStatement BindReturnStatement(ReturnStatementSyntax e)
|
|
{
|
|
var expression = BindExpression(e.Expression);
|
|
return new BoundReturnStatement(expression);
|
|
}
|
|
|
|
private BoundExpression BindTableExpression(TableExpressionSyntax e)
|
|
{
|
|
var keyType = Type.Unknown;
|
|
var valueType = Type.Unknown;
|
|
var dictionary = new Dictionary<string, bool>();
|
|
var statements = ImmutableArray.CreateBuilder<BoundStatement>();
|
|
var s = Scope;
|
|
Scope = BoundScope.WithReadOnlyScope(s);
|
|
foreach (var expressionSyntax in e.Expressions)
|
|
{
|
|
var bound = BindStatement((StatementSyntax) expressionSyntax);
|
|
statements.Add(bound);
|
|
}
|
|
|
|
foreach (var variable in Scope.Variables)
|
|
{
|
|
dictionary.Add(variable.Key, true);
|
|
}
|
|
Scope = s;
|
|
return new BoundTableExpression(keyType, valueType, dictionary, statements);
|
|
}
|
|
|
|
private BoundExpression BindIndexExpression(IndexExpressionSyntax e)
|
|
{
|
|
var expression = BindExpression(e.Expression);
|
|
|
|
var index = BindExpression(e.Index);
|
|
if (index.Type != Type.Number && index.Type != Type.String && index.Type != Type.Unknown)
|
|
{
|
|
_diagnostics.LogInvalidIndexExpression(expression.Type, index.Type, e.Span);
|
|
return new BoundLiteralExpression(new ScriptNull());
|
|
}
|
|
switch (expression.Type)
|
|
{
|
|
case Type.Table:
|
|
case Type.UserData:
|
|
case Type.Unknown:
|
|
return new BoundIndexExpression(expression, index, Type.Unknown);
|
|
case Type.String when index.Type == Type.Number:
|
|
return new BoundIndexExpression(expression, index, Type.String);
|
|
default:
|
|
_diagnostics.LogInvalidIndexExpression(expression.Type, index.Type, e.Span);
|
|
return new BoundLiteralExpression(new ScriptNull());
|
|
}
|
|
}
|
|
|
|
private BoundExpression BindFullStopIndexExpression(FullStopIndexExpressionSyntax e)
|
|
{
|
|
var expression = BindExpression(e.Expression);
|
|
var index = e.Index.Name;
|
|
switch (expression.Type)
|
|
{
|
|
case Type.Table:
|
|
case Type.UserData:
|
|
case Type.Unknown:
|
|
return new BoundFullStopIndexExpression(expression, index);
|
|
case Type.String:
|
|
return new BoundFullStopIndexExpression(expression, index);
|
|
default:
|
|
_diagnostics.LogInvalidIndexExpression(expression.Type, Type.String, e.Span);
|
|
return new BoundLiteralExpression(new ScriptNull());
|
|
}
|
|
}
|
|
|
|
private BoundStatement BindTableAssignmentStatement(TableAssigmentStatementSyntax e)
|
|
{
|
|
BoundExpression indexableExpression;
|
|
if (e.TableExpression.Kind == SyntaxKind.IndexExpression)
|
|
indexableExpression = (BoundIndexExpression)BindExpression(e.TableExpression);
|
|
else
|
|
indexableExpression = (BoundFullStopIndexExpression)BindExpression(e.TableExpression);
|
|
|
|
var value = BindExpression(e.Expression);
|
|
return new BoundTableAssigmentStatement(indexableExpression, value);
|
|
}
|
|
|
|
private BoundStatement BindNumericForStatement(NumericForStatementSyntax e)
|
|
{
|
|
var variableName = e.Identifier.Name;
|
|
Scope = new BoundScope(Scope);
|
|
var variable = new VariableSymbol(variableName, Type.Number, true);
|
|
Scope.DefineLocalVariable(variable);
|
|
var boundStart = BindExpression(e.StartExpression);
|
|
var boundStop = BindExpression(e.StopExpression);
|
|
BoundExpression boundStep = null;
|
|
if (e.StepExpression != null)
|
|
boundStep = BindExpression(e.StepExpression);
|
|
var block = BindBlockStatement((BlockStatementSyntax) e.Block);
|
|
Scope = Scope.ParentScope;
|
|
return new BoundNumericForStatement(variable, boundStart, boundStop, boundStep, (BoundBlockStatement) block);
|
|
}
|
|
|
|
private BoundStatement BindGenericForStatement(GenericForStatementSyntax e)
|
|
{
|
|
Scope = new BoundScope(Scope);
|
|
var array = ImmutableArray.CreateBuilder<VariableSymbol>();
|
|
foreach (var variableIdentifier in e.Variables)
|
|
{
|
|
var variable = new VariableSymbol(variableIdentifier.Name, Type.Unknown, true);
|
|
Scope.DefineLocalVariable(variable);
|
|
array.Add(variable);
|
|
}
|
|
var boundEnumerableExpression = BindExpression(e.EnumerableExpression);
|
|
var block = BindBlockStatement(e.Block);
|
|
|
|
return new BoundGenericForStatement(array.ToImmutable(), boundEnumerableExpression, block);
|
|
}
|
|
}
|
|
} |