From 3c0e5f5b132e00dfee792d758e1b104b01a55bca Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 20 Jan 2019 15:01:18 +0100 Subject: [PATCH] Allow parameter overloading script functions --- .../ScriptFunction/ScriptRuntimeFunction.cs | 112 ++++++++++++--- Upsilon/Binder/Binder.cs | 88 +++++++----- .../UnboundFunctionExpression.cs | 4 +- .../VariableSymbols/FunctionVariableSymbol.cs | 34 ++++- .../InternalFunctionVariableSymbol.cs | 127 +++++++++++------- .../ScriptFunctionVariableSymbol.cs | 60 ++++++--- Upsilon/Evaluator/Evaluator.cs | 85 +++++------- UpsilonTests/GeneralTests/FunctionTests.cs | 18 +++ 8 files changed, 354 insertions(+), 174 deletions(-) diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs index 3dd3263..ad32e54 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs @@ -1,35 +1,115 @@ +using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Upsilon.BaseTypes.ScriptTypeInterfaces; using Upsilon.Binder; +using Upsilon.Binder.VariableSymbols; using Upsilon.Evaluator; using Upsilon.Text; namespace Upsilon.BaseTypes.ScriptFunction { - internal class ScriptRuntimeFunction : ScriptFunction, IScopeOwner + internal class ScriptRuntimeFunction : ScriptFunction { - public BoundBlockStatement Block { get; } - public ImmutableArray Parameters { get; } - public EvaluationScope EvaluationScope { get; } + public List Options { get; } - public ScriptRuntimeFunction(ImmutableArray parameters, BoundBlockStatement block, - EvaluationScope evaluationScope) + public ScriptRuntimeFunction(List options) { - Parameters = parameters; - Block = block; - EvaluationScope = evaluationScope; + Options = options; } - public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) + public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, + EvaluationScope scope, TextSpan span) { - var innerEvaluator = new Evaluator.Evaluator(diagnostics, EvaluationScope, script); - for (var i = 0; i < Parameters.Length; i++) + var option = GetValidOption(variables); + if (option == null) { - var parameterVariable = Parameters[i]; - var parameterValue = variables[i]; - innerEvaluator.Scope.CreateLocal(parameterVariable.VariableSymbol, parameterValue); + throw new EvaluationException( + $"No valid function found"); + } + return option.Run(diagnostics, variables, script, scope, span); + } + + public ScriptRuntimeFunctionOption GetValidOption(object[] variables) + { + foreach (var option in Options) + { + if (option.Parameters.Length != variables.Length) + continue; + bool isCompatible = true; + for (var index = 0; index < variables.Length; index++) + { + var parameter = option.Parameters[index]; + var parameterSymbol = ((UserDataVariableSymbol)parameter.VariableSymbol); + var parameterType = variables[index].GetType(); + var validSymbol = + parameterSymbol.BoundTypeDefinition.ValidInternalTypes.Any(validType => + validType.IsAssignableFrom(parameterType)); + if (!validSymbol) + { + isCompatible = false; + break; + } + } + if (!isCompatible) + continue; + return option; + } + return null; + } + + public ScriptRuntimeFunctionOption GetValidOption(ScriptType[] variables) + { + foreach (var option in Options) + { + if (option.Parameters.Length != variables.Length) + continue; + bool isCompatible = true; + for (var index = 0; index < variables.Length; index++) + { + var callingVariable = variables[index]; + var optionVariable = option.Parameters[index]; + if (callingVariable.Type != optionVariable.Type) + { + isCompatible = false; + break; + } + } + if (!isCompatible) + continue; + return option; + } + return null; + } + + + public class ScriptRuntimeFunctionOption : IScopeOwner + { + public ScriptRuntimeFunctionOption(ImmutableArray parameters, + BoundBlockStatement block, EvaluationScope scope) + { + Parameters = parameters; + Block = block; + EvaluationScope = scope; + } + + public EvaluationScope EvaluationScope { get; } + + public BoundBlockStatement Block { get; } + public ImmutableArray Parameters { get; } + + public ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) + { + var innerEvaluator = new Evaluator.Evaluator(diagnostics, EvaluationScope, script); + for (var i = 0; i < Parameters.Length; i++) + { + var parameterVariable = Parameters[i]; + var parameterValue = variables[i]; + innerEvaluator.Scope.CreateLocal(parameterVariable.VariableSymbol, parameterValue); + } + return innerEvaluator.EvaluateNode(Block); } - return innerEvaluator.EvaluateNode(Block); } } } \ No newline at end of file diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index fad8d2c..7c3f8e7 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -19,8 +19,8 @@ namespace Upsilon.Binder public BoundScope Scope { get; private set; } private readonly Script _script; - private Dictionary _unboundFunctions = - new Dictionary(); + private List _unboundFunctions = + new List(); public Binder(Diagnostics diagnostics, Dictionary variables, Script script) { @@ -52,23 +52,22 @@ namespace Upsilon.Binder foreach (var unboundFunctionStatement in _unboundFunctions) { Scope = new BoundScope(Scope); - foreach (var valueParameter in unboundFunctionStatement.Value.Parameters) + foreach (var valueParameter in unboundFunctionStatement.Parameters) { Scope.AssignToNearest(valueParameter.VariableSymbol); if (valueParameter.VariableSymbol.TypeContainer == Type.Unknown) _diagnostics.LogUnknownVariableType(valueParameter.VariableSymbol.Name, valueParameter.Span); } - unboundFunctionStatement.Value.Block = - (BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.Value.UnboundBlock); + unboundFunctionStatement.Block = + (BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.UnboundBlock); var resultType = Scope.ReturnType; Scope = Scope.ParentScope; - var variable = - (ScriptFunctionVariableSymbol) unboundFunctionStatement.Value.Scope.ParentScope.Variables[ - unboundFunctionStatement.Key]; - variable.IsBound = true; - variable.ResultType = resultType; + //var variable = + // (ScriptFunctionVariableSymbol) unboundFunctionStatement.Scope.ParentScope.Variables[ + // unboundFunctionStatement]; + //variable.IsBound = true; } - _unboundFunctions = new Dictionary(); + _unboundFunctions.Clear(); return new BoundScript((BoundBlockStatement) bound, e.Span, Scope, fileName, _script); } @@ -231,25 +230,41 @@ namespace Upsilon.Binder { if (function is ScriptFunctionVariableSymbol scriptFunction) { - if (!scriptFunction.IsBound) + if (!(scriptFunction.GetFirstValid(parameters.Select(x => x.Type).ToArray()) is ScriptFunctionVariableOption functionOption)) + throw new Exception(); + + if (!functionOption.IsBound) { Scope = new BoundScope(Scope); - for (var index = 0; index < scriptFunction.Parameters.Length; index++) + for (var index = 0; index < functionOption.Parameters.Length; index++) { - var functionVariable = scriptFunction.Parameters[index]; + var functionVariable = functionOption.Parameters[index]; var callingVariable = parameters[index]; functionVariable.TypeContainer = callingVariable.Type; Scope.DefineLocalVariable(functionVariable); } - var unboundFunctionStatement = _unboundFunctions[scriptFunction.Name]; - unboundFunctionStatement.Block = - (BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.UnboundBlock); - returnType = Scope.ReturnType; - Scope = Scope.ParentScope; - scriptFunction.IsBound = true; - scriptFunction.ResultType = returnType; - _unboundFunctions.Remove(scriptFunction.Name); + UnboundFunctionExpression unbound = null; + foreach (var functionExpression in this._unboundFunctions.Where(x => + { + if (x.Name == function.Name) + { + return parameters.Count == functionOption.Parameters.Length; + } + + return false; + })) + { + unbound = functionExpression; + break; + } + unbound.Block = (BoundBlockStatement) BindBlockStatement(unbound.UnboundBlock); + + returnType = Scope.ReturnType; + Scope = Scope.ParentScope; + functionOption.IsBound = true; + functionOption.ResultType = returnType; + } } else @@ -287,7 +302,7 @@ namespace Upsilon.Binder } else { - returnType = function.ResultType; + //returnType = function.ResultType; } } @@ -636,15 +651,8 @@ namespace Upsilon.Binder } else { - var unbound = new UnboundFunctionExpression(parameters.ToImmutable(), e.Block, e.Span, innerScope); - if (functionVariableSymbol == null) - { - _unboundFunctions.Add( Guid.NewGuid().ToString(), unbound); - } - else - { - _unboundFunctions.Add(functionVariableSymbol, unbound); - } + var unbound = new UnboundFunctionExpression(parameters.ToImmutable(), e.Block, e.Span, innerScope, functionVariableSymbol); + _unboundFunctions.Add(unbound); return unbound; } } @@ -677,8 +685,8 @@ namespace Upsilon.Binder CommentValue = commentData.ToArray() }; variable = functionVariable; - functionVariable.IsBound = !(func is UnboundFunctionExpression); - functionVariable.ResultType = func.ReturnType; + ((ScriptFunctionVariableOption)functionVariable.FunctionOption[0]).IsBound = !(func is UnboundFunctionExpression); + functionVariable.FunctionOption[0].ResultType = func.ReturnType; if (isLocal) Scope.DefineLocalVariable(variable); else @@ -699,6 +707,18 @@ namespace Upsilon.Binder 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); + } + } } diff --git a/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs b/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs index ecf2927..ab9ba58 100644 --- a/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs +++ b/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs @@ -9,12 +9,14 @@ namespace Upsilon.Binder public class UnboundFunctionExpression : BoundFunctionExpression { public UnboundFunctionExpression(ImmutableArray parameters, - BlockStatementSyntax unboundBlock, TextSpan span, BoundScope scope) + BlockStatementSyntax unboundBlock, TextSpan span, BoundScope scope, string name) : base(parameters, null, span, scope, BaseTypes.Type.Unknown) { UnboundBlock = unboundBlock; + Name = name; } + public string Name { get; } public override BoundKind Kind => BoundKind.BoundPromise; public BlockStatementSyntax UnboundBlock { get; } diff --git a/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs index 9955092..8f46d7f 100644 --- a/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs @@ -1,19 +1,49 @@ +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Upsilon.BaseTypes; namespace Upsilon.Binder.VariableSymbols { public abstract class FunctionVariableSymbol : VariableSymbol { - public Type ResultType { get; internal set; } + public List FunctionOption { get; } = new List(); public FunctionVariableSymbol(string name, bool local, Type resultType) : base(name, BaseTypes.Type.Function, local) { - ResultType = resultType; } public abstract (bool IsValid, string Error, BoundExpression WrongParameter) ValidateParameters( ImmutableArray callingParameters); + + public FunctionVariableSymbolOption GetFirstValid(TypeContainer[] types) + { + return FunctionOption.FirstOrDefault(x => + { + var parTypes = x.GetParameterTypes(); + for (var i = 0; i < parTypes.Length; i++) + { + var parType = parTypes[i]; + var givenType = types[i]; + if (parType == Type.Unknown || givenType == Type.Unknown) + continue; + if (!parType.Type.HasFlag(givenType)) + return false; + } + return true; + }); + } + } + + public abstract class FunctionVariableSymbolOption + { + public FunctionVariableSymbolOption(TypeContainer resultType) + { + ResultType = resultType; + } + + public abstract TypeContainer[] GetParameterTypes(); + public TypeContainer ResultType { get; internal set; } } } \ No newline at end of file diff --git a/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs index 2b8efcf..65b70d4 100644 --- a/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs @@ -9,72 +9,90 @@ namespace Upsilon.Binder.VariableSymbols { public class InternalFunctionVariableSymbol : FunctionVariableSymbol { - // ReSharper disable once MemberCanBePrivate.Global - public InternalFunctionParameter[] FunctionParameters { get; } - private int MinimalParametersRequired { get; } - private MethodInfo OverrideResultType { get; } - - public InternalFunctionVariableSymbol(string name, bool local, Type resultType, + public InternalFunctionVariableSymbol(string name, bool local, TypeContainer resultType, InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType) : base(name, local, resultType) { - FunctionParameters = functionParameters; - MinimalParametersRequired = functionParameters.Count(x => !x.IsOptional); - OverrideResultType = overrideResultType; + FunctionOption.Add(new InternalFunctionVariableOption(resultType, functionParameters, overrideResultType)); } public override (bool IsValid, string Error, BoundExpression WrongParameter) ValidateParameters(ImmutableArray callingParameters) { - if (callingParameters.Length < MinimalParametersRequired - || callingParameters.Length > FunctionParameters.Length) + foreach (var functionVariableSymbolOption in FunctionOption) { - return (false, - $"Invalid number of parameters for function '{Name}'. Expected {FunctionParameters.Length}, got {callingParameters.Length}", - null); - } - - for (var i = 0; i < callingParameters.Length; i++) - { - var functionParameter = FunctionParameters[i]; - var callingParameter = callingParameters[i]; - if (callingParameter.Type == Type.Unknown || callingParameter.Type == Type.Nil) + if (!(functionVariableSymbolOption is InternalFunctionVariableOption option)) continue; - - if (!functionParameter.ValidTypes.HasFlag(callingParameter.Type)) + if (option.FunctionParameters.Length != callingParameters.Length) + continue; + var isValid = true; + for (var i = 0; i < callingParameters.Length; i++) { - return (false, - $"Unexpected variable passed to internal function at variable {i + 1}. " + - $"Expected one of the following: {functionParameter.ValidTypes.ToString()}, got: '{callingParameter.Type}'", - callingParameter); - } - - if (functionParameter.ValidTypes.HasFlag(Type.UserData)) - { - var variable = Binder.ResolveVariable(callingParameter, null); - if (variable != null && variable.TypeContainer == Type.UserData) + var functionParameter = option.FunctionParameters[i]; + var callingParameter = callingParameters[i]; + if (callingParameter.Type == Type.Unknown || callingParameter.Type == Type.Nil) { - var parent = - (UserDataBoundTypeDefinition) ((UserDataVariableSymbol) variable).BoundTypeDefinition; - if (functionParameter.ExpectedUserData != null && !functionParameter.ExpectedUserData.Contains(parent.Name)) + isValid = false; + break; + } + + if (!functionParameter.ValidTypes.Type.HasFlag(callingParameter.Type)) + { + isValid = false; + break; + } + + if (functionParameter.ValidTypes.Type.HasFlag(Type.UserData)) + { + var variable = Binder.ResolveVariable(callingParameter, null); + if (variable != null && variable.TypeContainer == Type.UserData) { - return (false, - $"Unexpected variable passed to internal function at variable {i + 1}. " + - $"Expected to be the following: {string.Join(", ", functionParameter.ExpectedUserData)}, got: '{parent.Name}'", - callingParameter); + var parent = + (UserDataBoundTypeDefinition) ((UserDataVariableSymbol) variable).BoundTypeDefinition; + if (functionParameter.ExpectedUserData != null && !functionParameter.ExpectedUserData.Contains(parent.Name)) + { + isValid = false; + break; + } } } } + if (!isValid) + continue; + return (true, null, null); } - return (true, null, null); + return (false, + $"No valid function with name '{Name}' and variables of type {string.Join(", ", callingParameters.Select(x => $"'{x.Type}'"))} found", + null); } public TypeContainer GetResultType(BoundExpression[] parameters) { - if (OverrideResultType == null) - return ResultType; - return (TypeContainer) OverrideResultType.Invoke(null, new object[] {parameters}); + foreach (var functionVariableSymbolOption in FunctionOption) + { + if (!(functionVariableSymbolOption is InternalFunctionVariableOption option)) + continue; + if (option.FunctionParameters.Length != parameters.Length) + continue; + var isValid = true; + for (var i = 0; i < parameters.Length; i++) + { + var functionParameter = option.FunctionParameters[i]; + var callingParameter = parameters[i]; + if (!functionParameter.ValidTypes.Type.HasFlag(callingParameter.Type)) + { + isValid = false; + break; + } + } + if (!isValid) + continue; + if (option.OverrideResultType == null) + return option.ResultType; + return (TypeContainer) option.OverrideResultType.Invoke(null, new object[] {parameters}); + } + return Type.Unknown; } public class InternalFunctionParameter @@ -95,9 +113,28 @@ namespace Upsilon.Binder.VariableSymbols } public string Name { get; } - public Type ValidTypes { get; } + public TypeContainer ValidTypes { get; } public string[] ExpectedUserData { get; } public bool IsOptional { get; } } } + + public class InternalFunctionVariableOption : FunctionVariableSymbolOption + { + public InternalFunctionVariableSymbol.InternalFunctionParameter[] FunctionParameters { get; } + public MethodInfo OverrideResultType { get; } + + public InternalFunctionVariableOption(TypeContainer resultType, + InternalFunctionVariableSymbol.InternalFunctionParameter[] functionParameters, + MethodInfo overrideResultType) : base(resultType) + { + FunctionParameters = functionParameters; + OverrideResultType = overrideResultType; + } + + public override TypeContainer[] GetParameterTypes() + { + return FunctionParameters.Select(x => x.ValidTypes).ToArray(); + } + } } \ No newline at end of file diff --git a/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs index 5d77abe..c00a846 100644 --- a/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs @@ -1,47 +1,63 @@ using System.Collections.Immutable; +using System.Linq; using Upsilon.BaseTypes; namespace Upsilon.Binder.VariableSymbols { public class ScriptFunctionVariableSymbol : FunctionVariableSymbol { - public ImmutableArray Parameters { get; } - public bool IsBound { get; set; } - - public ScriptFunctionVariableSymbol(string name, bool local, ImmutableArray parameters, Type resultType) : base(name, local, resultType) { - Parameters = parameters; + FunctionOption.Add(new ScriptFunctionVariableOption(resultType, parameters)); } public override (bool IsValid, string Error, BoundExpression WrongParameter) ValidateParameters(ImmutableArray callingParameters) { - if (Parameters.Length != callingParameters.Length) + foreach (var functionVariableSymbolOption in FunctionOption) { - return (false, - $"Invalid number of parameters for function '{Name}'. Expected {Parameters.Length}, got {callingParameters.Length}", - null); - } - - for (var i = 0; i < Parameters.Length; i++) - { - var functionParameter = Parameters[i]; - var callingParameter = callingParameters[i]; - if (functionParameter.TypeContainer != BaseTypes.Type.Unknown && - callingParameter.Type != BaseTypes.Type.Unknown && callingParameter.Type != BaseTypes.Type.Nil) + if (!(functionVariableSymbolOption is ScriptFunctionVariableOption option)) + continue; + if (option.Parameters.Length != callingParameters.Length) + continue; + bool isValid = true; + for (var i = 0; i < option.Parameters.Length; i++) { - if (callingParameter.Type != functionParameter.TypeContainer) + var functionParameter = option.Parameters[i]; + var callingParameter = callingParameters[i]; + if (functionParameter.TypeContainer != BaseTypes.Type.Unknown && + callingParameter.Type != BaseTypes.Type.Unknown && + callingParameter.Type != BaseTypes.Type.Nil) { - return (false, $"Invalid type for function '{Name}' at parameter '{functionParameter.Name}'. " + - $"Expected type '{functionParameter.TypeContainer}', got '{callingParameter.Type}'", - callingParameter); + if (callingParameter.Type != functionParameter.TypeContainer) + { + isValid = false; + break; + } } } + if (!isValid) + continue; + return (true, null, null); } - return (true, null, null); + return (false, null, null); + } + } + + public class ScriptFunctionVariableOption : FunctionVariableSymbolOption + { + public ImmutableArray Parameters { get; } + public bool IsBound { get; set; } + + public ScriptFunctionVariableOption(Type resultType, ImmutableArray parameters) : base(resultType) + { + Parameters = parameters; } + public override TypeContainer[] GetParameterTypes() + { + return Parameters.Select(x => x.TypeContainer).ToArray(); + } } } \ No newline at end of file diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 6b8eb54..f291707 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using Upsilon.BaseTypes; using Upsilon.BaseTypes.Number; @@ -86,55 +87,11 @@ namespace Upsilon.Evaluator } var function = (ScriptRuntimeFunction) statement; var innerEvaluator = new Evaluator(_diagnostics, Scope, _script); - if (parameters != null) - { - for (var index = 0; index < parameters.Length; index++) - { - object parameter; - if (index < parameters.Length) - { - parameter = parameters[index]; - } - else - { - parameter = null; - } - - UserDataVariableSymbol parameterSymbol; - if (index < function.Parameters.Length) - { - parameterSymbol = (UserDataVariableSymbol)function.Parameters[index].VariableSymbol; - } - else - { - continue; - } - if (parameterSymbol.BoundTypeDefinition != null && parameter != null) - { - bool isCompatible = false; - var parameterType = parameter.GetType(); - foreach (var validType in parameterSymbol.BoundTypeDefinition.ValidInternalTypes) - { - if (validType.IsAssignableFrom(parameterType)) - { - isCompatible = true; - break; - } - } - - if (!isCompatible) - { - throw new EvaluationException( - $"Parameter '{parameterSymbol.Name}' of function '{functionName}' can't handle the given object with type '{parameterType}'"); - } - } - - var parameterConverted = parameter == null ? new ScriptNull() : parameter.ToScriptType(); - innerEvaluator.Scope.CreateLocal(parameterSymbol, parameterConverted); - } - } - - var result = innerEvaluator.EvaluateNode(function.Block); + var option = function.GetValidOption(parameters); + if (option == null) + throw new EvaluationException( + $"No function found with name '{functionName}' and available parameters {string.Join(", ", parameters.Select(x => $"{x.GetType().Name}"))}"); + var result = option.Run(_diagnostics, parameters.Select(x => x.ToScriptType()).ToArray(), _script, Scope, new TextSpan()); return result; } @@ -539,22 +496,42 @@ namespace Upsilon.Evaluator private void EvaluateBoundFunctionAssigmentStatement(BoundFunctionAssignmentStatement e) { - var func = EvaluateBoundFunctionStatement(e.Func); + var func = (ScriptRuntimeFunction)EvaluateBoundFunctionStatement(e.Func); if (e.Variable.Local) - Scope.CreateLocal(e.Variable, func); + { + if (Scope.Variables.TryGetValue(e.Variable.Name, out var f) && f is ScriptRuntimeFunction scriptRuntimeFunction) + { + scriptRuntimeFunction.Options.AddRange(func.Options); + } + else + { + Scope.CreateLocal(e.Variable, func); + } + } else - Scope.AssignToNearest(e.Variable, func); + { + if (Scope.TryGet(e.Variable, out var f) && f is ScriptRuntimeFunction scriptRuntimeFunction) + { + scriptRuntimeFunction.Options.AddRange(func.Options); + } + else + { + Scope.AssignToNearest(e.Variable, func); + } + } } private ScriptType EvaluateBoundFunctionStatement(BoundFunctionExpression boundFunctionExpression) { - var func = new ScriptRuntimeFunction(boundFunctionExpression.Parameters, boundFunctionExpression.Block, Scope); + var option = new ScriptRuntimeFunction.ScriptRuntimeFunctionOption(boundFunctionExpression.Parameters, boundFunctionExpression.Block, Scope); + var func = new ScriptRuntimeFunction(new List(){option}); return func; } private ScriptType EvaluateUnboundFunctionStatement(UnboundFunctionExpression unboundFunctionExpression) { - var func = new ScriptRuntimeFunction(unboundFunctionExpression.Parameters, unboundFunctionExpression.Block, Scope); + var option = new ScriptRuntimeFunction.ScriptRuntimeFunctionOption(unboundFunctionExpression.Parameters, unboundFunctionExpression.Block, Scope); + var func = new ScriptRuntimeFunction(new List(){option}); return func; } diff --git a/UpsilonTests/GeneralTests/FunctionTests.cs b/UpsilonTests/GeneralTests/FunctionTests.cs index 20f07ea..89cb9b1 100644 --- a/UpsilonTests/GeneralTests/FunctionTests.cs +++ b/UpsilonTests/GeneralTests/FunctionTests.cs @@ -182,5 +182,23 @@ return value var result = Executor.EvaluateScript(input, Options); Assert.Equal(6, result); } + + [Fact] + public void HandleMultipleFunctionOptions() + { + const string input = @" +function a(number v) + return v + 10 +end +function a(string s) + return s == ""test"" +end + +return a(50) == 60 and a(""test"") +"; + Assert.Equal(60, Executor.EvaluateFunction(input, "a", new object[] {50})); + Assert.True(Executor.EvaluateFunction(input, "a", new object[] {"test"})); + Assert.True(Executor.EvaluateScript(input, Options)); + } } } \ No newline at end of file