Allow parameter overloading script functions
This commit is contained in:
parent
43da2b3d19
commit
3c0e5f5b13
|
@ -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<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)
|
||||
{
|
||||
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<BoundVariableSymbol> parameters,
|
||||
BoundBlockStatement block, EvaluationScope scope)
|
||||
{
|
||||
Parameters = parameters;
|
||||
Block = block;
|
||||
EvaluationScope = scope;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
var parameterVariable = Parameters[i];
|
||||
var parameterValue = variables[i];
|
||||
innerEvaluator.Scope.CreateLocal(parameterVariable.VariableSymbol, parameterValue);
|
||||
}
|
||||
return innerEvaluator.EvaluateNode(Block);
|
||||
}
|
||||
return innerEvaluator.EvaluateNode(Block);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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<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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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++)
|
||||
{
|
||||
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<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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue