From f55e6d314d7346cba6830ad89eb7bb3e76679a70 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Fri, 18 Jan 2019 16:09:25 +0100 Subject: [PATCH] Rework type binding to allow for type awareness in iterators --- .../ScriptMethodInfoFunction.cs | 14 +-- Upsilon/BaseTypes/TypeContainer.cs | 65 ++++++++++++ Upsilon/Binder/Binder.cs | 100 +++++++++++------- .../BoundExpressions/BoundBadExpression.cs | 2 +- .../BoundExpressions/BoundBinaryExpression.cs | 2 +- .../BoundExpressions/BoundExpression.cs | 2 +- .../BoundFunctionCallExpression.cs | 4 +- .../BoundFunctionExpression.cs | 3 +- .../BoundExpressions/BoundIndexExpression.cs | 4 +- .../BoundLiteralExpression.cs | 2 +- .../BoundExpressions/BoundTableExpression.cs | 36 ++++++- .../BoundExpressions/BoundUnaryExpression.cs | 2 +- .../BoundVariableExpression.cs | 3 +- .../UnboundFunctionExpression.cs | 2 +- Upsilon/Binder/BoundVariableSymbol.cs | 2 +- .../VariableSymbols/FunctionVariableSymbol.cs | 8 +- .../InternalFunctionVariableSymbol.cs | 65 +++++++----- .../ScriptFunctionVariableSymbol.cs | 8 +- .../VariableSymbols/TableVariableSymbol.cs | 9 +- .../VariableSymbols/UserDataVariableSymbol.cs | 12 +-- .../Binder/VariableSymbols/VariableSymbol.cs | 6 +- .../BoundTypes/UserDataBoundTypeDefinition.cs | 2 +- Upsilon/StandardLibraries/BasicFunctions.cs | 18 +++- .../ScriptFunctionAttribute.cs | 11 +- Upsilon/StandardLibraries/StaticScope.cs | 8 +- Upsilon/Text/TextSpan.cs | 4 +- UpsilonTests/GeneralTests/ForLoopTests.cs | 17 +++ 27 files changed, 286 insertions(+), 125 deletions(-) create mode 100644 Upsilon/BaseTypes/TypeContainer.cs diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs index f5104d2..93f6613 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs @@ -15,13 +15,13 @@ namespace Upsilon.BaseTypes.ScriptFunction public ScriptMethodInfoFunction(UserDataMethod method, object o, bool directTypeManipulation, bool passScriptReference = false, bool passScopeReference = false) { - _method = method; + Method = method; _object = o; _directTypeManipulation = directTypeManipulation; _passScriptReference = passScriptReference; _passScopeReference = passScopeReference; - ReturnType = _method.ReturnType; + ReturnType = Method.ReturnType; if (method.GetMethods().First().Attribute != null) { @@ -33,7 +33,7 @@ namespace Upsilon.BaseTypes.ScriptFunction } } - private readonly UserDataMethod _method; + public UserDataMethod Method { get; } private readonly object _object; private readonly bool _directTypeManipulation; private readonly bool _passScriptReference; @@ -42,7 +42,7 @@ namespace Upsilon.BaseTypes.ScriptFunction public IEnumerable GetParameterInfo() { - return _method.GetMethods().First().Parameters; + return Method.GetMethods().First().Parameters; } public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) @@ -61,7 +61,7 @@ namespace Upsilon.BaseTypes.ScriptFunction { // grab the parameters, and just invoke it var array = objects.ToArray(); - var methodInfo = _method.GetMethod(ref array); + var methodInfo = Method.GetMethod(ref array); result = methodInfo.Invoke(null, array); } else @@ -90,7 +90,7 @@ namespace Upsilon.BaseTypes.ScriptFunction foreach (var match in methods) { // if the method does not have the name we're looking for, continue - if (!string.Equals(match.Name, _method.Name)) continue; + if (!string.Equals(match.Name, Method.Name)) continue; // Use the type binder to check if the match is allowed if (UpsilonBinder.Default.IsValidMatch(match, convertedTypes)) { @@ -108,7 +108,7 @@ namespace Upsilon.BaseTypes.ScriptFunction if (method == null) { throw new ScriptRuntimeException( - $"Can't find method {_method.Name} with parameter types {string.Join(", ", convertedTypes.Select(x => x.Name))} on type {_object.GetType().Name}", + $"Can't find method {Method.Name} with parameter types {string.Join(", ", convertedTypes.Select(x => x.Name))} on type {_object.GetType().Name}", span, diagnostics.ScriptString.GetSpan(span)); } // get the method parameters diff --git a/Upsilon/BaseTypes/TypeContainer.cs b/Upsilon/BaseTypes/TypeContainer.cs new file mode 100644 index 0000000..900d050 --- /dev/null +++ b/Upsilon/BaseTypes/TypeContainer.cs @@ -0,0 +1,65 @@ +using System.Collections.Immutable; + +namespace Upsilon.BaseTypes +{ + public class TypeContainer + { + public Type Type { get; } + + protected TypeContainer(Type t) + { + Type = t; + } + + public static implicit operator TypeContainer (Type type) + { + return new TypeContainer(type); + } + + public static implicit operator Type(TypeContainer t) + { + return t.Type; + } + + protected bool Equals(TypeContainer other) + { + return Type == other.Type; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj is Type t) + { + return t == Type; + } + if (obj.GetType() != this.GetType()) return false; + return Equals((TypeContainer) obj); + } + + public override int GetHashCode() + { + return (int) Type; + } + + public static bool operator == (TypeContainer a, object b) + { + return Equals(a, b); + } + + public static bool operator != (TypeContainer a, object b) + { + return !Equals(a, b); + } + } + + public class CompositeTypeContainer : TypeContainer + { + public ImmutableArray Types { get; set; } + + public CompositeTypeContainer() : base(Type.Table) + { + } + } +} \ No newline at end of file diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 580398c..7dcee03 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -55,7 +55,7 @@ namespace Upsilon.Binder foreach (var valueParameter in unboundFunctionStatement.Value.Parameters) { Scope.AssignToNearest(valueParameter.VariableSymbol); - if (valueParameter.VariableSymbol.Type == Type.Unknown) + if (valueParameter.VariableSymbol.TypeContainer == Type.Unknown) _diagnostics.LogUnknownVariableType(valueParameter.VariableSymbol.Name, valueParameter.Span); } unboundFunctionStatement.Value.Block = @@ -220,13 +220,14 @@ namespace Upsilon.Binder parameters.Add(bound); } - var returnType = Type.Unknown; + TypeContainer returnType = Type.Unknown; var resolved = ResolveVariable(expression, _diagnostics); if (resolved == null) { _diagnostics.LogError("Can't resolve variable", expression.Span); } - if (resolved is FunctionVariableSymbol function) + + else if (resolved is FunctionVariableSymbol function) { if (function is ScriptFunctionVariableSymbol scriptFunction) { @@ -237,7 +238,7 @@ namespace Upsilon.Binder { var functionVariable = scriptFunction.Parameters[index]; var callingVariable = parameters[index]; - functionVariable.Type = callingVariable.Type; + functionVariable.TypeContainer = callingVariable.Type; Scope.DefineLocalVariable(functionVariable); } @@ -280,10 +281,16 @@ namespace Upsilon.Binder } } } + if (resolved is InternalFunctionVariableSymbol internalFunction) + { + returnType = internalFunction.GetResultType(parameters.ToArray()); + } + else + { + returnType = function.ResultType; + } } - returnType = function.ResultType; - var (isValid, error, wrongParameter) = function.ValidateParameters(parameters.ToImmutable()); if (!isValid) { @@ -331,11 +338,11 @@ namespace Upsilon.Binder { diagnostics?.LogError("Can't resolve variable", expression.Span); } - else if (indexerVariable.Type == Type.Table) + else if (indexerVariable.TypeContainer == Type.Table) { return ((TableVariableSymbol)indexerVariable).Variables[fullStopIndexExpression.Index]; } - else if (indexerVariable.Type == Type.UserData) + else if (indexerVariable.TypeContainer == Type.UserData) { var parent = (UserDataBoundTypeDefinition) ((UserDataVariableSymbol) indexerVariable).BoundTypeDefinition; @@ -349,7 +356,7 @@ namespace Upsilon.Binder } } } - else if (indexerVariable.Type == Type.Unknown) + else if (indexerVariable.TypeContainer == Type.Unknown) { if (indexerVariable is UserDataVariableSymbol funcSymbol) { @@ -405,17 +412,18 @@ namespace Upsilon.Binder { if (assignment.Kind == BoundKind.BoundTableExpression) { - variable = new TableVariableSymbol(name, isLocal, ((BoundTableExpression)assignment).Expressions); + variable = new TableVariableSymbol(name, isLocal, + ((BoundTableExpression) assignment).Expressions, assignment.Type); } else if (assignment.Kind == BoundKind.VariableExpression) { variable = new TableVariableSymbol(name, isLocal, ((TableVariableSymbol) ((BoundVariableExpression) assignment).Variable.VariableSymbol) - .Variables); + .Variables, assignment.Type); } else { - variable = new TableVariableSymbol(name, isLocal); + variable = new TableVariableSymbol(name, isLocal, assignment.Type); } } else @@ -435,26 +443,26 @@ namespace Upsilon.Binder 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 (assignment.Type != variable.TypeContainer) { - if (variable.Type == Type.Nil || assignment.Type == Type.Nil) + if (variable.TypeContainer == Type.Nil || assignment.Type == Type.Nil) { - variable.Type = assignment.Type; + variable.TypeContainer = assignment.Type; } - else if (variable.Type == Type.Unknown) + else if (variable.TypeContainer == Type.Unknown) { - variable.Type = assignment.Type; + variable.TypeContainer = assignment.Type; } else if (assignment.Type == Type.Unknown && assignment is BoundVariableExpression v) { - v.Variable.VariableSymbol.Type = variable.Type; + v.Variable.VariableSymbol.TypeContainer = variable.TypeContainer; } else if (assignment.Type == Type.Unknown) { } else { - _diagnostics.LogCannotConvert(assignment.Type, variable.Type, assignment.Span); + _diagnostics.LogCannotConvert(assignment.Type, variable.TypeContainer, assignment.Span); return (null, false); } } @@ -474,7 +482,7 @@ namespace Upsilon.Binder var (symbol, isCreation) = TryBindVariable(name, isLocal, boundExpression, e.CommentData); if (symbol != null) { - if (symbol.Type == Type.Unknown) + if (symbol.TypeContainer == Type.Unknown) { _diagnostics.LogUnknownVariableType(symbol.Name, variableExpression.Span); } @@ -496,7 +504,7 @@ namespace Upsilon.Binder foreach (var identifierToken in s.Identifiers) { var boundVariable = TryBindVariable(identifierToken.Name, isLocal, assignment, null); - if (boundVariable.Symbol.Type == Type.Unknown) + if (boundVariable.Symbol.TypeContainer == Type.Unknown) { _diagnostics.LogUnknownVariableType(boundVariable.Symbol.Name, identifierToken.Span); } @@ -610,7 +618,7 @@ namespace Upsilon.Binder foreach (var parameter in func.Parameters) { - var vari = new VariableSymbol(parameter.VariableSymbol.Name, parameter.VariableSymbol.Type, true); + var vari = new VariableSymbol(parameter.VariableSymbol.Name, parameter.VariableSymbol.TypeContainer, true); parameters.Add(vari); innerScope.DefineLocalVariable(vari); } @@ -637,15 +645,15 @@ namespace Upsilon.Binder 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.TypeContainer != Type.Function) { - if (variable.Type == Type.Nil || variable.Type == Type.Unknown) + if (variable.TypeContainer == Type.Nil || variable.TypeContainer == Type.Unknown) { - variable.Type = Type.Function; + variable.TypeContainer = Type.Function; } else { - _diagnostics.LogCannotConvert(Type.Function, variable.Type, e.Span); + _diagnostics.LogCannotConvert(Type.Function, variable.TypeContainer, e.Span); return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span); } } @@ -712,7 +720,7 @@ namespace Upsilon.Binder _diagnostics.LogInvalidIndexExpression(expression.Type, index.Type, e.Span); return new BoundBadExpression(e.Span); } - switch (expression.Type) + switch (expression.Type.Type) { case Type.Table: if (isAssignment) @@ -726,7 +734,7 @@ namespace Upsilon.Binder var variableDic = table.Expressions; if (variableDic.TryGetValue(realIndex.Value.ToString(), out var variable)) { - return new BoundIndexExpression(expression, index, variable.Type, e.Span); + return new BoundIndexExpression(expression, index, variable.TypeContainer, e.Span); } _diagnostics.LogError($"No variable '{realIndex.Value}' found in table.", @@ -746,7 +754,7 @@ namespace Upsilon.Binder var variableDic = tableSymbol.Variables; if (variableDic.TryGetValue(realIndex.Value.ToString(), out var variable)) { - return new BoundIndexExpression(expression, index, variable.Type, e.Span); + return new BoundIndexExpression(expression, index, variable.TypeContainer, e.Span); } _diagnostics.LogError($"No variable '{realIndex.Value}' found in table '{realTable.VariableSymbol.Name}'.", @@ -768,7 +776,7 @@ namespace Upsilon.Binder { var expression = BindExpression(e.Expression); var index = e.Index.Name; - switch (expression.Type) + switch (expression.Type.Type) { case Type.Table: if (isAssignment) @@ -780,7 +788,7 @@ namespace Upsilon.Binder var table = (BoundTableExpression)expression; if (table.Expressions.TryGetValue(index, out var variable)) { - return new BoundFullStopIndexExpression(expression, index, variable.Type, e.Span); + return new BoundFullStopIndexExpression(expression, index, variable.TypeContainer, e.Span); } _diagnostics.LogError($"No variable '{index}' found in table.", e.Span); } @@ -791,7 +799,7 @@ namespace Upsilon.Binder var variableDic = ((TableVariableSymbol) realTable.VariableSymbol).Variables; if (variableDic.TryGetValue(index, out var variable)) { - return new BoundFullStopIndexExpression(expression, index, variable.Type, e.Span); + return new BoundFullStopIndexExpression(expression, index, variable.TypeContainer, e.Span); } _diagnostics.LogError($"No variable '{index}' found in table '{realTable.VariableSymbol.Name}'.", @@ -897,13 +905,29 @@ namespace Upsilon.Binder { Scope = new BoundScope(Scope); var array = ImmutableArray.CreateBuilder(); - foreach (var variableIdentifier in e.Variables) - { - var variable = new VariableSymbol(variableIdentifier.Name, Type.Unknown, true); - Scope.DefineLocalVariable(variable); - array.Add(new BoundVariableSymbol(variable, true, variableIdentifier.Span)); - } + + 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.Type is CompositeTypeContainer composite && composite.Types.Length == 2) + { + valueVariable = new VariableSymbol(valueVar.Name, composite.Types[1], true); + } + else + { + throw new Exception(); + 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); diff --git a/Upsilon/Binder/BoundExpressions/BoundBadExpression.cs b/Upsilon/Binder/BoundExpressions/BoundBadExpression.cs index de7c972..be0b7a9 100644 --- a/Upsilon/Binder/BoundExpressions/BoundBadExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundBadExpression.cs @@ -17,6 +17,6 @@ namespace Upsilon.Binder yield break; } - public override Type Type => Type.Nil; + public override TypeContainer Type => BaseTypes.Type.Nil; } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs b/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs index 28b736c..188055b 100644 --- a/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs @@ -23,7 +23,7 @@ namespace Upsilon.Binder yield return RightExpression; } - public override Type Type { get; } + public override TypeContainer Type { get; } public BoundBinaryOperator Operator { get; } public BoundExpression LeftExpression { get; } diff --git a/Upsilon/Binder/BoundExpressions/BoundExpression.cs b/Upsilon/Binder/BoundExpressions/BoundExpression.cs index 1fc0bde..645589e 100644 --- a/Upsilon/Binder/BoundExpressions/BoundExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundExpression.cs @@ -9,6 +9,6 @@ namespace Upsilon.Binder { } - public abstract Type Type { get; } + public abstract TypeContainer Type { get; } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs b/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs index c98b876..290b071 100644 --- a/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs @@ -11,7 +11,7 @@ namespace Upsilon.Binder public ImmutableArray Parameters { get; } public BoundFunctionCallExpression(BoundExpression identifier, ImmutableArray parameters, - TextSpan span, Type type) : base(span) + TextSpan span, TypeContainer type) : base(span) { Identifier = identifier; Parameters = parameters; @@ -29,6 +29,6 @@ namespace Upsilon.Binder } } - public override Type Type { get; } + public override TypeContainer Type { get; } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs b/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs index 853fbb3..6bf1e75 100644 --- a/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using Upsilon.BaseTypes; using Upsilon.Text; using Type = Upsilon.BaseTypes.Type; @@ -31,7 +32,7 @@ namespace Upsilon.Binder yield return Block; } - public override Type Type => Type.Function; + public override TypeContainer Type => BaseTypes.Type.Function; public BoundScope Scope { get; set; } public Type ReturnType { get; } } diff --git a/Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs b/Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs index 4fe3ae3..7828ba4 100644 --- a/Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs @@ -25,7 +25,7 @@ namespace Upsilon.Binder yield return Index; } - public override Type Type { get; } + public override TypeContainer Type { get; } } public class BoundFullStopIndexExpression : BoundExpression @@ -47,6 +47,6 @@ namespace Upsilon.Binder yield return Expression; } - public override Type Type { get; } + public override TypeContainer Type { get; } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundExpressions/BoundLiteralExpression.cs b/Upsilon/Binder/BoundExpressions/BoundLiteralExpression.cs index b6d1978..6904e22 100644 --- a/Upsilon/Binder/BoundExpressions/BoundLiteralExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundLiteralExpression.cs @@ -18,7 +18,7 @@ namespace Upsilon.Binder yield break; } - public override Type Type => Value.Type; + public override TypeContainer Type => Value.Type; public ScriptType Value { get; } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundExpressions/BoundTableExpression.cs b/Upsilon/Binder/BoundExpressions/BoundTableExpression.cs index d6bc072..a5561dc 100644 --- a/Upsilon/Binder/BoundExpressions/BoundTableExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundTableExpression.cs @@ -1,6 +1,6 @@ -using System; using System.Collections.Generic; using System.Collections.Immutable; +using Upsilon.BaseTypes; using Upsilon.Binder.VariableSymbols; using Upsilon.Text; using Type = Upsilon.BaseTypes.Type; @@ -24,7 +24,39 @@ namespace Upsilon.Binder return Statements; } - public override Type Type => Type.Table; + public override TypeContainer Type + { + get + { + Type? valueType = null; + foreach (var statement in Statements) + { + if (!(statement is BoundExpressionStatement exp)) + { + valueType = BaseTypes.Type.Unknown; + break; + } + if (!valueType.HasValue) + { + valueType = exp.Expression.Type; + continue; + } + if (valueType == exp.Expression.Type) continue; + valueType = BaseTypes.Type.Unknown; + break; + } + + var valueRealType = BaseTypes.Type.Unknown; + if (valueType.HasValue) + valueRealType = valueType.Value; + + var arr = new[] {BaseTypes.Type.String, valueRealType}; + return new CompositeTypeContainer() + { + Types = arr.ToImmutableArray() + }; + } + } public Dictionary Expressions { get; } diff --git a/Upsilon/Binder/BoundExpressions/BoundUnaryExpression.cs b/Upsilon/Binder/BoundExpressions/BoundUnaryExpression.cs index 6e167ca..69370d5 100644 --- a/Upsilon/Binder/BoundExpressions/BoundUnaryExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundUnaryExpression.cs @@ -13,7 +13,7 @@ namespace Upsilon.Binder yield return InExpression; } - public override Type Type { get; } + public override TypeContainer Type { get; } public BoundUnaryExpression(BoundUnaryOperator op, BoundExpression inExpression, Type type, TextSpan span) : base(span) { diff --git a/Upsilon/Binder/BoundExpressions/BoundVariableExpression.cs b/Upsilon/Binder/BoundExpressions/BoundVariableExpression.cs index 19937e0..172cd48 100644 --- a/Upsilon/Binder/BoundExpressions/BoundVariableExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundVariableExpression.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Upsilon.BaseTypes; using Upsilon.Text; using Type = Upsilon.BaseTypes.Type; @@ -20,6 +21,6 @@ namespace Upsilon.Binder yield break; } - public override Type Type => Variable.Type; + public override TypeContainer Type => Variable.Type; } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs b/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs index 6c1532e..ecf2927 100644 --- a/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs +++ b/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs @@ -10,7 +10,7 @@ namespace Upsilon.Binder { public UnboundFunctionExpression(ImmutableArray parameters, BlockStatementSyntax unboundBlock, TextSpan span, BoundScope scope) - : base(parameters, null, span, scope, Type.Unknown) + : base(parameters, null, span, scope, BaseTypes.Type.Unknown) { UnboundBlock = unboundBlock; } diff --git a/Upsilon/Binder/BoundVariableSymbol.cs b/Upsilon/Binder/BoundVariableSymbol.cs index dab8704..e3533b8 100644 --- a/Upsilon/Binder/BoundVariableSymbol.cs +++ b/Upsilon/Binder/BoundVariableSymbol.cs @@ -27,6 +27,6 @@ namespace Upsilon.Binder yield break; } - public override Type Type => VariableSymbol.Type; + public override TypeContainer Type => VariableSymbol.TypeContainer; } } \ No newline at end of file diff --git a/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs index 13953c7..9955092 100644 --- a/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs @@ -8,18 +8,12 @@ namespace Upsilon.Binder.VariableSymbols public Type ResultType { get; internal set; } public FunctionVariableSymbol(string name, bool local, Type resultType) - : base(name, Type.Function, local) + : base(name, BaseTypes.Type.Function, local) { ResultType = resultType; } public abstract (bool IsValid, string Error, BoundExpression WrongParameter) ValidateParameters( ImmutableArray callingParameters); - - public override Type Type - { - get => Type.Function; - set{} - } } } \ No newline at end of file diff --git a/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs index 177219a..78f56e8 100644 --- a/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs @@ -1,6 +1,7 @@ -using System; using System.Collections.Immutable; using System.Linq; +using System.Reflection; +using Upsilon.BaseTypes; using Upsilon.BoundTypes; using Type = Upsilon.BaseTypes.Type; @@ -8,37 +9,17 @@ namespace Upsilon.Binder.VariableSymbols { public class InternalFunctionVariableSymbol : FunctionVariableSymbol { - public class InternalFunctionParameter - { - public InternalFunctionParameter(string name, Type type, bool isOptional) - { - ValidTypes = type; - IsOptional = isOptional; - Name = name; - } - - public InternalFunctionParameter(string name, Type type, string[] expectedUserData, bool isOptional) - { - ValidTypes = type; - ExpectedUserData = expectedUserData; - IsOptional = isOptional; - Name = name; - } - - public string Name { get; } - public Type ValidTypes { get; } - public string[] ExpectedUserData { get; } - public bool IsOptional { get; } - } - - public InternalFunctionParameter[] FunctionParameters { get; } + private InternalFunctionParameter[] FunctionParameters { get; } private int MinimalParametersRequired { get; } + private MethodInfo OverrideResultType { get; } - public InternalFunctionVariableSymbol(string name, bool local, Type resultType, InternalFunctionParameter[] functionParameters) + public InternalFunctionVariableSymbol(string name, bool local, Type resultType, + InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType) : base(name, local, resultType) { FunctionParameters = functionParameters; MinimalParametersRequired = functionParameters.Count(x => !x.IsOptional); + OverrideResultType = overrideResultType; } public override (bool IsValid, string Error, @@ -70,7 +51,7 @@ namespace Upsilon.Binder.VariableSymbols if (functionParameter.ValidTypes.HasFlag(Type.UserData)) { var variable = Binder.ResolveVariable(callingParameter, null); - if (variable != null && variable.Type == Type.UserData) + if (variable != null && variable.TypeContainer == Type.UserData) { var parent = (UserDataBoundTypeDefinition) ((UserDataVariableSymbol) variable).BoundTypeDefinition; @@ -87,5 +68,35 @@ namespace Upsilon.Binder.VariableSymbols return (true, null, null); } + + public TypeContainer GetResultType(BoundExpression[] parameters) + { + if (OverrideResultType == null) + return ResultType; + return (TypeContainer) OverrideResultType.Invoke(null, new object[] {parameters}); + } + + public class InternalFunctionParameter + { + public InternalFunctionParameter(string name, TypeContainer type, bool isOptional) + { + ValidTypes = type; + IsOptional = isOptional; + Name = name; + } + + public InternalFunctionParameter(string name, TypeContainer type, string[] expectedUserData, bool isOptional) + { + ValidTypes = type; + ExpectedUserData = expectedUserData; + IsOptional = isOptional; + Name = name; + } + + public string Name { get; } + public Type ValidTypes { get; } + public string[] ExpectedUserData { get; } + public bool IsOptional { get; } + } } } \ No newline at end of file diff --git a/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs index 671780a..5d77abe 100644 --- a/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs @@ -28,13 +28,13 @@ namespace Upsilon.Binder.VariableSymbols { var functionParameter = Parameters[i]; var callingParameter = callingParameters[i]; - if (functionParameter.Type != Type.Unknown && - callingParameter.Type != Type.Unknown && callingParameter.Type != Type.Nil) + if (functionParameter.TypeContainer != BaseTypes.Type.Unknown && + callingParameter.Type != BaseTypes.Type.Unknown && callingParameter.Type != BaseTypes.Type.Nil) { - if (callingParameter.Type != functionParameter.Type) + if (callingParameter.Type != functionParameter.TypeContainer) { return (false, $"Invalid type for function '{Name}' at parameter '{functionParameter.Name}'. " + - $"Expected type '{functionParameter.Type}', got '{callingParameter.Type}'", + $"Expected type '{functionParameter.TypeContainer}', got '{callingParameter.Type}'", callingParameter); } } diff --git a/Upsilon/Binder/VariableSymbols/TableVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/TableVariableSymbol.cs index ae1f7f0..13fc16d 100644 --- a/Upsilon/Binder/VariableSymbols/TableVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/TableVariableSymbol.cs @@ -7,15 +7,16 @@ namespace Upsilon.Binder.VariableSymbols { public Dictionary Variables { get; } public bool ContentAware { get; } - public TableVariableSymbol(string name, bool local, Dictionary variables) - :base (name, Type.Table, local) + public TableVariableSymbol(string name, bool local, Dictionary variables, + TypeContainer type) + :base (name, type, local) { Variables = variables; ContentAware = true; } - public TableVariableSymbol(string name, bool local) - :base (name, Type.Table, local) + public TableVariableSymbol(string name, bool local, TypeContainer type) + :base (name, type, local) { Variables = new Dictionary(); ContentAware = false; diff --git a/Upsilon/Binder/VariableSymbols/UserDataVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/UserDataVariableSymbol.cs index ad71603..7103443 100644 --- a/Upsilon/Binder/VariableSymbols/UserDataVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/UserDataVariableSymbol.cs @@ -8,9 +8,9 @@ namespace Upsilon.Binder.VariableSymbols public BoundTypeDefinition BoundTypeDefinition { get; } public UserDataBoundTypeDefinition Parent { get; } - public UserDataVariableSymbol(string name, Type type) : base(name, type, true) + public UserDataVariableSymbol(string name, Type typeContainer) : base(name, typeContainer, true) { - _type = type; + _typeContainer = typeContainer; } public UserDataVariableSymbol(string name, BoundTypeDefinition type, UserDataBoundTypeDefinition parent = null) @@ -20,11 +20,11 @@ namespace Upsilon.Binder.VariableSymbols Parent = parent; } - private Type _type; - public override Type Type + private TypeContainer _typeContainer; + public override TypeContainer TypeContainer { - get => BoundTypeDefinition?.ScriptType ?? _type; - set => _type = Type; + get => BoundTypeDefinition?.ScriptType ?? _typeContainer; + set => _typeContainer = value; } } } \ No newline at end of file diff --git a/Upsilon/Binder/VariableSymbols/VariableSymbol.cs b/Upsilon/Binder/VariableSymbols/VariableSymbol.cs index 6cb86b0..335c88f 100644 --- a/Upsilon/Binder/VariableSymbols/VariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/VariableSymbol.cs @@ -4,14 +4,14 @@ namespace Upsilon.Binder.VariableSymbols { public class VariableSymbol { - public VariableSymbol(string name, Type type, bool local) + public VariableSymbol(string name, TypeContainer typeContainer, bool local) { - Type = type; + TypeContainer = typeContainer; Local = local; Name = name; } - public virtual Type Type { get; set; } = Type.Unknown; + public virtual TypeContainer TypeContainer { get; set; } public bool Local { get; } public string Name { get; } public string[] CommentValue { get; set; } diff --git a/Upsilon/BoundTypes/UserDataBoundTypeDefinition.cs b/Upsilon/BoundTypes/UserDataBoundTypeDefinition.cs index c4e91a0..9fd8458 100644 --- a/Upsilon/BoundTypes/UserDataBoundTypeDefinition.cs +++ b/Upsilon/BoundTypes/UserDataBoundTypeDefinition.cs @@ -191,7 +191,7 @@ namespace Upsilon.BoundTypes if (functionParameter.Type.HasFlag(Type.UserData)) { var variable = Binder.Binder.ResolveVariable(callingParameter, null); - if (variable != null && variable.Type == Type.UserData) + if (variable != null && variable.TypeContainer == Type.UserData) { var parent = (UserDataBoundTypeDefinition) ((UserDataVariableSymbol) variable).BoundTypeDefinition; diff --git a/Upsilon/StandardLibraries/BasicFunctions.cs b/Upsilon/StandardLibraries/BasicFunctions.cs index 3eff75a..a5c662f 100644 --- a/Upsilon/StandardLibraries/BasicFunctions.cs +++ b/Upsilon/StandardLibraries/BasicFunctions.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; using Upsilon.BaseTypes; using Upsilon.BaseTypes.Number; using Upsilon.BaseTypes.ScriptTypeInterfaces; using Upsilon.Binder; +using Upsilon.Binder.VariableSymbols; using Upsilon.Evaluator; // ReSharper disable UnusedMember.Global @@ -39,19 +39,27 @@ namespace Upsilon.StandardLibraries } [ScriptFunction("ipairs", "Iterates over an iterable variable, like a table, until it encounters a nil value.", - directScriptManipulation: true)] - public IIterable UpTillNullPairs(IIterable table) + directScriptManipulation: true, overrideReturnType: typeof(BasicFunctions), overrideReturnMethod: nameof(PairsBindHandler))] + public UpTillNullPairsScriptIterator UpTillNullPairs(IIterable table) { return new UpTillNullPairsScriptIterator(table); } [ScriptFunction("pairs", "Iterates over an iterable variable, like a table, skipping all nil values.", - directScriptManipulation: true)] - public IIterable Pairs(IIterable table) + directScriptManipulation: true, overrideReturnType: typeof(BasicFunctions), overrideReturnMethod: nameof(PairsBindHandler))] + public PairsScriptIterator Pairs(IIterable table) { return new PairsScriptIterator(table); } + private static TypeContainer PairsBindHandler(BoundExpression[] variableSymbols) + { + if (variableSymbols.Length != 1) + return BaseTypes.Type.Unknown; + var parameter = variableSymbols[0]; + return parameter.Type; + } + [ScriptFunction("print", "Prints a message to the action given in the script options", passScriptReference: true, directScriptManipulation: true)] public void Print(Script script, ScriptType message) diff --git a/Upsilon/StandardLibraries/ScriptFunctionAttribute.cs b/Upsilon/StandardLibraries/ScriptFunctionAttribute.cs index 2b5fe70..4de231c 100644 --- a/Upsilon/StandardLibraries/ScriptFunctionAttribute.cs +++ b/Upsilon/StandardLibraries/ScriptFunctionAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Upsilon.StandardLibraries { @@ -6,13 +7,20 @@ namespace Upsilon.StandardLibraries public class ScriptFunctionAttribute : Attribute { public ScriptFunctionAttribute(string name, string comment = null, - bool directScriptManipulation = false, bool passScriptReference = false, bool passScopeReference = false) + bool directScriptManipulation = false, bool passScriptReference = false, bool passScopeReference = false, + Type overrideReturnType = null, string overrideReturnMethod = null) { Name = name; Comment = comment; DirectScriptManipulation = directScriptManipulation; PassScriptReference = passScriptReference; PassScopeReference = passScopeReference; + if (overrideReturnType != null && overrideReturnMethod != null) + { + var method = overrideReturnType.GetMethod(overrideReturnMethod, + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + OverrideReturnType = method; + } } public string Name { get; } @@ -20,5 +28,6 @@ namespace Upsilon.StandardLibraries public bool DirectScriptManipulation { get; } public bool PassScriptReference { get; } public bool PassScopeReference { get; } + public MethodInfo OverrideReturnType { get; } } } \ No newline at end of file diff --git a/Upsilon/StandardLibraries/StaticScope.cs b/Upsilon/StandardLibraries/StaticScope.cs index e55e56c..763596f 100644 --- a/Upsilon/StandardLibraries/StaticScope.cs +++ b/Upsilon/StandardLibraries/StaticScope.cs @@ -65,7 +65,7 @@ namespace Upsilon.StandardLibraries var derivedType = DeriveValidTypes(typeInfo.Type); return new InternalFunctionVariableSymbol.InternalFunctionParameter(func.Key, derivedType, typeInfo.IsOptional); - }).ToArray()) + }).ToArray(), func.Value.MethodInfoFunction.Method.GetMethods()[0].Attribute.OverrideReturnType) { CommentValue = func.Value.CommentValue?.Split('\n') }; @@ -128,7 +128,7 @@ namespace Upsilon.StandardLibraries } var result = genericParameters[genericParameters.Length - 1].GetScriptType(); - return new InternalFunctionVariableSymbol(name, true, result, parameters.ToArray()); + return new InternalFunctionVariableSymbol(name, true, result, parameters.ToArray(), null); } private static VariableSymbol BuildActionVariableSymbol(string name, System.Type type) @@ -136,10 +136,10 @@ namespace Upsilon.StandardLibraries var genericParameters = type.GetGenericArguments(); return new InternalFunctionVariableSymbol(name, true, Type.Nil, genericParameters.Select(DeriveValidTypes).Select(t => - new InternalFunctionVariableSymbol.InternalFunctionParameter(name, t, false)).ToArray()); + new InternalFunctionVariableSymbol.InternalFunctionParameter(name, t, false)).ToArray(), null); } - public static Type DeriveValidTypes(System.Type type) + public static TypeContainer DeriveValidTypes(System.Type type) { if (type.IsEnum) return Type.UserData | Type.Number; diff --git a/Upsilon/Text/TextSpan.cs b/Upsilon/Text/TextSpan.cs index 744af4a..02d16ab 100644 --- a/Upsilon/Text/TextSpan.cs +++ b/Upsilon/Text/TextSpan.cs @@ -29,9 +29,7 @@ namespace Upsilon.Text { if (StartLine == EndLine && linePosition == StartLine) { - if (characterPosition >= StartPosition && characterPosition <= EndPosition) - return true; - return false; + return characterPosition >= StartPosition && characterPosition <= EndPosition; } if (StartLine == linePosition && StartPosition <= characterPosition) return true; if (StartLine < linePosition && EndLine > linePosition) return true; diff --git a/UpsilonTests/GeneralTests/ForLoopTests.cs b/UpsilonTests/GeneralTests/ForLoopTests.cs index 2521438..a4a24ef 100644 --- a/UpsilonTests/GeneralTests/ForLoopTests.cs +++ b/UpsilonTests/GeneralTests/ForLoopTests.cs @@ -1,4 +1,5 @@ using Upsilon; +using Upsilon.BaseTypes; using Upsilon.Evaluator; using Xunit; @@ -98,5 +99,21 @@ return value var actual = Executor.EvaluateScript(input, Options); Assert.Equal(6, actual); } + + [Fact] + public void GenericForLoopValueTypeBindTest() + { + const string input = @" +arr = {100, 56, 28} +for key, val in ipairs(arr) do + a = val + break +end +"; + var script = Executor.ParseInputAndEvaluate(input, Options); + Assert.True(script.Bind().Scope.TryGetVariable("a", true, out var variable)); + Assert.Equal(Type.Number, variable.TypeContainer.Type); + } + } } \ No newline at end of file