Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Redesigned the way functions are resolved #76

Merged
merged 10 commits into from
Mar 18, 2022
6 changes: 3 additions & 3 deletions ExpressionEngine/ExpressionGrammar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ public class ExpressionGrammar
private readonly Parser<IRule> _method;
private readonly Parser<Task<ValueContainer>> _input;

public ExpressionGrammar(IEnumerable<IFunction> functions, IEnumerable<IFunctionDefinition> functionDefinitions)
public ExpressionGrammar(IEnumerable<FunctionMetadata> functions, IEnumerable<IFunctionDefinition> functionDefinitions, IServiceProvider serviceProvider)
{
_functionDefinitions = functionDefinitions?.ToList();

var functionCollection = functions ?? throw new ArgumentNullException(nameof(functions));
var functionCollection = functions.ToList() ?? throw new ArgumentNullException(nameof(functions));

#region BasicAuxParsers

Expand Down Expand Up @@ -92,7 +92,7 @@ from args in argument.Token().DelimitedBy(Parse.Char(',')).Optional()
from mandatoryLetter in Parse.Letter
from rest in Parse.LetterOrDigit.Many().Text()
from args in arguments.Contained(lParenthesis, rParenthesis)
select new ExpressionRule(functionCollection, mandatoryLetter + rest,
select new ExpressionRule(functionCollection, serviceProvider, mandatoryLetter + rest,
args.IsEmpty
? null
: args.Get());
Expand Down
161 changes: 105 additions & 56 deletions ExpressionEngine/FlowRunnerDependencyExtension.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ExpressionEngine.Functions.Base;
using System;
using ExpressionEngine.Functions.Base;
using ExpressionEngine.Functions.CustomException;
using ExpressionEngine.Functions.Implementations.CollectionFunctions;
using ExpressionEngine.Functions.Implementations.ConversionFunctions;
Expand All @@ -9,8 +10,15 @@

namespace ExpressionEngine
{
/// <summary>
/// Selection of extension methods
/// </summary>
public static class FlowRunnerDependencyExtension
{
/// <summary>
/// Add necessary dependencies inorder to use expression engine.
/// </summary>
/// <param name="services"><see cref="IServiceCollection"/> to add dependencies</param>
public static void AddExpressionEngine(this IServiceCollection services)
{
services.AddScoped<IExpressionEngine, ExpressionEngine>();
Expand All @@ -22,8 +30,49 @@ public static void AddExpressionEngine(this IServiceCollection services)
AddLogicalComparisonFunctions(services);
AddMathFunctions(services);

services.AddTransient<IFunction, LengthFunction>();
services.AddTransient<IFunction, GreaterFunction>();
services.RegisterTransientFunctionAlias<LengthFunction>("length");
services.RegisterTransientFunctionAlias<GreaterFunction>("greater");
}

/// <summary>
/// Register function to be used in expression, function implementation must implement <see cref="IFunction"/>.
/// </summary>
/// <param name="services">Services which to add function metadata</param>
/// <param name="functionName">name of function used to invoke it</param>
/// <typeparam name="T">Function implementation</typeparam>
public static void RegisterTransientFunctionAlias<T>(this IServiceCollection services, string functionName)
where T : class, IFunction
{
services.AddTransient<T>();
services.AddSingleton(new FunctionMetadata(typeof(T), functionName));
}

/// <summary>
/// Register function to be used in expression, function implementation must implement <see cref="IFunction"/>.
/// </summary>
/// <param name="services">Services which to add function metadata</param>
/// <param name="functionName">name of function used to invoke it</param>
/// <typeparam name="T">Function implementation</typeparam>
public static void RegisterScopedFunctionAlias<T>(this IServiceCollection services, string functionName)
where T : class, IFunction
{
services.AddScoped<T>();
services.AddSingleton(new FunctionMetadata(typeof(T), functionName));
}

/// <summary>
/// Register function to be used in expression, function implementation must implement <see cref="IFunction"/>.
/// </summary>
/// <param name="services">Services which to add function metadata</param>
/// <param name="functionName">name of function used to invoke it</param>
/// <param name="implementationFactory"></param>
/// <typeparam name="T">Function implementation</typeparam>
public static void RegisterScopedFunctionAlias<T>(this IServiceCollection services, string functionName,
Func<IServiceProvider, T> implementationFactory)
where T : class, IFunction
{
services.AddScoped(implementationFactory);
services.AddSingleton(new FunctionMetadata(typeof(T), functionName));
}

/// <summary>
Expand All @@ -44,75 +93,75 @@ public static void AddFunctionDefinition(this IServiceCollection services, strin

private static void AddStringFunctions(IServiceCollection services)
{
services.AddTransient<IFunction, ConcatFunction>();
services.AddTransient<IFunction, EndsWithFunction>();
services.AddTransient<IFunction, FormatNumberFunction>();
services.AddTransient<IFunction, GuidFunction>();
services.AddTransient<IFunction, IndexOfFunction>();
services.AddTransient<IFunction, LastIndexOfFunction>();
services.AddTransient<IFunction, LengthFunction>();
services.AddTransient<IFunction, ReplaceFunction>();
services.AddTransient<IFunction, SplitFunction>();
services.AddTransient<IFunction, StartsWithFunction>();
services.AddTransient<IFunction, SubstringFunction>();
services.AddTransient<IFunction, ToLowerFunction>();
services.AddTransient<IFunction, ToUpperFunction>();
services.AddTransient<IFunction, TrimFunction>();
services.RegisterTransientFunctionAlias<ConcatFunction>("concat");
services.RegisterTransientFunctionAlias<EndsWithFunction>("endsWith");
services.RegisterTransientFunctionAlias<FormatNumberFunction>("formatNumber");
services.RegisterTransientFunctionAlias<GuidFunction>("guid");
services.RegisterTransientFunctionAlias<IndexOfFunction>("indexOf");
services.RegisterTransientFunctionAlias<LastIndexOfFunction>("lastIndexOf");
services.RegisterTransientFunctionAlias<LengthFunction>("length");
services.RegisterTransientFunctionAlias<ReplaceFunction>("replace");
services.RegisterTransientFunctionAlias<SplitFunction>("split");
services.RegisterTransientFunctionAlias<StartsWithFunction>("startsWith");
services.RegisterTransientFunctionAlias<SubstringFunction>("substring");
services.RegisterTransientFunctionAlias<ToLowerFunction>("toLower");
services.RegisterTransientFunctionAlias<ToUpperFunction>("toUpper");
services.RegisterTransientFunctionAlias<TrimFunction>("trim");
}

private static void AddCollectionFunction(IServiceCollection services)
{
services.AddTransient<IFunction, ContainsFunction>();
services.AddTransient<IFunction, EmptyFunction>();
services.AddTransient<IFunction, FirstFunction>();
services.AddTransient<IFunction, InterSectionFunction>();
services.AddTransient<IFunction, JoinFunction>();
services.AddTransient<IFunction, LastFunction>();
services.AddTransient<IFunction, LengthFunction>();
services.AddTransient<IFunction, SkipFunction>();
services.AddTransient<IFunction, TakeFunction>();
services.AddTransient<IFunction, UnionFunction>();
services.RegisterTransientFunctionAlias<ContainsFunction>("contains");
services.RegisterTransientFunctionAlias<EmptyFunction>("empty");
services.RegisterTransientFunctionAlias<FirstFunction>("first");
services.RegisterTransientFunctionAlias<InterSectionFunction>("intersection");
services.RegisterTransientFunctionAlias<JoinFunction>("join");
services.RegisterTransientFunctionAlias<LastFunction>("last");
services.RegisterTransientFunctionAlias<LengthFunction>("length");
services.RegisterTransientFunctionAlias<SkipFunction>("skip");
services.RegisterTransientFunctionAlias<TakeFunction>("take");
services.RegisterTransientFunctionAlias<UnionFunction>("union");
}

private static void AddConversionFunction(IServiceCollection services)
{
services.AddTransient<IFunction, ArrayFunction>();
services.AddTransient<IFunction, Base64Function>();
services.AddTransient<IFunction, Base64ToBinaryFunction>();
services.AddTransient<IFunction, Base64ToStringFunction>();
services.AddTransient<IFunction, BinaryFunction>();
services.AddTransient<IFunction, BoolFunction>();
services.AddTransient<IFunction, CreateArrayFunction>();
services.AddTransient<IFunction, DataUriFunction>();
services.AddTransient<IFunction, DataUriToBinaryFunction>();
services.AddTransient<IFunction, FloatFunction>();
services.AddTransient<IFunction, IntFunction>();
services.RegisterTransientFunctionAlias<ArrayFunction>("array");
services.RegisterTransientFunctionAlias<Base64Function>("base64");
services.RegisterTransientFunctionAlias<Base64ToBinaryFunction>("base64ToBinary");
services.RegisterTransientFunctionAlias<Base64ToStringFunction>("base64ToString");
services.RegisterTransientFunctionAlias<BinaryFunction>("binary");
services.RegisterTransientFunctionAlias<BoolFunction>("bool");
services.RegisterTransientFunctionAlias<CreateArrayFunction>("createArray");
services.RegisterTransientFunctionAlias<DataUriFunction>("dataUri");
services.RegisterTransientFunctionAlias<DataUriToBinaryFunction>("dataUriToBinary");
services.RegisterTransientFunctionAlias<FloatFunction>("float");
services.RegisterTransientFunctionAlias<IntFunction>("int");
}

private static void AddLogicalComparisonFunctions(IServiceCollection services)
{
services.AddTransient<IFunction, AndFunction>();
services.AddTransient<IFunction, EqualFunction>();
services.AddTransient<IFunction, GreaterFunction>();
services.AddTransient<IFunction, GreaterOrEqualsFunction>();
services.AddTransient<IFunction, IfFunction>();
services.AddTransient<IFunction, LessFunction>();
services.AddTransient<IFunction, LessOrEqualsFunction>();
services.AddTransient<IFunction, NotFunction>();
services.AddTransient<IFunction, OrFunction>();
services.RegisterTransientFunctionAlias<AndFunction>("and");
services.RegisterTransientFunctionAlias<EqualFunction>("equal");
services.RegisterTransientFunctionAlias<GreaterFunction>("greater");
services.RegisterTransientFunctionAlias<GreaterOrEqualsFunction>("greaterOrEquals");
services.RegisterTransientFunctionAlias<IfFunction>("if");
services.RegisterTransientFunctionAlias<LessFunction>("less");
services.RegisterTransientFunctionAlias<LessOrEqualsFunction>("lessOrEquals");
services.RegisterTransientFunctionAlias<NotFunction>("not");
services.RegisterTransientFunctionAlias<OrFunction>("or");
}

private static void AddMathFunctions(IServiceCollection services)
{
services.AddTransient<IFunction, AddFunction>();
services.AddTransient<IFunction, DivFunction>();
services.AddTransient<IFunction, MaxFunction>();
services.AddTransient<IFunction, MinFunction>();
services.AddTransient<IFunction, ModFunction>();
services.AddTransient<IFunction, MulFunction>();
services.AddTransient<IFunction, RandFunction>();
services.AddTransient<IFunction, RangeFunction>();
services.AddTransient<IFunction, SubFunction>();
services.RegisterTransientFunctionAlias<AddFunction>("add");
services.RegisterTransientFunctionAlias<DivFunction>("div");
services.RegisterTransientFunctionAlias<MaxFunction>("max");
services.RegisterTransientFunctionAlias<MinFunction>("min");
services.RegisterTransientFunctionAlias<ModFunction>("mod");
services.RegisterTransientFunctionAlias<MulFunction>("mul");
services.RegisterTransientFunctionAlias<RandFunction>("rand");
services.RegisterTransientFunctionAlias<RangeFunction>("range");
services.RegisterTransientFunctionAlias<SubFunction>("sub");
}
}
}
33 changes: 33 additions & 0 deletions ExpressionEngine/FunctionMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using ExpressionEngine.Functions.Base;

namespace ExpressionEngine
{
/// <summary>
/// FunctionMetadata is used to register functions
/// </summary>
public class FunctionMetadata
{
/// <summary>
///
/// </summary>
/// <param name="function">Function must implement <see cref="IFunction"/></param>
/// <param name="alias"></param>
public FunctionMetadata(Type function, string alias)
{

Function = function;
Alias = alias;
}

/// <summary>
/// Function implementation
/// </summary>
public Type Function { get; set; }

/// <summary>
/// Alias used to invoke function
/// </summary>
public string Alias { get; set; }
}
}
26 changes: 10 additions & 16 deletions ExpressionEngine/Functions/Base/IFunction.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
using System;
using System.Threading.Tasks;

namespace ExpressionEngine.Functions.Base
{
/// <summary>
/// Interface for implementing a function
/// </summary>
public interface IFunction
{
string FunctionName { get; }
ValueTask<ValueContainer> ExecuteFunction(ValueContainer[] strings);
/// <summary>
/// Execute the given function implementation
/// </summary>
/// <param name="parameters">Parameters given to the functiopn</param>
/// <returns>ValueContainer with the result</returns>
ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters);
}

public abstract class Function : IFunction
{
public string FunctionName { get; }

protected Function(string functionName)
{
FunctionName = functionName ?? throw new ArgumentNullException(nameof(functionName));
}

public abstract ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@

namespace ExpressionEngine.Functions.Implementations.CollectionFunctions
{
public class ContainsFunction : Function
public class ContainsFunction : IFunction
{
public ContainsFunction() : base("contains")
{
}

public override ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
public ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
{
var collection = parameters[0];
var value = parameters[1];
Expand Down Expand Up @@ -48,4 +44,4 @@ public override ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@

namespace ExpressionEngine.Functions.Implementations.CollectionFunctions
{
public class EmptyFunction : Function
public class EmptyFunction : IFunction
{
public EmptyFunction() : base("empty")
{
}

public override ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
public ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
{
var value = parameters[0];

Expand All @@ -29,4 +25,4 @@ public override ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[
};
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@

namespace ExpressionEngine.Functions.Implementations.CollectionFunctions
{
public class FirstFunction : Function
public class FirstFunction : IFunction
{
public FirstFunction() : base("first")
{
}

public override ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
public ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
{
var value = parameters[0];

Expand All @@ -26,4 +22,4 @@ public override ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[
};
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@

namespace ExpressionEngine.Functions.Implementations.CollectionFunctions
{
public class InterSectionFunction : Function
public class InterSectionFunction : IFunction
{
public InterSectionFunction() : base("intersection")
{
}

public override ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
public ValueTask<ValueContainer> ExecuteFunction(params ValueContainer[] parameters)
{
return parameters[0].Type() switch
{
Expand Down Expand Up @@ -49,4 +45,4 @@ private ValueTask<ValueContainer> IntersectList(IReadOnlyList<ValueContainer> pa
return new ValueTask<ValueContainer>(new ValueContainer(intersection));
}
}
}
}
Loading