![]() |
VOOZH | about |
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" />
<PackageVersion Include="StirlingLabs.StringToExpression" Version="23.1.1" />Directory.Packages.props
<PackageReference Include="StirlingLabs.StringToExpression" />Project file
paket add StirlingLabs.StringToExpression --version 23.1.1
#r "nuget: StirlingLabs.StringToExpression, 23.1.1"
#:package StirlingLabs.StringToExpression@23.1.1
#addin nuget:?package=StirlingLabs.StringToExpression&version=23.1.1Install as a Cake Addin
#tool nuget:?package=StirlingLabs.StringToExpression&version=23.1.1Install as a Cake Tool
👁 Integration
👁 Codacy Badge
👁 GitHub release (latest SemVer)
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.
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());
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
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.
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.
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 simple ConstantExpression, or the root of a complicated Tree
of BinaryExpressionsOperator - 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 into Tokens. A Token knows the GrammarDefinition that created it
and the string value it represents.
Apply GrammarDefinitions - All GrammarDefinition has an void Apply(Token token, ParseState state) method. We
first read all the Tokens in sequentially and run each GrammarDefinition 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 more Operands and create
one Operand. This way by the time we apply all the operators we should only have a single Operand 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 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 is compatible. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 is compatible. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 is compatible. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.