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.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using Upsilon.BaseTypes.ScriptTypeInterfaces;
|
using Upsilon.BaseTypes.ScriptTypeInterfaces;
|
||||||
using Upsilon.Binder;
|
using Upsilon.Binder;
|
||||||
|
using Upsilon.Binder.VariableSymbols;
|
||||||
using Upsilon.Evaluator;
|
using Upsilon.Evaluator;
|
||||||
using Upsilon.Text;
|
using Upsilon.Text;
|
||||||
|
|
||||||
namespace Upsilon.BaseTypes.ScriptFunction
|
namespace Upsilon.BaseTypes.ScriptFunction
|
||||||
{
|
{
|
||||||
internal class ScriptRuntimeFunction : ScriptFunction, IScopeOwner
|
internal class ScriptRuntimeFunction : ScriptFunction
|
||||||
{
|
{
|
||||||
public BoundBlockStatement Block { get; }
|
public List<ScriptRuntimeFunctionOption> Options { get; }
|
||||||
public ImmutableArray<BoundVariableSymbol> Parameters { get; }
|
|
||||||
public EvaluationScope EvaluationScope { get; }
|
|
||||||
|
|
||||||
public ScriptRuntimeFunction(ImmutableArray<BoundVariableSymbol> parameters, BoundBlockStatement block,
|
public ScriptRuntimeFunction(List<ScriptRuntimeFunctionOption> options)
|
||||||
EvaluationScope evaluationScope)
|
|
||||||
{
|
{
|
||||||
Parameters = parameters;
|
Options = options;
|
||||||
Block = block;
|
|
||||||
EvaluationScope = evaluationScope;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
var option = GetValidOption(variables);
|
||||||
for (var i = 0; i < Parameters.Length; i++)
|
if (option == null)
|
||||||
{
|
{
|
||||||
var parameterVariable = Parameters[i];
|
throw new EvaluationException(
|
||||||
var parameterValue = variables[i];
|
$"No valid function found");
|
||||||
innerEvaluator.Scope.CreateLocal(parameterVariable.VariableSymbol, parameterValue);
|
}
|
||||||
|
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; }
|
public BoundScope Scope { get; private set; }
|
||||||
private readonly Script _script;
|
private readonly Script _script;
|
||||||
|
|
||||||
private Dictionary<string, UnboundFunctionExpression> _unboundFunctions =
|
private List<UnboundFunctionExpression> _unboundFunctions =
|
||||||
new Dictionary<string, UnboundFunctionExpression>();
|
new List<UnboundFunctionExpression>();
|
||||||
|
|
||||||
public Binder(Diagnostics diagnostics, Dictionary<string, VariableSymbol> variables, Script script)
|
public Binder(Diagnostics diagnostics, Dictionary<string, VariableSymbol> variables, Script script)
|
||||||
{
|
{
|
||||||
|
@ -52,23 +52,22 @@ namespace Upsilon.Binder
|
||||||
foreach (var unboundFunctionStatement in _unboundFunctions)
|
foreach (var unboundFunctionStatement in _unboundFunctions)
|
||||||
{
|
{
|
||||||
Scope = new BoundScope(Scope);
|
Scope = new BoundScope(Scope);
|
||||||
foreach (var valueParameter in unboundFunctionStatement.Value.Parameters)
|
foreach (var valueParameter in unboundFunctionStatement.Parameters)
|
||||||
{
|
{
|
||||||
Scope.AssignToNearest(valueParameter.VariableSymbol);
|
Scope.AssignToNearest(valueParameter.VariableSymbol);
|
||||||
if (valueParameter.VariableSymbol.TypeContainer == Type.Unknown)
|
if (valueParameter.VariableSymbol.TypeContainer == Type.Unknown)
|
||||||
_diagnostics.LogUnknownVariableType(valueParameter.VariableSymbol.Name, valueParameter.Span);
|
_diagnostics.LogUnknownVariableType(valueParameter.VariableSymbol.Name, valueParameter.Span);
|
||||||
}
|
}
|
||||||
unboundFunctionStatement.Value.Block =
|
unboundFunctionStatement.Block =
|
||||||
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.Value.UnboundBlock);
|
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.UnboundBlock);
|
||||||
var resultType = Scope.ReturnType;
|
var resultType = Scope.ReturnType;
|
||||||
Scope = Scope.ParentScope;
|
Scope = Scope.ParentScope;
|
||||||
var variable =
|
//var variable =
|
||||||
(ScriptFunctionVariableSymbol) unboundFunctionStatement.Value.Scope.ParentScope.Variables[
|
// (ScriptFunctionVariableSymbol) unboundFunctionStatement.Scope.ParentScope.Variables[
|
||||||
unboundFunctionStatement.Key];
|
// unboundFunctionStatement];
|
||||||
variable.IsBound = true;
|
//variable.IsBound = true;
|
||||||
variable.ResultType = resultType;
|
|
||||||
}
|
}
|
||||||
_unboundFunctions = new Dictionary<string, UnboundFunctionExpression>();
|
_unboundFunctions.Clear();
|
||||||
return new BoundScript((BoundBlockStatement) bound, e.Span, Scope, fileName, _script);
|
return new BoundScript((BoundBlockStatement) bound, e.Span, Scope, fileName, _script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,25 +230,41 @@ namespace Upsilon.Binder
|
||||||
{
|
{
|
||||||
if (function is ScriptFunctionVariableSymbol scriptFunction)
|
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);
|
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];
|
var callingVariable = parameters[index];
|
||||||
functionVariable.TypeContainer = callingVariable.Type;
|
functionVariable.TypeContainer = callingVariable.Type;
|
||||||
Scope.DefineLocalVariable(functionVariable);
|
Scope.DefineLocalVariable(functionVariable);
|
||||||
}
|
}
|
||||||
|
|
||||||
var unboundFunctionStatement = _unboundFunctions[scriptFunction.Name];
|
UnboundFunctionExpression unbound = null;
|
||||||
unboundFunctionStatement.Block =
|
foreach (var functionExpression in this._unboundFunctions.Where(x =>
|
||||||
(BoundBlockStatement) BindBlockStatement(unboundFunctionStatement.UnboundBlock);
|
{
|
||||||
returnType = Scope.ReturnType;
|
if (x.Name == function.Name)
|
||||||
Scope = Scope.ParentScope;
|
{
|
||||||
scriptFunction.IsBound = true;
|
return parameters.Count == functionOption.Parameters.Length;
|
||||||
scriptFunction.ResultType = returnType;
|
}
|
||||||
_unboundFunctions.Remove(scriptFunction.Name);
|
|
||||||
|
return false;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
unbound = functionExpression;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
unbound.Block = (BoundBlockStatement) BindBlockStatement(unbound.UnboundBlock);
|
||||||
|
|
||||||
|
returnType = Scope.ReturnType;
|
||||||
|
Scope = Scope.ParentScope;
|
||||||
|
functionOption.IsBound = true;
|
||||||
|
functionOption.ResultType = returnType;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -287,7 +302,7 @@ namespace Upsilon.Binder
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
returnType = function.ResultType;
|
//returnType = function.ResultType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,15 +651,8 @@ namespace Upsilon.Binder
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var unbound = new UnboundFunctionExpression(parameters.ToImmutable(), e.Block, e.Span, innerScope);
|
var unbound = new UnboundFunctionExpression(parameters.ToImmutable(), e.Block, e.Span, innerScope, functionVariableSymbol);
|
||||||
if (functionVariableSymbol == null)
|
_unboundFunctions.Add(unbound);
|
||||||
{
|
|
||||||
_unboundFunctions.Add( Guid.NewGuid().ToString(), unbound);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_unboundFunctions.Add(functionVariableSymbol, unbound);
|
|
||||||
}
|
|
||||||
return unbound;
|
return unbound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -677,8 +685,8 @@ namespace Upsilon.Binder
|
||||||
CommentValue = commentData.ToArray()
|
CommentValue = commentData.ToArray()
|
||||||
};
|
};
|
||||||
variable = functionVariable;
|
variable = functionVariable;
|
||||||
functionVariable.IsBound = !(func is UnboundFunctionExpression);
|
((ScriptFunctionVariableOption)functionVariable.FunctionOption[0]).IsBound = !(func is UnboundFunctionExpression);
|
||||||
functionVariable.ResultType = func.ReturnType;
|
functionVariable.FunctionOption[0].ResultType = func.ReturnType;
|
||||||
if (isLocal)
|
if (isLocal)
|
||||||
Scope.DefineLocalVariable(variable);
|
Scope.DefineLocalVariable(variable);
|
||||||
else
|
else
|
||||||
|
@ -699,6 +707,18 @@ namespace Upsilon.Binder
|
||||||
return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span);
|
return new BoundExpressionStatement(new BoundBadExpression(e.Span), e.Span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (variable is ScriptFunctionVariableSymbol functionVariable)
|
||||||
|
{
|
||||||
|
var functionOption =
|
||||||
|
new ScriptFunctionVariableOption(func.ReturnType, parameters.ToImmutable())
|
||||||
|
{
|
||||||
|
IsBound = !(func is UnboundFunctionExpression)
|
||||||
|
};
|
||||||
|
functionVariable.FunctionOption.Add(functionOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,14 @@ namespace Upsilon.Binder
|
||||||
public class UnboundFunctionExpression : BoundFunctionExpression
|
public class UnboundFunctionExpression : BoundFunctionExpression
|
||||||
{
|
{
|
||||||
public UnboundFunctionExpression(ImmutableArray<BoundVariableSymbol> parameters,
|
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)
|
: base(parameters, null, span, scope, BaseTypes.Type.Unknown)
|
||||||
{
|
{
|
||||||
UnboundBlock = unboundBlock;
|
UnboundBlock = unboundBlock;
|
||||||
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
public override BoundKind Kind => BoundKind.BoundPromise;
|
public override BoundKind Kind => BoundKind.BoundPromise;
|
||||||
|
|
||||||
public BlockStatementSyntax UnboundBlock { get; }
|
public BlockStatementSyntax UnboundBlock { get; }
|
||||||
|
|
|
@ -1,19 +1,49 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using Upsilon.BaseTypes;
|
using Upsilon.BaseTypes;
|
||||||
|
|
||||||
namespace Upsilon.Binder.VariableSymbols
|
namespace Upsilon.Binder.VariableSymbols
|
||||||
{
|
{
|
||||||
public abstract class FunctionVariableSymbol : VariableSymbol
|
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)
|
public FunctionVariableSymbol(string name, bool local, Type resultType)
|
||||||
: base(name, BaseTypes.Type.Function, local)
|
: base(name, BaseTypes.Type.Function, local)
|
||||||
{
|
{
|
||||||
ResultType = resultType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract (bool IsValid, string Error, BoundExpression WrongParameter) ValidateParameters(
|
public abstract (bool IsValid, string Error, BoundExpression WrongParameter) ValidateParameters(
|
||||||
ImmutableArray<BoundExpression> callingParameters);
|
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
|
public class InternalFunctionVariableSymbol : FunctionVariableSymbol
|
||||||
{
|
{
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
public InternalFunctionVariableSymbol(string name, bool local, TypeContainer resultType,
|
||||||
public InternalFunctionParameter[] FunctionParameters { get; }
|
|
||||||
private int MinimalParametersRequired { get; }
|
|
||||||
private MethodInfo OverrideResultType { get; }
|
|
||||||
|
|
||||||
public InternalFunctionVariableSymbol(string name, bool local, Type resultType,
|
|
||||||
InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType)
|
InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType)
|
||||||
: base(name, local, resultType)
|
: base(name, local, resultType)
|
||||||
{
|
{
|
||||||
FunctionParameters = functionParameters;
|
FunctionOption.Add(new InternalFunctionVariableOption(resultType, functionParameters, overrideResultType));
|
||||||
MinimalParametersRequired = functionParameters.Count(x => !x.IsOptional);
|
|
||||||
OverrideResultType = overrideResultType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (bool IsValid, string Error,
|
public override (bool IsValid, string Error,
|
||||||
BoundExpression WrongParameter) ValidateParameters(ImmutableArray<BoundExpression> callingParameters)
|
BoundExpression WrongParameter) ValidateParameters(ImmutableArray<BoundExpression> callingParameters)
|
||||||
{
|
{
|
||||||
if (callingParameters.Length < MinimalParametersRequired
|
foreach (var functionVariableSymbolOption in FunctionOption)
|
||||||
|| callingParameters.Length > FunctionParameters.Length)
|
|
||||||
{
|
{
|
||||||
return (false,
|
if (!(functionVariableSymbolOption is InternalFunctionVariableOption option))
|
||||||
$"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)
|
|
||||||
continue;
|
continue;
|
||||||
|
if (option.FunctionParameters.Length != callingParameters.Length)
|
||||||
if (!functionParameter.ValidTypes.HasFlag(callingParameter.Type))
|
continue;
|
||||||
|
var isValid = true;
|
||||||
|
for (var i = 0; i < callingParameters.Length; i++)
|
||||||
{
|
{
|
||||||
return (false,
|
var functionParameter = option.FunctionParameters[i];
|
||||||
$"Unexpected variable passed to internal function at variable {i + 1}. " +
|
var callingParameter = callingParameters[i];
|
||||||
$"Expected one of the following: {functionParameter.ValidTypes.ToString()}, got: '{callingParameter.Type}'",
|
if (callingParameter.Type == Type.Unknown || callingParameter.Type == Type.Nil)
|
||||||
callingParameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (functionParameter.ValidTypes.HasFlag(Type.UserData))
|
|
||||||
{
|
|
||||||
var variable = Binder.ResolveVariable(callingParameter, null);
|
|
||||||
if (variable != null && variable.TypeContainer == Type.UserData)
|
|
||||||
{
|
{
|
||||||
var parent =
|
isValid = false;
|
||||||
(UserDataBoundTypeDefinition) ((UserDataVariableSymbol) variable).BoundTypeDefinition;
|
break;
|
||||||
if (functionParameter.ExpectedUserData != null && !functionParameter.ExpectedUserData.Contains(parent.Name))
|
}
|
||||||
|
|
||||||
|
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,
|
var parent =
|
||||||
$"Unexpected variable passed to internal function at variable {i + 1}. " +
|
(UserDataBoundTypeDefinition) ((UserDataVariableSymbol) variable).BoundTypeDefinition;
|
||||||
$"Expected to be the following: {string.Join(", ", functionParameter.ExpectedUserData)}, got: '{parent.Name}'",
|
if (functionParameter.ExpectedUserData != null && !functionParameter.ExpectedUserData.Contains(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)
|
public TypeContainer GetResultType(BoundExpression[] parameters)
|
||||||
{
|
{
|
||||||
if (OverrideResultType == null)
|
foreach (var functionVariableSymbolOption in FunctionOption)
|
||||||
return ResultType;
|
{
|
||||||
return (TypeContainer) OverrideResultType.Invoke(null, new object[] {parameters});
|
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
|
public class InternalFunctionParameter
|
||||||
|
@ -95,9 +113,28 @@ namespace Upsilon.Binder.VariableSymbols
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public Type ValidTypes { get; }
|
public TypeContainer ValidTypes { get; }
|
||||||
public string[] ExpectedUserData { get; }
|
public string[] ExpectedUserData { get; }
|
||||||
public bool IsOptional { 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.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
using Upsilon.BaseTypes;
|
using Upsilon.BaseTypes;
|
||||||
|
|
||||||
namespace Upsilon.Binder.VariableSymbols
|
namespace Upsilon.Binder.VariableSymbols
|
||||||
{
|
{
|
||||||
public class ScriptFunctionVariableSymbol : FunctionVariableSymbol
|
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)
|
public ScriptFunctionVariableSymbol(string name, bool local, ImmutableArray<VariableSymbol> parameters, Type resultType)
|
||||||
: base(name, local, 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)
|
public override (bool IsValid, string Error, BoundExpression WrongParameter) ValidateParameters(ImmutableArray<BoundExpression> callingParameters)
|
||||||
{
|
{
|
||||||
if (Parameters.Length != callingParameters.Length)
|
foreach (var functionVariableSymbolOption in FunctionOption)
|
||||||
{
|
{
|
||||||
return (false,
|
if (!(functionVariableSymbolOption is ScriptFunctionVariableOption option))
|
||||||
$"Invalid number of parameters for function '{Name}'. Expected {Parameters.Length}, got {callingParameters.Length}",
|
continue;
|
||||||
null);
|
if (option.Parameters.Length != callingParameters.Length)
|
||||||
}
|
continue;
|
||||||
|
bool isValid = true;
|
||||||
for (var i = 0; i < Parameters.Length; i++)
|
for (var i = 0; i < option.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 (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}'. " +
|
if (callingParameter.Type != functionParameter.TypeContainer)
|
||||||
$"Expected type '{functionParameter.TypeContainer}', got '{callingParameter.Type}'",
|
{
|
||||||
callingParameter);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Upsilon.BaseTypes;
|
using Upsilon.BaseTypes;
|
||||||
using Upsilon.BaseTypes.Number;
|
using Upsilon.BaseTypes.Number;
|
||||||
|
@ -86,55 +87,11 @@ namespace Upsilon.Evaluator
|
||||||
}
|
}
|
||||||
var function = (ScriptRuntimeFunction) statement;
|
var function = (ScriptRuntimeFunction) statement;
|
||||||
var innerEvaluator = new Evaluator(_diagnostics, Scope, _script);
|
var innerEvaluator = new Evaluator(_diagnostics, Scope, _script);
|
||||||
if (parameters != null)
|
var option = function.GetValidOption(parameters);
|
||||||
{
|
if (option == null)
|
||||||
for (var index = 0; index < parameters.Length; index++)
|
throw new EvaluationException(
|
||||||
{
|
$"No function found with name '{functionName}' and available parameters {string.Join(", ", parameters.Select(x => $"{x.GetType().Name}"))}");
|
||||||
object parameter;
|
var result = option.Run(_diagnostics, parameters.Select(x => x.ToScriptType()).ToArray(), _script, Scope, new TextSpan());
|
||||||
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);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,22 +496,42 @@ namespace Upsilon.Evaluator
|
||||||
|
|
||||||
private void EvaluateBoundFunctionAssigmentStatement(BoundFunctionAssignmentStatement e)
|
private void EvaluateBoundFunctionAssigmentStatement(BoundFunctionAssignmentStatement e)
|
||||||
{
|
{
|
||||||
var func = EvaluateBoundFunctionStatement(e.Func);
|
var func = (ScriptRuntimeFunction)EvaluateBoundFunctionStatement(e.Func);
|
||||||
if (e.Variable.Local)
|
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
|
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)
|
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;
|
return func;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScriptType EvaluateUnboundFunctionStatement(UnboundFunctionExpression unboundFunctionExpression)
|
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;
|
return func;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,5 +182,23 @@ return value
|
||||||
var result = Executor.EvaluateScript<long>(input, Options);
|
var result = Executor.EvaluateScript<long>(input, Options);
|
||||||
Assert.Equal(6, result);
|
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