StirlingLabs.StringToExpression
23.1.1
Prefix Reserved
dotnet add package StirlingLabs.StringToExpression --version 23.1.1
NuGet\Install-Package StirlingLabs.StringToExpression -Version 23.1.1
<PackageReference Include="StirlingLabs.StringToExpression" Version="23.1.1" />
paket add StirlingLabs.StringToExpression --version 23.1.1
#r "nuget: StirlingLabs.StringToExpression, 23.1.1"
// Install StirlingLabs.StringToExpression as a Cake Addin #addin nuget:?package=StirlingLabs.StringToExpression&version=23.1.1 // Install StirlingLabs.StringToExpression as a Cake Tool #tool nuget:?package=StirlingLabs.StringToExpression&version=23.1.1
This is a fork of Alex Davies' StringToExpression library.
StringToExpression allows you to create methods that take strings and outputs .NET expressions. It is highly configurable allowing you to define your own language with your own syntax.
Available via NuGet as StirlingLabs.StringToExpression
.
Arithmetic
A basic arithmetic language ArithmeticLanguage
is provided for performing algebra. It can be used as is, or extended as desired.
var language = new ArithmeticLanguage();
Expression<Func<decimal>> expressionFunction = language.Parse("(4 - 2) * 5 + 9 / 3");
Func<decimal> function = expressionFunction.compile();
Assert.Equal(13, function());
OData filter
ODataFilterLanguage
is provided as a lightweight way to parse OData filter expressions which are a nice way to pass generic filtering requirements into a WebAPI.
public async Task<IHttpActionResult> GetDoohickies([FromUri(Name = "$filter")] string filter = "name eq 'discount' and rating gt 18")
{
var language = new ODataFilterLanguage()
Expression<Func<Doohicky, bool>> predicate = language.Parse<Doohickey>(filter);
//can either pass this expression into either IQueryable or IEnumerable where clauses
return await DataContext.Doohickies.Where(predicte).ToListAsync();
}
StringToExpression
has the advantage of being configurable; if the OData parser doesnt support methods you want, (or
it supports methods you dont want) it is very easy to extend ODataFilterLanguage
and modify the configuration
Custom languages
Languages are defined by a set of GrammarDefintions
. These define both how the string is broken up into tokens as well
as the behaviour of each token. There are many subclasses of GrammarDefinition
that makes implementing standard
language features very easy.
An example of a very simple arithmetic language is as follows
ListDelimiterDefinition delimeter;
BracketOpenDefinition openBracket, sqrt;
language = new Language(new [] {
new OperandDefinition(
name:"DECIMAL",
regex: @"\-?\d+(\.\d+)?",
expressionBuilder: x => Expression.Constant(decimal.Parse(x))),
new BinaryOperatorDefinition(
name:"ADD",
regex: @"\+",
orderOfPrecedence: 2,
expressionBuilder: (left,right) => Expression.Add(left, right)),
new BinaryOperatorDefinition(
name:"SUB",
regex: @"\-",
orderOfPrecedence: 2,
expressionBuilder: (left,right) => Expression.Subtract(left, right)),
new BinaryOperatorDefinition(
name:"MUL",
regex: @"\*",
orderOfPrecedence: 1, //multiply should be done before add/subtract
expressionBuilder: (left,right) => Expression.Multiply(left, right)),
new BinaryOperatorDefinition(
name:"DIV",
regex: @"\/",
orderOfPrecedence: 1, //division should be done before add/subtract
expressionBuilder: (left,right) => Expression.Divide(left, right)),
sqrt = new FunctionCallDefinition(
name:"FN_SQRT",
regex: @"sqrt\(",
argumentTypes: new[] {typeof(double) },
expressionBuilder: (parameters) => {
return Expression.Call(
null,
method:typeof(Math).GetMethod("Sqrt"),
arguments: new [] { parameters[0] });
}),
openBracket = new BracketOpenDefinition(
name: "OPEN_BRACKET",
regex: @"\("),
delimeter = new ListDelimiterDefinition(
name: "COMMA",
regex: ","),
new BracketCloseDefinition(
name: "CLOSE_BRACKET",
regex: @"\)",
bracketOpenDefinitions: new[] { openBracket, sqrt },
listDelimeterDefinition: delimeter)
new GrammarDefinition(name: "WHITESPACE", regex: @"\s+", ignore: true) //we dont want to process whitespace
});
Some of the out of the box grammar definitions are detailed below
Name | Description | Properties |
---|---|---|
GrammarDefintion |
Base class for all definitions. Does not perform any functionality during the parsing. | name - A name for this rule.<br />regex - the regular expression that will match for this token. |
OperandDefinition |
Defines the smallest atomic piece in your language, used to represent items like numbers or strings. | expressionBuilder - a function that when given the string matched from the regex it produces a .NET expression (usually an ConstantExpression ). |
BinaryOperatorDefintion |
An operation that takes parameters from the left and right of it. Often represents arithmetic operaitons (+ , - , * , / ) or equality checks (== , != , < > ) or boolean logic (and , or ). |
orderOfPrecedence - determines when this function should run, lower numbers get run before higher numbers (allowing defining BEDMAS rules).<br />expressionBuilder - function that takes in two Expression (the left and right of the operator), and outputs a new expression combining them. |
UnaryOperator |
An operation that takes a single parameter, used for operations such as not . |
orderOfPrecedence - determines when this function should run, lower numbers get run before higher numbers.<br />parameterPosition - whether the operand is to the left or right of the operator. |
BracketOpenDefinition |
Defines an open bracket, functionally does not do much unless paired with a BracketCloseDefinition . |
|
ListDelimeterDefinition |
The separator to use to denote lists within brackets (a , in most languages) functionally does not do much unless paired with a BracketCloseDefinition . |
|
BracketCloseDefinition |
The expression between the brackets is evaluated first. | bracketOpenDefinitions - list of definitions that would be treated as a start to the bracketing.<br />listDelimiterDefinition - definition of the ListDelimeterDefinition . |
FunctionCallDefinition |
Defines a function that takes in a list of operands. Also acts as a bracket BracketOpenDefinition definition. |
argumentTypes - list of types that define the types expected and the number of arguments expecte.<br />expressionBuilder - takes an array of expressions and output a single expression. |
If your language is more complicated than the provided GrammarDefinitions
you are able to define your own by
extending GrammarDefintion
. You best read the Nuts and bolts section to determine the best way to
implement your definition.
Error handling
All parsing exceptions extend ParseException
. A ParseException
will contain both a readable message and
a StringSegment
that represents what token(s) in the original input string caused the error.
The StringSegment
allows pinpointing of issues such as where operands are missing, which function has too many
parameters or where the unexpected character is. It provides a useful feedback for wherever you are getting your
original strings from.
Nuts and bolts
Under the hood StringToExpression
implements a shunting-yard algorithm.
The internal parsing state contains a Stack<Operand>
and a Stack<Operator>
that are built up during the parsing
Operand
- Represent a .NET expression. This may be a simpleConstantExpression
, or the root of a complicated Tree ofBinaryExpressions
Operator
- Is a function that can be run. Generally these functions when run will consume one or more operands and produce one operand. Such that run operators reduces the number of operands on the stack.
The parsing is done in roughly three steps
Tokenize - The string is parsed through a tokenizer which uses the regular expressions defined in the
GrammarDefinitions
to break the strings intoTokens
. AToken
knows theGrammarDefinition
that created it and the string value it represents.Apply
GrammarDefinitions
- AllGrammarDefinition
has anvoid Apply(Token token, ParseState state)
method. We first read all theTokens
in sequentially and run eachGrammarDefinition
Apply
method. The apply method can make any modifications to the state it wants, this can range from something simple like pushing an Operand on to the stack, to something more complicated like executing operands.Execute Operators -Once all the tokens are Applied we will start poping Operators off the stack and executing them. When an
Operator
executes its generally expected that it will consume one or moreOperands
and create oneOperand
. This way by the time we apply all the operators we should only have a singleOperand
on the stack, that is our result.
To customize you can make your own GrammarDefinition
and implment the Apply
method to meet your purposes.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- FastExpressionCompiler.LightExpression (>= 3.3.3)
- Microsoft.Bcl.HashCode (>= 1.1.1)
- System.Memory (>= 4.5.5)
- System.Runtime.CompilerServices.Unsafe (>= 6.0.0)
-
.NETStandard 2.1
- FastExpressionCompiler.LightExpression (>= 3.3.3)
- Microsoft.Bcl.HashCode (>= 1.1.1)
- System.Runtime.CompilerServices.Unsafe (>= 6.0.0)
-
net6.0
- FastExpressionCompiler.LightExpression (>= 3.3.3)
-
net7.0
- FastExpressionCompiler.LightExpression (>= 3.3.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.