Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace StringToExpression.Exceptions
{
/// <summary>
/// Exception when a collection accessor pattern as been defined multiple times.
/// </summary>
public class AccessorDefinitionDuplicatePatternException : Exception
{
/// <summary>
/// The pattern that was duplicated
/// </summary>
public readonly string Pattern;

/// <summary>
/// Initializes a new instance of the <see cref="AccessorDefinitionDuplicatePatternException"/> class.
/// </summary>
/// <param name="pattern">Pattern that has been duplicated.</param>
public AccessorDefinitionDuplicatePatternException(string pattern) : base($"Pattern '{pattern}' has been defined multiple times")
{
Pattern = pattern;
}
}
}
38 changes: 38 additions & 0 deletions src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using StringToExpression.Exceptions;
using StringToExpression.Parser;
using StringToExpression.Tokenizer;
using System;
using System.Linq;
using System.Linq.Expressions;

namespace StringToExpression.GrammerDefinitions
{
public class CollectionAccessor : GrammerDefinition
{
public readonly Func<string, Operand[], ParameterExpression[], Expression> ExpressionBuilder;


public CollectionAccessor(string name, string regex)
: base(name, regex)
{

}

public override void Apply(Token token, ParseState state)
{
var tokenValue = token.Value.Replace(":", "");
var genType = state.Operands.ToArray().FirstOrDefault().Expression.Type.GenericTypeArguments.FirstOrDefault();

var paramExpression = Expression.Parameter(genType, tokenValue);

if (!state.Parameters.Select(p => p.Pattern).Contains(tokenValue))
{
state.Parameters.Add(new Accessor(paramExpression, tokenValue));
}
else
{
throw new AccessorDefinitionDuplicatePatternException(tokenValue);
}
}
}
}
67 changes: 67 additions & 0 deletions src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using StringToExpression.Exceptions;
using StringToExpression.Parser;
using StringToExpression.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace StringToExpression.GrammerDefinitions
{
public class CollectionDefinition : BracketOpenDefinition
{
public readonly Func<string, string, ParameterExpression[], Expression> ExpressionBuilder;

public CollectionDefinition(string name, string regex, Func<string, string, Expression> expressionBuilder)
: this(name, regex, (v, sm, a) => expressionBuilder(v, sm))
{

}

public CollectionDefinition(string name, string regex, Func<string, string, ParameterExpression[], Expression> expressionBuilder)
: base(name, regex)
{
ExpressionBuilder = expressionBuilder ?? throw new ArgumentNullException(nameof(expressionBuilder));
}


/// <summary>
/// Applies the bracket operands. Executes the expressionBuilder with all the operands in the brackets.
/// </summary>
/// <param name="bracketOpen">The operator that opened the bracket.</param>
/// <param name="bracketOperands">The list of operands within the brackets.</param>
/// <param name="bracketClose">The operator that closed the bracket.</param>
/// <param name="state">The current parse state.</param>
/// <exception cref="FunctionArgumentCountException">When the number of opreands does not match the number of arguments</exception>
/// <exception cref="FunctionArgumentTypeException">When argument Type does not match the type of the expression</exception>
/// <exception cref="OperationInvalidException">When an error occured while executing the expressionBuilder</exception>
public override void ApplyBracketOperands(Operator bracketOpen, Stack<Operand> bracketOperands, Operator bracketClose, ParseState state)
{
string methodName = "";
var operandSource = StringSegment.Encompass(bracketOperands.Select(x => x.SourceMap));

switch (bracketOpen.SourceMap.Value)
{
case "all(":
methodName = nameof(Enumerable.All);
break;
case "any(":
methodName = nameof(Enumerable.Any);
break;
default:
throw new NotImplementedException($"{bracketOpen.SourceMap.Value} is an unknown collection definition");
}

var operandType = state.Operands.First().Expression.Type.GetGenericArguments()[0];

var methodCall = Expression.Call(
typeof(Enumerable), methodName, new[] { operandType },
state.Operands.Pop().Expression,
Expression.Lambda(bracketOperands.Pop().Expression, state.Parameters.FirstOrDefault(p => p.Expression.Type == operandType).Expression));

var functionSourceMap = StringSegment.Encompass(bracketOpen.SourceMap, operandSource);

state.Operands.Push(new Operand(methodCall, functionSourceMap));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class OperandDefinition : GrammerDefinition
/// <summary>
/// A function to generate the operator's Expression.
/// </summary>
public readonly Func<string, ParameterExpression[], Expression> ExpressionBuilder;
public readonly Func<string, Accessor[], Expression> ExpressionBuilder;

/// <summary>
/// Initializes a new instance of the <see cref="OperandDefinition"/> class.
Expand All @@ -41,7 +41,7 @@ public OperandDefinition(string name, string regex, Func<string, Expression> exp
/// <param name="regex">The regex to match tokens.</param>
/// <param name="expressionBuilder">The function to generate the operator's Expression given the token's value and parsing state parameters.</param>
/// <exception cref="System.ArgumentNullException">expressionBuilder</exception>
public OperandDefinition(string name, string regex, Func<string, ParameterExpression[], Expression> expressionBuilder)
public OperandDefinition(string name, string regex, Func<string, Accessor[], Expression> expressionBuilder)
: base(name, regex)
{
if (expressionBuilder == null)
Expand All @@ -58,6 +58,7 @@ public OperandDefinition(string name, string regex, Func<string, ParameterExpres
public override void Apply(Token token, ParseState state)
{
Expression expression;

try
{
expression = ExpressionBuilder(token.Value, state.Parameters.ToArray());
Expand Down
7 changes: 4 additions & 3 deletions src/StringToExpression/Language.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StringToExpression.GrammerDefinitions;
using StringToExpression.Parser;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -44,7 +45,7 @@ public Language(params GrammerDefinition[] grammerDefintions)
/// <returns>
/// expression that represents the input string.
/// </returns>
public Expression Parse(string text, params ParameterExpression[] parameters)
public Expression Parse(string text, params Accessor[] parameters)
{
var tokenStream = this.Tokenizer.Tokenize(text);
var expression = Parser.Parse(tokenStream, parameters);
Expand Down Expand Up @@ -76,10 +77,10 @@ public Expression<Func<TOut>> Parse<TOut>(string text)
/// </returns>
public Expression<Func<TIn, TOut>> Parse<TIn, TOut>(string text)
{
var parameters = new[] { Expression.Parameter(typeof(TIn)) };
var parameters = new[] { (Accessor)Expression.Parameter(typeof(TIn)) };
var body = this.Parse(text, parameters);

return Expression.Lambda<Func<TIn, TOut>>(body, parameters);
return Expression.Lambda<Func<TIn, TOut>>(body, parameters.Select(p => p.Expression));
}
}
}
5 changes: 3 additions & 2 deletions src/StringToExpression/Languages/ArithmeticLanguage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using StringToExpression.GrammerDefinitions;
using StringToExpression.Parser;
using StringToExpression.Util;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -45,10 +46,10 @@ public Expression<Func<decimal>> Parse(string text)
/// <returns></returns>
public Expression<Func<T, decimal>> Parse<T>(string text)
{
var parameters = new[] { Expression.Parameter(typeof(T)) };
var parameters = new[] { (Accessor)Expression.Parameter(typeof(T)) };
var body = language.Parse(text, parameters);
body = ExpressionConversions.Convert(body, typeof(decimal));
return Expression.Lambda<Func<T, decimal>>(body, parameters);
return Expression.Lambda<Func<T, decimal>>(body, parameters.Select(p => p.Expression));
}

/// <summary>
Expand Down
86 changes: 71 additions & 15 deletions src/StringToExpression/Languages/ODataFilterLanguage.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using StringToExpression.GrammerDefinitions;
using StringToExpression.Parser;
using StringToExpression.Util;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace StringToExpression.LanguageDefinitions
{
Expand Down Expand Up @@ -88,7 +87,8 @@ protected static class DateTimeMembers
/// <summary>
/// Initializes a new instance of the <see cref="ODataFilterLanguage"/> class.
/// </summary>
public ODataFilterLanguage() {
public ODataFilterLanguage()
{
language = new Language(AllDefinitions().ToArray());
}

Expand All @@ -100,12 +100,12 @@ public ODataFilterLanguage() {
/// <returns></returns>
public Expression<Func<T, bool>> Parse<T>(string text)
{
var parameters = new[] { Expression.Parameter(typeof(T)) };
var parameters = new[] { (Accessor)Expression.Parameter(typeof(T)) };
var body = language.Parse(text, parameters);

ExpressionConversions.TryBoolean(ref body);

return Expression.Lambda<Func<T, bool>>(body, parameters);
return Expression.Lambda<Func<T, bool>>(body, parameters.Select(p => p.Expression));
}

/// <summary>
Expand All @@ -115,10 +115,13 @@ public Expression<Func<T, bool>> Parse<T>(string text)
protected virtual IEnumerable<GrammerDefinition> AllDefinitions()
{
IEnumerable<FunctionCallDefinition> functions;
IEnumerable<CollectionDefinition> collections;

var definitions = new List<GrammerDefinition>();
definitions.AddRange(TypeDefinitions());
definitions.AddRange(collections = CollectionDefinitions());
definitions.AddRange(functions = FunctionDefinitions());
definitions.AddRange(BracketDefinitions(functions));
definitions.AddRange(BracketDefinitions(functions, collections));
definitions.AddRange(LogicalOperatorDefinitions());
definitions.AddRange(ArithmeticOperatorDefinitions());
definitions.AddRange(PropertyDefinitions());
Expand Down Expand Up @@ -304,10 +307,11 @@ protected virtual IEnumerable<GrammerDefinition> ArithmeticOperatorDefinitions()
/// </summary>
/// <param name="functionCalls">The function calls in the language. (used as opening brackets)</param>
/// <returns></returns>
protected virtual IEnumerable<GrammerDefinition> BracketDefinitions(IEnumerable<FunctionCallDefinition> functionCalls)
protected virtual IEnumerable<GrammerDefinition> BracketDefinitions(IEnumerable<FunctionCallDefinition> functionCalls, IEnumerable<CollectionDefinition> collectionCalls)
{
BracketOpenDefinition openBracket;
ListDelimiterDefinition delimeter;

return new GrammerDefinition[] {
openBracket = new BracketOpenDefinition(
name: "OPEN_BRACKET",
Expand All @@ -318,7 +322,7 @@ protected virtual IEnumerable<GrammerDefinition> BracketDefinitions(IEnumerable<
new BracketCloseDefinition(
name: "CLOSE_BRACKET",
regex: @"\)",
bracketOpenDefinitions: new[] { openBracket }.Concat(functionCalls),
bracketOpenDefinitions: new[] { openBracket }.Concat(functionCalls).Concat(collectionCalls),
listDelimeterDefinition: delimeter)
};
}
Expand All @@ -337,7 +341,7 @@ protected virtual IEnumerable<FunctionCallDefinition> FunctionDefinitions()
argumentTypes: new[] {typeof(string), typeof(string) },
expressionBuilder: (parameters) => {
return Expression.Call(
instance:parameters[0],
instance:parameters[0],
method:StringMembers.StartsWith,
arguments: new [] { parameters[1] });
}),
Expand Down Expand Up @@ -443,18 +447,45 @@ protected virtual IEnumerable<FunctionCallDefinition> FunctionDefinitions()
/// <returns></returns>
protected virtual IEnumerable<GrammerDefinition> PropertyDefinitions()
{
return new[]
return new GrammerDefinition[]
{
//Properties
new CollectionAccessor(
name: "ACCESSOR",
regex:@"(?<=\()[a-z]+?:"
),
new OperandDefinition(
name:"PROPERTY_PATH",
regex: @"(?<![0-9])([A-Za-z_][A-Za-z0-9_]*/?)+",
expressionBuilder: (value, parameters) => {
return value.Split('/').Aggregate((Expression)parameters[0], (exp, prop)=> Expression.MakeMemberAccess(exp, TypeShim.GetProperty(exp.Type, prop)));
}),
regex: @"(?<![0-9])([A-Za-z_][A-Za-z0-9_]*\/?(?!any|all))+",
expressionBuilder: OperandBuilder
),
};
}

private static Expression OperandBuilder(string value, Accessor[] parameters)
{
Expression parameter;
var values = value.Split('/').ToList();

if (parameters.Any(p => !string.IsNullOrWhiteSpace(p.Pattern) && p.Pattern.ToLower() == values[0].ToLower()))
{
parameter = parameters.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p.Pattern) && p.Pattern.ToLower() == values[0].ToLower());
values.RemoveAt(0);
}
else
{
parameter = parameters[0].Expression;
}

return values.Aggregate(parameter, CreateMemberAccess);
}

private static Expression CreateMemberAccess(Expression exp, string prop)
{
var memberAccess = Expression.MakeMemberAccess(exp, TypeShim.GetProperty(exp.Type, prop));
return memberAccess;
}

/// <summary>
/// Returns the definitions for whitespace used within the language.
/// </summary>
Expand All @@ -463,10 +494,35 @@ protected virtual IEnumerable<GrammerDefinition> WhitespaceDefinitions()
{
return new[]
{
new GrammerDefinition(name: "WHITESPACE", regex: @"\s+", ignore: true)
new GrammerDefinition(name: "WHITESPACE", regex: @"\s+", ignore: true),
new GrammerDefinition(name: "SLASH", regex: @"\/(?=any|all)", ignore: true)
};
}

protected virtual IEnumerable<CollectionDefinition> CollectionDefinitions()
{
return new[]
{
new CollectionDefinition(
name: "ANY",
regex: @"any\(",
expressionBuilder: CreateCollectionAccess
),
new CollectionDefinition(
name: "ALL",
regex: @"all\(",
expressionBuilder: CreateCollectionAccess
)
};
}

private static Expression CreateCollectionAccess(string param, string source, ParameterExpression[] parameterExpressions)
{
//var expParam = Expression.Parameter(parameterExpressions[0], "sc");

return null;
}


/// <summary>
/// Wraps the function to convert any constants to enums if required
Expand Down
Loading