ParserLibrary 1.0.6
See the version list below for details.
dotnet add package ParserLibrary --version 1.0.6
NuGet\Install-Package ParserLibrary -Version 1.0.6
<PackageReference Include="ParserLibrary" Version="1.0.6" />
paket add ParserLibrary --version 1.0.6
#r "nuget: ParserLibrary, 1.0.6"
// Install ParserLibrary as a Cake Addin #addin nuget:?package=ParserLibrary&version=1.0.6 // Install ParserLibrary as a Cake Tool #tool nuget:?package=ParserLibrary&version=1.0.6
ParserLibrary
No Other Expression Parser, Ever
How it began
I wanted to write my "custom terminal" that used interactive commands with expressions. Other expression builders used only "numbers" as basic entities which I did not want; this is something too common. I wanted some variables to represent "musical notes" or "musical chords", or even "vectors" and "matrices" and some other to represent numbers. The only way, was to build an Expression builder that could allow custom types. Obviously, the default capability of handling numerical values was needed as a start. Let's speed up with the some examples.
The library is based on dependency injection concepts and can be highly customized.
There are 2 main classes: the Tokenizer
and the Parser
. Both of them are base classes and adapt to the corresponding interfaces ITokenizer
and IParser
. Let's uncover all the potential by giving examples with incremental adding functionality.
Examples
Using the DefaultParser
//This is a simple expression, which uses variables and literals of type double, and the DefaultParser.
double result = (double)App.Evaluate( "-5.0+2*a", new() { { "a", 5.0 } });
Console.WriteLine(result); //5
//2 variables example (spaces are obviously ignored)
double result2 = (double)App.Evaluate("-a + 500 * b + 2^3", new() { { "a", 5 }, { "b", 1 } });
Console.WriteLine(result2); //503
The first example is the same with the example below: the second way uses explicitly the DefaultParser
, which can be later overriden in order to use a custom Parser.
//The example below uses explicitly the DefaultParser.
var app = App.GetParserApp<DefaultParser>();
var parser = app.Services.GetParser();
double result = (double)parser.Evaluate("-5.0+2*a", new() { { "a", 5.0 } });
Let's use some functions already defined in the DefaultParser
double result3 = (double)App.Evaluate("cosd(phi)^2+sind(phi)^2", new() { { "phi", 45 } });
Console.WriteLine(result3); // 1.0000000000000002
Adding new functions to the DefaultParser
That was the boring stuff, let's start adding some custom functionality. Let's add a custom function add3
that takes 3 arguments. For this purpose, we create a new subclass of DefaultParser
. Note that we can add custom logging via dependency injection (some more examples will follow on this). For the moment, ignore the constructor. We assume that the add3
functions sums its 3 arguments with a specific weight.
private class SimpleFunctionParser : DefaultParser
{
public SimpleFunctionParser(ILogger<Parser> logger, ITokenizer tokenizer, IOptions<TokenizerOptions> options) : base(logger, tokenizer, options)
{
}
protected override object EvaluateFunction(Node<Token> functionNode, Dictionary<Node<Token>, object> nodeValueDictionary)
{
double[] a = GetDoubleFunctionArguments(functionNode, nodeValueDictionary);
return functionNode.Text.ToLower() switch
{
"add3" => a[0] + 2 * a[1] + 3 * a[2],
//for all other functions use the base class stuff (DefaultParser)
_ => base.EvaluateFunction(functionNode, nodeValueDictionary)
};
}
}
Let's use our first customized Parser
:
var parser = App.GetCustomParser<SimpleFunctionParser>();
double result = (double)parser.Evaluate("8 + add3(5.0,g,3.0)", new() { { "g", 3 } }); // will return 8 + (5 + 2 * 3 + 3 * 3.0) i.e -> 28
Using custom types
Let's assume that we have a class named Item
, which we want to interact with integer numbers and with other Item
objects:
public class Item
{
public string Name { get; set; }
public int Value { get; set; } = 0;
//we define a custom operator for the type to simplify the evaluateoperator example later
//this is not 100% needed, but it keeps the code in the CustomTypeParser simpler
public static Item operator +(int v1, Item v2) =>
new Item { Name = v2.Name , Value = v2.Value + v1 };
public static Item operator +(Item v2, int v1) =>
new Item { Name = v2.Name, Value = v2.Value + v1 };
public static Item operator +(Item v1, Item v2) =>
new Item { Name = $"{v1.Name} {v2.Name}", Value = v2.Value + v1.Value };
public override string ToString() => $"{Name} {Value}";
}
A custom parser that uses custom types should derive from the Parser
class. Because the Parser
class does not assume any type in advance, we should override the EvaluateLiteral
function which is used to parse the integer numbers in the string, In the following example we define the +
operator, which can take an Item
object or an int
for its operands. We also define the add
function, which assumes that the first argument is an Item
and the second argument is an int
. In practice, the Function syntax is usually stricter regarding the type of the arguments, so it is easier to write its implementation:
public class CustomTypeParser : Parser
{
public CustomTypeParser(ILogger<Parser> logger, ITokenizer tokenizer, IOptions<TokenizerOptions> options) : base(logger, tokenizer, options)
{ }
//we assume that literals are integer numbers only
protected override object EvaluateLiteral(string s) => int.Parse(s);
protected override object EvaluateOperator(Node<Token> operatorNode, Dictionary<Node<Token>, object> nodeValueDictionary)
{
(object LeftOperand, object RightOperand) = operatorNode.GetBinaryArguments(nodeValueDictionary);
//we assume the + operator
if (operatorNode.Text == "+")
{
//we manage all combinations of Item/Item, Item/int, int/Item combinations here
if (LeftOperand is Item && RightOperand is Item)
return (Item)LeftOperand + (Item)RightOperand;
return LeftOperand is Item ? (Item)LeftOperand + (int)RightOperand : (int)LeftOperand + (Item)RightOperand;
}
return base.EvaluateOperator(operatorNode, nodeValueDictionary);
}
protected override object EvaluateFunction(Node<Token> functionNode, Dictionary<Node<Token>, object> nodeValueDictionary)
{
var a = functionNode.GetFunctionArguments(nodeValueDictionary);
return functionNode.Text switch
{
"add" => (Item)a[0] + (int)a[1],
_ => base.EvaluateFunction(functionNode, nodeValueDictionary)
};
}
}
Now we can use the CustomTypeParser
for parsing our custom expression:
var parser = App.GetCustomParser<CustomTypeParser>();
Item result = (Item)parser.Evaluate("a + add(b,4) + 5",
new() {
{"a", new Item { Name="foo", Value = 3} },
{"b", new Item { Name="bar"} }
});
Console.WriteLine(result); // foo bar 12
more examples and documentation to follow soon...
The DefaultParser
Parser
All Parsers use parenthesis pairs ((
, )
) to override opertors priority. The priority of the operators is internally defined in the DefaultParser
. A custom Parser
can override the default operator priority and use other than the common operators using an external appsettings.json
file, which will be analyzed in later examples.
The DefaultParser
class for the moment accepts the followig operators:
+
: plus sign (unary) and plus (binary)-
: negative sign (unary) and minus (binary)*
: multiplication/
: division^
: power
and the following functions:
abs(x)
: Absolute valueacos(x)
: Inverse cosine (in radians)acosd(x)
: Inverse cosine (in degrees)acosh(x)
: Inverse hyperbolic cosineasin(x)
: Inverse sine (in radians)asind(x)
: Inverse sine (in degrees)asinh(x)
: Inverse hyperbolic sineatan(x)
: Inverse tangent (in radians)atand(x)
: Inverse tangent (in degrees)atan2(y,x)
: Inverse tangent (in radians)atan2d(y,x)
: Inverse tangent (in degrees)atanh(x)
: Inverse hyperbolic tangentcbrt(x)
: Cube rootcos(x)
: Cosine (x in radians)cosd(x)
: Cosine (x in degrees)cosh(x)
: Hyperbolic cosineexp(x)
: Exponential function (e^x)log(x)
/ ln(x): Natural logarithmlog10(x)
: Base 10 logarithmlog2(x)
: Base 2 logarithmlogn(x,n)
: Base n logarithmmax(x,y)
: Maximummin(x,y)
: Minimumpow(x,y)
: Power function (x^y)round(x,y)
: Round to y decimal digitssin(x)
: Sine (x in radians)sind(x)
: Sine (x in degrees)sinh(x)
: Hyperbolic sinesqr(x)
/sqrt(x)
: Square roottan(x)
: Tangent (x in radians)tand(x)
: Tangent (x in degrees)tanh(x)
: Hyperbolic tangent
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | 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 was computed. 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. |
-
net6.0
- Microsoft.Extensions.Hosting (>= 6.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 6.0.0)
- Serilog.AspNetCore (>= 5.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated | |
---|---|---|---|
1.4.12 | 122 | 3/1/2024 | |
1.4.11 | 167 | 6/26/2023 | |
1.4.10 | 125 | 6/26/2023 | |
1.4.9 | 124 | 6/25/2023 | |
1.4.8 | 129 | 6/23/2023 | |
1.4.7 | 117 | 6/19/2023 | |
1.4.6 | 133 | 6/16/2023 | |
1.4.5 | 373 | 8/20/2022 | |
1.4.4 | 366 | 8/19/2022 | |
1.4.3 | 396 | 8/5/2022 | |
1.4.0 | 377 | 8/2/2022 | |
1.3.3 | 382 | 7/25/2022 | |
1.3.2 | 371 | 7/24/2022 | |
1.3.1 | 390 | 7/18/2022 | |
1.2.1 | 403 | 7/17/2022 | |
1.1.1 | 403 | 7/17/2022 | |
1.1.0 | 411 | 7/17/2022 | |
1.0.15 | 422 | 7/17/2022 | |
1.0.12 | 492 | 7/17/2022 | |
1.0.11 | 450 | 7/17/2022 | |
1.0.10 | 440 | 7/17/2022 | |
1.0.9 | 445 | 7/17/2022 | |
1.0.8 | 453 | 7/17/2022 | |
1.0.7 | 444 | 7/17/2022 | |
1.0.6 | 504 | 7/16/2022 | |
1.0.5 | 527 | 7/15/2022 | |
1.0.4 | 522 | 7/11/2022 | |
1.0.3 | 551 | 7/11/2022 | |
1.0.2 | 524 | 7/11/2022 | |
1.0.1 | 522 | 7/10/2022 | |
1.0.0 | 473 | 7/10/2022 |