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 _unboundFunctions = new List(); public Binder(Diagnostics diagnostics, Dictionary 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(); 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(); 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(); 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(); 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(); 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(); 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(); var s = Scope; Scope = BoundScope.WithReadOnlyScope(s); var innerVariables = new Dictionary(); 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(); 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); } } }