Upsilon/Upsilon/Binder/Binder.cs

1084 lines
50 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Upsilon.BaseTypes;
using Upsilon.BaseTypes.Number;
using Upsilon.Binder.VariableSymbols;
using Upsilon.BoundTypes;
using Upsilon.Evaluator;
using Upsilon.Parser;
using Type = Upsilon.BaseTypes.Type;
namespace Upsilon.Binder
{
public class Binder : IDisposable
{
private Diagnostics _diagnostics;
public BoundScope Scope { get; private set; }
private readonly Script _script;
private List<UnboundFunctionExpression> _unboundFunctions =
new List<UnboundFunctionExpression>();
public Binder(Diagnostics diagnostics, Dictionary<string, VariableSymbol> variables, Script script)
{
_diagnostics = diagnostics;
_script = script;
Scope = new BoundScope(variables, null);
}
private Binder(Script script)
{
_script = script;
}
internal static Binder CreateWithSetScope(Diagnostics diagnostics, BoundScope scope, Script script)
{
return new Binder(script) {_diagnostics = diagnostics, Scope = scope};
}
public void Dispose()
{
Scope?.Dispose();
Scope = null;
_unboundFunctions.Clear();
}
public BoundScript BindScript(string fileName, BlockStatementSyntax e)
{
var bound = BindStatement(e);
foreach (var unboundFunctionStatement in _unboundFunctions)
{
Scope = new BoundScope(Scope);
foreach (var valueParameter in unboundFunctionStatement.Parameters)
{
Scope.AssignToNearest(valueParameter.VariableSymbol);
if (valueParameter.VariableSymbol.TypeContainer == Type.Unknown)
_diagnostics.LogUnknownVariableType(valueParameter.VariableSymbol.Name, valueParameter.Span);
}
unboundFunctionStatement.Block =
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.UnboundBlock);
Scope = Scope.ParentScope;
}
_unboundFunctions.Clear();
return new BoundScript((BoundBlockStatement) bound, e.Span, Scope, fileName, _script);
}
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.WhileStatement:
return BindWhileStatement((WhileStatementSyntax) s);
case SyntaxKind.YieldStatement:
return BindYieldStatement((YieldStatementSyntax) s);
case SyntaxKind.BreakStatement:
return new BoundBreakStatement(s.Span);
}
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:
return new BoundBadExpression(e.Span);
case SyntaxKind.ScriptUnit:
break;
default:
throw new ArgumentOutOfRangeException();
}
throw new NotImplementedException(e.Kind.ToString());
}
private BoundExpression BindUnaryExpression(UnaryExpressionSyntax e)
{
var inExp = BindExpression(e.Expression);
if (inExp.Kind == BoundKind.BoundBadExpression)
return new BoundBadExpression(e.Span);
var op = BoundUnaryOperator.Bind(e.Operator.Kind, inExp.ValueType);
if (op == null)
{
_diagnostics.LogUnknownUnaryOperator(e.Span, e.Operator.Kind, inExp.ValueType);
return inExp;
}
return new BoundUnaryExpression(op, inExp, op.OutType, e.Span);
}
private BoundExpression BindBinaryExpression(BinaryExpressionSyntax e)
{
var left = BindExpression(e.Left);
var right = BindExpression(e.Right);
if (left.Kind == BoundKind.BoundBadExpression || right.Kind == BoundKind.BoundBadExpression)
return new BoundBadExpression(e.Span);
var op = BoundBinaryOperator.Bind(e.Operator.Kind, left.ValueType, right.ValueType);
if (op == null)
{
_diagnostics.LogUnknownBinaryOperator(e.Span, e.Operator.Kind, left.ValueType, right.ValueType);
return left;
}
return new BoundBinaryExpression(op, left, right, op.OutType, e.Span);
}
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, e.Span);
}
private BoundExpression BindParenthesizedExpression(ParenthesizedExpressionSyntax e)
{
return BindExpression(e.Expression);
}
private BoundExpression BindFunctionCallExpression(FunctionCallExpressionSyntax e)
{
var expression = BindExpression(e.Identifier);
if (expression.ValueType != Type.Function && expression.ValueType != Type.Unknown)
{
_diagnostics.LogError($"Unknown function called.", e.Span);
return new BoundBadExpression(e.Span);
}
var parameters = ImmutableArray.CreateBuilder<BoundExpression>();
foreach (var expressionSyntax in e.Parameters)
{
var bound = BindExpression(expressionSyntax);
parameters.Add(bound);
}
TypeContainer returnType = Type.Unknown;
var resolved = ResolveVariable(expression, _diagnostics);
if (resolved == null)
{
_diagnostics.LogError("Can't resolve variable", expression.Span);
}
else if (resolved is FunctionVariableSymbol function)
{
if (function is ScriptFunctionVariableSymbol scriptFunction)
{
if ((scriptFunction.GetFirstValid(parameters.Select(x => x.ValueType).ToArray()) is ScriptFunctionVariableOption functionOption))
{
if (!functionOption.IsBound)
{
Scope = new BoundScope(Scope);
for (var index = 0; index < functionOption.Parameters.Length; index++)
{
var functionVariable = functionOption.Parameters[index];
var callingVariable = parameters[index];
functionVariable.TypeContainer = callingVariable.ValueType;
Scope.DefineLocalVariable(functionVariable);
}
UnboundFunctionExpression unbound = null;
foreach (var functionExpression in _unboundFunctions.Where(x =>
{
if (x.Name == function.Name)
{
return parameters.Count == functionOption.Parameters.Length;
}
return false;
}))
{
unbound = functionExpression;
break;
}
if (unbound != null)
{
unbound.Block = (BoundBlockStatement) BindBlockStatement(unbound.UnboundBlock);
returnType = Scope.ReturnType;
Scope = Scope.ParentScope;
functionOption.IsBound = true;
functionOption.ResultType = returnType;
}
}
}
}
else
{
if (string.Equals(function.Name, "require") && parameters.Count > 0)
{
var parameter = parameters[0];
if (parameter is BoundLiteralExpression be && be.Value.Type == Type.String)
{
var moduleName = be.Value.ToString();
var module = _script.Options.ModuleHandler.GetModule(_script, moduleName);
if (!_script.ModuleDependencies.Contains(moduleName))
_script.ModuleDependencies.Add(moduleName);
if (module == null)
{
var fullPath = Path.GetFullPath(_script.Options.ScriptLoader.ModulesPath);
_diagnostics.LogError(
$"Can't find module '{moduleName}' in folder '{fullPath}'",
parameter.Span);
}
else
{
foreach (var moduleVariable in module.Scope.Variables)
{
if (moduleVariable.Value.Local)
continue;
Scope.AssignToNearest(moduleVariable.Value);
}
}
}
}
if (resolved is InternalFunctionVariableSymbol internalFunction)
{
returnType = internalFunction.GetResultType(parameters.ToArray());
}
}
var isValid = function.ValidateParameters(parameters.ToImmutable());
if (!isValid)
{
_diagnostics.LogError(
$"No valid function with name '{function.Name}' and parameter types {string.Join(", ", parameters.Select(x => $"'{x.ValueType}'"))} found",
expression.Span);
}
}
else if (resolved is UserDataVariableSymbol udSymbol)
{
if (udSymbol.Parent != null )
{
if (udSymbol.Parent.Properties.TryGetValue(resolved.Name.ToLowerInvariant(),
out var ubProperty) && ubProperty is UserDataBoundMethod ubMethod)
{
var option = ubMethod.Validate(parameters.ToImmutable());
if (option == null)
{
_diagnostics.LogError(
$"No valid function with name '{ubMethod.Name}' and parameter types {string.Join(", ", parameters.Select(x => $"'{x.ValueType}'"))} found",
expression.Span);
}
else
{
returnType = option.ResultType;
}
}
}
}
return new BoundFunctionCallExpression(expression, parameters.ToImmutable(), e.Span, returnType);
}
public static VariableSymbol ResolveVariable(BoundExpression expression, Diagnostics diagnostics)
{
if (expression.Kind == BoundKind.VariableExpression)
{
var variableExpression = (BoundVariableExpression) expression;
return variableExpression.Variable.VariableSymbol;
}
if (expression.Kind == BoundKind.BoundFullstopIndexExpression)
{
var fullStopIndexExpression = (BoundFullStopIndexExpression) expression;
var indexerExpression = fullStopIndexExpression.Expression;
var indexerVariable = ResolveVariable(indexerExpression, diagnostics);
if (indexerVariable == null)
{
diagnostics?.LogError("Can't resolve variable", expression.Span);
}
else if (indexerVariable.TypeContainer == Type.Table)
{
return ((TableVariableSymbol)indexerVariable).Variables[fullStopIndexExpression.Index];
}
else if (indexerVariable.TypeContainer == Type.UserData)
{
var parent =
(UserDataBoundTypeDefinition) ((UserDataVariableSymbol) indexerVariable).BoundTypeDefinition;
if (parent.Properties.TryGetValue(fullStopIndexExpression.Index.ToLowerInvariant(),
out var bDefProperty))
{
var boundDef = BoundTypeHandler.GetTypeDefinition(bDefProperty.Type.UserData ?? bDefProperty.ActualType);
if (boundDef != null)
{
return new UserDataVariableSymbol(fullStopIndexExpression.Index, boundDef, true, parent);
}
diagnostics?.LogWarning($"Can't resolve type '{bDefProperty.Type}'", fullStopIndexExpression.Span);
return new VariableSymbol(fullStopIndexExpression.Index, Type.Unknown, true);
}
else
{
diagnostics?.LogWarning(
$"Can't resolve property '{fullStopIndexExpression.Index}' on type {parent.Name}",
fullStopIndexExpression.Span);
}
}
else if (indexerVariable.TypeContainer == Type.Unknown)
{
if (indexerVariable is UserDataVariableSymbol funcSymbol)
{
var boundProp = funcSymbol.BoundTypeDefinition;
if (boundProp is UserDataBoundTypeDefinition bProp)
{
var property = bProp.Properties[fullStopIndexExpression.Index.ToLowerInvariant()];
var boundDef = BoundTypeHandler.GetTypeDefinition(property.ActualType);
return new UserDataVariableSymbol(fullStopIndexExpression.Index, boundDef, true, bProp);
}
}
return new VariableSymbol(fullStopIndexExpression.Index, Type.Unknown, true);
}
}
else if (expression.Kind == BoundKind.BoundIndexExpression)
{
var indexExpression = (BoundIndexExpression) expression;
var indexerExpression = indexExpression.Identifier;
var indexerVariable = ResolveVariable(indexerExpression, diagnostics);
if (indexerVariable == null)
{
diagnostics?.LogError("Can't resolve variable", expression.Span);
}
else if (indexerVariable.TypeContainer == Type.Table)
{
var tableVariable = (TableVariableSymbol)indexerVariable;
if (indexExpression.Index is BoundLiteralExpression literal)
{
if (literal.Value.Type == Type.Number || literal.Value.Type == Type.String)
{
var toString = literal.Value.ToString();
if (!tableVariable.Variables.TryGetValue(toString, out var variable) || variable.Local)
{
diagnostics?.LogError($"No variable '{toString}' found in table.", expression.Span);
return new VariableSymbol("", Type.Nil, true);
}
return new VariableSymbol("", variable.TypeContainer, true);
}
}
if (tableVariable.TypeContainer is CompositeTypeContainer compositeTypeContainer)
{
return new VariableSymbol("", compositeTypeContainer.Types[1], true);
}
return new VariableSymbol("", expression.ValueType, true);
}
else if (indexerVariable.TypeContainer == Type.Unknown)
{
return new VariableSymbol("", Type.Unknown, true);
}
}
else if (expression.Kind == BoundKind.BoundFunctionCallExpression)
{
if (expression.ValueType == Type.UserData)
{
var ud = expression.ValueType.UserData;
if (!string.IsNullOrEmpty(ud))
{
var boundDef = BoundTypeHandler.GetTypeDefinition(ud);
return new UserDataVariableSymbol("", boundDef, true);
}
}
return new VariableSymbol("", expression.ValueType, true);
}
else if (expression.ValueType == Type.Unknown)
{
return new VariableSymbol("", Type.Unknown, true);
}
return null;
}
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 BoundBadExpression(e.Span);
}
var boundVariable = new BoundVariableSymbol(variable, false, e.Identifier.Span);
return new BoundVariableExpression(boundVariable, e.Span);
}
private BoundExpressionStatement BindExpressionStatement(ExpressionStatementSyntax s)
{
var exp = BindExpression(s.Expression);
return new BoundExpressionStatement(exp, s.Span);
}
private (VariableSymbol Symbol, bool IsCreation) TryBindVariable(string name, bool isLocal, BoundExpression assignment, string[] commentData)
{
if (name == "_")
return (null, false);
var isCreation = false;
if (!Scope.TryGetVariable(name, !isLocal, out var variable))
{
if (assignment.ValueType == Type.Table)
{
if (assignment.Kind == BoundKind.BoundTableExpression)
{
variable = new TableVariableSymbol(name, isLocal,
((BoundTableExpression) assignment).Expressions, assignment.ValueType);
}
else if (assignment.Kind == BoundKind.VariableExpression)
{
variable = new TableVariableSymbol(name, isLocal,
((TableVariableSymbol) ((BoundVariableExpression) assignment).Variable.VariableSymbol)
.Variables, assignment.ValueType);
}
else
{
variable = new TableVariableSymbol(name, isLocal, assignment.ValueType);
}
}
else if (assignment.ValueType == Type.UserData)
{
var ud = assignment.ValueType.UserData;
if (string.IsNullOrEmpty(ud))
{
variable = new VariableSymbol(name, Type.Unknown, isLocal);
}
else
{
var boundType = BoundTypeHandler.GetTypeDefinition(ud);
if (boundType != null)
{
variable = new UserDataVariableSymbol(name,
BoundTypeHandler.GetTypeDefinition(ud), isLocal);
}
else
{
variable = new VariableSymbol(name, assignment.ValueType, isLocal);
}
}
}
else
{
variable = new VariableSymbol(name, assignment.ValueType, isLocal);
}
variable.CommentValue = commentData;
if (isLocal)
Scope.DefineLocalVariable(variable);
else
Scope.AssignToNearest(variable);
isCreation = true;
}
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.ValueType != variable.TypeContainer)
{
if (variable.TypeContainer == Type.Nil || assignment.ValueType == Type.Nil)
{
variable.TypeContainer = assignment.ValueType;
}
else if (variable.TypeContainer == Type.Unknown)
{
variable.TypeContainer = assignment.ValueType;
}
else if (assignment.ValueType == Type.Unknown && assignment is BoundVariableExpression v)
{
v.Variable.VariableSymbol.TypeContainer = variable.TypeContainer;
}
else if (assignment.ValueType == Type.Unknown)
{
}
else
{
_diagnostics.LogCannotConvert(assignment.ValueType, variable.TypeContainer, assignment.Span);
return (null, false);
}
}
}
return (variable, isCreation);
}
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 (symbol, isCreation) = TryBindVariable(name, isLocal, boundExpression, e.CommentData);
if (symbol != null)
{
if (symbol.TypeContainer == Type.Unknown)
{
_diagnostics.LogUnknownVariableType(symbol.Name, variableExpression.Span);
}
var variable = new BoundVariableSymbol(symbol, isCreation, variableExpression.Span);
return new BoundVariableAssignment(variable, boundExpression, isLocal, e.Span);
}
}
return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span);
}
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, null);
if (boundVariable.Symbol.TypeContainer == Type.Unknown)
{
_diagnostics.LogUnknownVariableType(boundVariable.Symbol.Name, identifierToken.Span);
}
ls.Add(boundVariable.Symbol);
}
return new BoundMultiAssignmentStatement(ls.ToImmutableArray(), assignment, s.Span);
}
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(), e.Span);
}
private BoundStatement BindIfStatement(IfStatementSyntax e)
{
Scope = new BoundScope(Scope);
var condition = BindExpressionStatement(e.Condition);
if (condition.Expression.ValueType != Type.Boolean && condition.Expression.ValueType != Type.Unknown)
{
_diagnostics.LogError("Condition for if statement should be a boolean.", condition.Span);
}
var block = BindBlockStatement(e.Block);
Scope = Scope.ParentScope;
if (e.NextElseIfStatement != null)
{
var nextElseIf = BindIfStatement(e.NextElseIfStatement);
return new BoundIfStatement(condition, (BoundBlockStatement) block,
(BoundIfStatement) nextElseIf, e.Span);
}
if (e.ElseStatement == null)
{
return new BoundIfStatement(condition, (BoundBlockStatement) block, e.Span);
}
else
{
Scope = new BoundScope(Scope);
var elseBlock = BindBlockStatement(e.ElseStatement.Block);
var elseStatement = new BoundElseStatement((BoundBlockStatement) elseBlock, e.Span);
Scope = Scope.ParentScope;
return new BoundIfStatement(condition, (BoundBlockStatement) block,
elseStatement, e.Span);
}
}
private BoundExpression BindFunctionExpression(FunctionExpressionSyntax e, string functionVariableSymbol = null)
{
var innerScope = new BoundScope(Scope);
var parameters = ImmutableArray.CreateBuilder<BoundVariableSymbol>();
foreach (var identifierToken in e.Parameters)
{
var variable = identifierToken;
VariableSymbol variableSymbol;
if (variable.TypeName != null)
{
var type = BoundTypeHandler.GetTypeDefinition(variable.TypeName.Name);
if (type == null)
{
_diagnostics.LogError($"Unknown type name '{variable.TypeName.Name}'", variable.Span);
variableSymbol = new UserDataVariableSymbol(variable.IdentifierName.Name, Type.Unknown, true);
}
else if (type.ScriptType == Type.Table)
{
variableSymbol = new TableVariableSymbol(variable.IdentifierName.Name, true,
new CompositeTypeContainer(
new TypeContainer[] {Type.Unknown, Type.Unknown}.ToImmutableArray()));
}
else
{
variableSymbol = new UserDataVariableSymbol(variable.IdentifierName.Name, type, true);
}
}
else
{
variableSymbol = new UserDataVariableSymbol(variable.IdentifierName.Name, Type.Unknown, true);
}
parameters.Add(new BoundVariableSymbol(variableSymbol, true, identifierToken.Span));
innerScope.DefineLocalVariable(variableSymbol);
}
if (parameters.All(x => x.ValueType != Type.Unknown))
{
Scope = innerScope;
var block = BindBlockStatement(e.Block);
var returnType = Scope.ReturnType;
Scope = Scope.ParentScope;
var func = new BoundFunctionExpression(parameters.ToImmutable(), (BoundBlockStatement) block, e.Span,
innerScope, returnType, e.IsCoroutine);
return func;
}
else
{
var unbound = new UnboundFunctionExpression(parameters.ToImmutable(), e.Block, e.Span, innerScope,
functionVariableSymbol, e.IsCoroutine);
_unboundFunctions.Add(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 parameter in func.Parameters)
{
var vari = new VariableSymbol(parameter.VariableSymbol.Name, parameter.VariableSymbol.TypeContainer, true);
parameters.Add(vari);
innerScope.DefineLocalVariable(vari);
}
var commentData = new List<string>();
if (e.CommentData != null)
{
commentData.AddRange(e.CommentData);
}
if (!Scope.TryGetVariable(name, !isLocal, out var variable))
{
var functionVariable = new ScriptFunctionVariableSymbol(name, isLocal, parameters.ToImmutable(), func.ReturnType
, func.IsCoroutine)
{
CommentValue = commentData.ToArray()
};
variable = functionVariable;
((ScriptFunctionVariableOption)functionVariable.FunctionOption[0]).IsBound = !(func is UnboundFunctionExpression);
functionVariable.FunctionOption[0].ResultType = func.ReturnType;
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.TypeContainer != Type.Function)
{
if (variable.TypeContainer == Type.Nil || variable.TypeContainer == Type.Unknown)
{
variable.TypeContainer = Type.Function;
}
else
{
_diagnostics.LogCannotConvert(Type.Function, variable.TypeContainer, e.Span);
return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span);
}
}
else
{
if (variable is ScriptFunctionVariableSymbol functionVariable)
{
var functionOption =
new ScriptFunctionVariableOption(func.ReturnType, parameters.ToImmutable())
{
IsBound = !(func is UnboundFunctionExpression)
};
functionVariable.FunctionOption.Add(functionOption);
}
}
}
return new BoundFunctionAssignmentStatement(variable, func, e.Span);
}
private BoundStatement BindReturnStatement(ReturnStatementSyntax e)
{
if (e.Expression == null)
return new BoundReturnStatement(null, e.Span);
var expression = BindExpression(e.Expression);
if (expression.ValueType != Type.Unknown && expression.ValueType != Type.Unknown &&
Scope.ReturnType != Type.Unknown && Scope.ReturnType != Type.Nil)
{
if (expression.ValueType != Scope.ReturnType)
{
_diagnostics.LogError($"Can't return type '{expression.ValueType}' from this scope, earlier in the" +
$" scope a return type of '{Scope.ReturnType}' is defined.", e.Span);
}
}
if (expression.ValueType != Type.Unknown && expression.ValueType != Type.Nil)
Scope.ReturnType = expression.ValueType;
return new BoundReturnStatement(expression, e.Span);
}
private BoundExpression BindTableExpression(TableExpressionSyntax e)
{
var statements = ImmutableArray.CreateBuilder<BoundStatement>();
var s = Scope;
Scope = BoundScope.WithReadOnlyScope(s);
var innerVariables = new Dictionary<string, VariableSymbol>();
var current = 0;
foreach (var expressionSyntax in e.Expressions)
{
var bound = BindStatement((StatementSyntax) expressionSyntax);
if (bound.Kind == BoundKind.BoundExpressionStatement)
{
var boundExpression = (BoundExpressionStatement)bound;
current++;
innerVariables.Add(current.ToString(),
new VariableSymbol(current.ToString(), boundExpression.Expression.ValueType, false));
}
statements.Add(bound);
}
innerVariables = innerVariables.Union(Scope.Variables).ToDictionary(k => k.Key, v => v.Value);
var innerSCope = Scope;
Scope = s;
return new BoundTableExpression(innerVariables, statements.ToImmutable(), e.Span, innerSCope);
}
private BoundExpression BindIndexExpression(IndexExpressionSyntax e, bool isAssignment = false)
{
var expression = BindExpression(e.Expression);
var index = BindExpression(e.Index);
switch (expression.ValueType.Type)
{
case Type.Table:
if (isAssignment)
{
return new BoundIndexExpression(expression, index, Type.Unknown, e.Span);
}
if (expression.Kind == BoundKind.BoundTableExpression && index.Kind == BoundKind.BoundLiteralExpression)
{
var table = (BoundTableExpression)expression;
var realIndex = (BoundLiteralExpression) index;
var variableDic = table.Expressions;
if (variableDic.TryGetValue(realIndex.Value.ToString(), out var variable) && !variable.Local)
{
return new BoundIndexExpression(expression, index, variable.TypeContainer, e.Span);
}
_diagnostics.LogError($"No variable '{realIndex.Value}' found in table.",
e.Span);
}
else if (expression.Kind == BoundKind.VariableExpression &&
index.Kind == BoundKind.BoundLiteralExpression)
{
var table = (BoundVariableExpression)expression;
var realTable = table.Variable;
var realIndex = (BoundLiteralExpression) index;
var tableSymbol = ((TableVariableSymbol) realTable.VariableSymbol);
if (!tableSymbol.ContentAware)
{
return new BoundIndexExpression(expression, index, Type.Unknown, e.Span);
}
var variableDic = tableSymbol.Variables;
if (variableDic.TryGetValue(realIndex.Value.ToString(), out var variable) && !variable.Local)
{
return new BoundIndexExpression(expression, index, variable.TypeContainer, e.Span);
}
_diagnostics.LogError($"No variable '{realIndex.Value}' found in table '{realTable.VariableSymbol.Name}'.",
e.Span);
}
else if (expression.ValueType is CompositeTypeContainer compositeTypeContainer)
{
return new BoundIndexExpression(expression, index, compositeTypeContainer.Types[1], e.Span);
}
return new BoundIndexExpression(expression, index, Type.Unknown, e.Span);
case Type.UserData:
case Type.Unknown:
return new BoundIndexExpression(expression, index, Type.Unknown, e.Span);
case Type.String when index.ValueType == Type.Number:
return new BoundIndexExpression(expression, index, Type.String, e.Span);
default:
_diagnostics.LogInvalidIndexExpression(expression.ValueType, index.ValueType, e.Span);
return new BoundBadExpression(e.Span);
}
}
private BoundExpression BindFullStopIndexExpression(FullStopIndexExpressionSyntax e, bool isAssignment = false)
{
var expression = BindExpression(e.Expression);
var index = e.Index.Name;
switch (expression.ValueType.Type)
{
case Type.Table:
if (isAssignment)
{
return new BoundFullStopIndexExpression(expression, index, Type.Unknown, e.Span);
}
if (expression.Kind == BoundKind.BoundTableExpression)
{
var table = (BoundTableExpression)expression;
if (table.Expressions.TryGetValue(index, out var variable) && !variable.Local)
{
return new BoundFullStopIndexExpression(expression, index, variable.TypeContainer, e.Span);
}
_diagnostics.LogError($"No variable '{index}' found in table.", e.Span);
}
if (expression.Kind == BoundKind.VariableExpression)
{
var table = (BoundVariableExpression)expression;
var realTable = table.Variable;
var variableDic = ((TableVariableSymbol) realTable.VariableSymbol).Variables;
if (variableDic.TryGetValue(index, out var variable) && !variable.Local)
{
return new BoundFullStopIndexExpression(expression, index, variable.TypeContainer, e.Span);
}
_diagnostics.LogError($"No variable '{index}' found in table '{realTable.VariableSymbol.Name}'.",
e.Span);
}
return new BoundFullStopIndexExpression(expression, index, Type.Unknown, e.Span);
case Type.UserData:
if (isAssignment)
{
return new BoundFullStopIndexExpression(expression, index, Type.Unknown, e.Span);
}
var variableSymbol = ResolveVariable(expression, _diagnostics);
if (variableSymbol == null)
{
_diagnostics.LogError("Can't resolve variable", expression.Span);
}
else
{
if (variableSymbol is UserDataVariableSymbol functionParameter)
{
var udBoundDef = (UserDataBoundTypeDefinition)functionParameter.BoundTypeDefinition;
if (udBoundDef.Properties.TryGetValue(index.ToLowerInvariant(), out var property))
{
return new BoundFullStopIndexExpression(expression, index, property.Type, e.Span);
}
_diagnostics.LogError($"No variable '{index}' found on type '{udBoundDef.Name}'.",
e.Span);
}
}
return new BoundFullStopIndexExpression(expression, index, Type.Unknown, e.Span);
case Type.Unknown:
return new BoundFullStopIndexExpression(expression, index, Type.Unknown, e.Span);
case Type.String:
return new BoundFullStopIndexExpression(expression, index, Type.String, e.Span);
default:
_diagnostics.LogInvalidIndexExpression(expression.ValueType, Type.String, e.Span);
return new BoundBadExpression(e.Span);
}
}
private BoundStatement BindTableAssignmentStatement(TableAssigmentStatementSyntax e)
{
BoundExpression indexableExpression;
var value = BindExpression(e.Expression);
if (e.TableExpression.Kind == SyntaxKind.BadExpression)
return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span);
if (e.TableExpression.Kind == SyntaxKind.IndexExpression)
{
var idExp = BindIndexExpression((IndexExpressionSyntax) e.TableExpression, true);
if (idExp is BoundBadExpression)
return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span);
var indexable = (BoundIndexExpression) idExp;
if (indexable.Identifier.Kind == BoundKind.VariableExpression &&
indexable.Index.Kind == BoundKind.BoundLiteralExpression)
{
var variable = (BoundVariableExpression)indexable.Identifier;
var index = (BoundLiteralExpression)indexable.Index;
var variableSymbol = variable.Variable.VariableSymbol;
if (variableSymbol is TableVariableSymbol tableVariableSymbol)
{
tableVariableSymbol.Variables.Add(index.Value.ToString(),
new VariableSymbol(index.Value.ToString(), value.ValueType, false));
}
}
indexableExpression = indexable;
}
else
{
var indexableExp = BindFullStopIndexExpression(
(FullStopIndexExpressionSyntax) e.TableExpression, true);
if (!(indexableExp is BoundFullStopIndexExpression indexable))
return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span);
if (indexable.Expression.Kind == BoundKind.VariableExpression)
{
var variable = (BoundVariableExpression)indexable.Expression;
if (variable.ValueType == Type.Table)
{
((TableVariableSymbol)variable.Variable.VariableSymbol).Variables.Add(indexable.Index,
new VariableSymbol(indexable.Index, value.ValueType, false));
}
}
indexableExpression = indexable;
}
return new BoundTableAssigmentStatement(indexableExpression, value, e.Span);
}
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,
e.Span);
}
private BoundStatement BindGenericForStatement(GenericForStatementSyntax e)
{
Scope = new BoundScope(Scope);
var array = ImmutableArray.CreateBuilder<BoundVariableSymbol>();
var keyVar = e.Variables[0];
var keyVariable = new VariableSymbol(keyVar.Name, Type.String, true);
Scope.DefineLocalVariable(keyVariable);
array.Add(new BoundVariableSymbol(keyVariable, true, keyVar.Span));
var boundEnumerableExpression = BindExpression(e.EnumerableExpression);
var valueVar = e.Variables[1];
VariableSymbol valueVariable;
if (boundEnumerableExpression.ValueType is CompositeTypeContainer composite && composite.Types.Length == 2)
{
var keyType = composite.Types[0];
keyVariable.TypeContainer = keyType;
var type = composite.Types[1];
if (type == Type.UserData)
{
var ud = type.UserData;
if (ud == null)
{
valueVariable = new VariableSymbol(valueVar.Name, Type.Unknown, true);
}
else
{
valueVariable = new UserDataVariableSymbol(valueVar.Name,
BoundTypeHandler.GetTypeDefinition(type.UserData), true);
}
}
else if (type == Type.Table)
{
valueVariable = new TableVariableSymbol(valueVar.Name, true, composite.Types[1]);
}
else
{
valueVariable = new VariableSymbol(valueVar.Name, composite.Types[1], true);
}
}
else
{
valueVariable = new VariableSymbol(valueVar.Name, Type.Unknown, true);
}
Scope.DefineLocalVariable(valueVariable);
array.Add(new BoundVariableSymbol(valueVariable, true, valueVar.Span));
var block = BindBlockStatement(e.Block);
return new BoundGenericForStatement(array.ToImmutable(), boundEnumerableExpression, block, e.Span);
}
private BoundStatement BindWhileStatement(WhileStatementSyntax e)
{
Scope = new BoundScope(Scope);
var condition = BindExpression(e.Expression);
var block = BindBlockStatement((BlockStatementSyntax) e.Block);
return new BoundWhileStatement(condition, block, e.Span);
}
private BoundYieldStatement BindYieldStatement(YieldStatementSyntax yieldStatementSyntax)
{
var expression = BindExpression(yieldStatementSyntax.Expression);
return new BoundYieldStatement(expression, yieldStatementSyntax.Span);
}
}
}