Allow parameter overloading script functions

This commit is contained in:
Deukhoofd 2019-01-20 15:01:18 +01:00
parent 43da2b3d19
commit 3c0e5f5b13
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
8 changed files with 354 additions and 174 deletions

View File

@ -1,26 +1,105 @@
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<BoundVariableSymbol> Parameters { get; }
public EvaluationScope EvaluationScope { get; }
public List<ScriptRuntimeFunctionOption> Options { get; }
public ScriptRuntimeFunction(ImmutableArray<BoundVariableSymbol> parameters, BoundBlockStatement block,
EvaluationScope evaluationScope)
public ScriptRuntimeFunction(List<ScriptRuntimeFunctionOption> options)
{
Options = options;
}
public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script,
EvaluationScope scope, TextSpan span)
{
var option = GetValidOption(variables);
if (option == null)
{
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<BoundVariableSymbol> parameters,
BoundBlockStatement block, EvaluationScope scope)
{
Parameters = parameters;
Block = block;
EvaluationScope = evaluationScope;
EvaluationScope = scope;
}
public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span)
public EvaluationScope EvaluationScope { get; }
public BoundBlockStatement Block { get; }
public ImmutableArray<BoundVariableSymbol> 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++)
@ -33,3 +112,4 @@ namespace Upsilon.BaseTypes.ScriptFunction
}
}
}
}

View File

@ -19,8 +19,8 @@ namespace Upsilon.Binder
public BoundScope Scope { get; private set; }
private readonly Script _script;
private Dictionary<string, UnboundFunctionExpression> _unboundFunctions =
new Dictionary<string, UnboundFunctionExpression>();
private List<UnboundFunctionExpression> _unboundFunctions =
new List<UnboundFunctionExpression>();
public Binder(Diagnostics diagnostics, Dictionary<string, VariableSymbol> 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<string, UnboundFunctionExpression>();
_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);
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;
scriptFunction.IsBound = true;
scriptFunction.ResultType = returnType;
_unboundFunctions.Remove(scriptFunction.Name);
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);
}
}
}

View File

@ -9,12 +9,14 @@ namespace Upsilon.Binder
public class UnboundFunctionExpression : BoundFunctionExpression
{
public UnboundFunctionExpression(ImmutableArray<BoundVariableSymbol> 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; }

View File

@ -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<FunctionVariableSymbolOption> FunctionOption { get; } = new List<FunctionVariableSymbolOption>();
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<BoundExpression> 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; }
}
}

View File

@ -9,47 +9,40 @@ 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<BoundExpression> 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);
}
if (!(functionVariableSymbolOption is InternalFunctionVariableOption option))
continue;
if (option.FunctionParameters.Length != callingParameters.Length)
continue;
var isValid = true;
for (var i = 0; i < callingParameters.Length; i++)
{
var functionParameter = FunctionParameters[i];
var functionParameter = option.FunctionParameters[i];
var callingParameter = callingParameters[i];
if (callingParameter.Type == Type.Unknown || callingParameter.Type == Type.Nil)
continue;
if (!functionParameter.ValidTypes.HasFlag(callingParameter.Type))
{
return (false,
$"Unexpected variable passed to internal function at variable {i + 1}. " +
$"Expected one of the following: {functionParameter.ValidTypes.ToString()}, got: '{callingParameter.Type}'",
callingParameter);
isValid = false;
break;
}
if (functionParameter.ValidTypes.HasFlag(Type.UserData))
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)
@ -58,23 +51,48 @@ namespace Upsilon.Binder.VariableSymbols
(UserDataBoundTypeDefinition) ((UserDataVariableSymbol) variable).BoundTypeDefinition;
if (functionParameter.ExpectedUserData != null && !functionParameter.ExpectedUserData.Contains(parent.Name))
{
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);
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();
}
}
}

View File

@ -1,47 +1,63 @@
using System.Collections.Immutable;
using System.Linq;
using Upsilon.BaseTypes;
namespace Upsilon.Binder.VariableSymbols
{
public class ScriptFunctionVariableSymbol : FunctionVariableSymbol
{
public ImmutableArray<VariableSymbol> Parameters { get; }
public bool IsBound { get; set; }
public ScriptFunctionVariableSymbol(string name, bool local, ImmutableArray<VariableSymbol> 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<BoundExpression> 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++)
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++)
{
var functionParameter = Parameters[i];
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)
callingParameter.Type != BaseTypes.Type.Unknown &&
callingParameter.Type != BaseTypes.Type.Nil)
{
if (callingParameter.Type != functionParameter.TypeContainer)
{
return (false, $"Invalid type for function '{Name}' at parameter '{functionParameter.Name}'. " +
$"Expected type '{functionParameter.TypeContainer}', got '{callingParameter.Type}'",
callingParameter);
isValid = false;
break;
}
}
}
if (!isValid)
continue;
return (true, null, null);
}
return (false, null, null);
}
}
public class ScriptFunctionVariableOption : FunctionVariableSymbolOption
{
public ImmutableArray<VariableSymbol> Parameters { get; }
public bool IsBound { get; set; }
public ScriptFunctionVariableOption(Type resultType, ImmutableArray<VariableSymbol> parameters) : base(resultType)
{
Parameters = parameters;
}
public override TypeContainer[] GetParameterTypes()
{
return Parameters.Select(x => x.TypeContainer).ToArray();
}
}
}

View File

@ -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)
{
var option = function.GetValidOption(parameters);
if (option == null)
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);
$"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
{
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<ScriptRuntimeFunction.ScriptRuntimeFunctionOption>(){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<ScriptRuntimeFunction.ScriptRuntimeFunctionOption>(){option});
return func;
}

View File

@ -182,5 +182,23 @@ return value
var result = Executor.EvaluateScript<long>(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<long>(input, "a", new object[] {50}));
Assert.True(Executor.EvaluateFunction<bool>(input, "a", new object[] {"test"}));
Assert.True(Executor.EvaluateScript<bool>(input, Options));
}
}
}