Parser: Variables and functions.

This commit is contained in:
2019-12-01 18:44:10 +01:00
parent 0fe4e54bcb
commit b82500492f
18 changed files with 219 additions and 29 deletions

View File

@@ -14,7 +14,7 @@ namespace VAR.ExpressionEvaluator.Tests
operation: (a, b) => (decimal)a + (decimal)b
);
var result = expr.Eval();
var result = expr.Eval(null);
Assert.AreEqual(30m, result);
}
@@ -28,7 +28,7 @@ namespace VAR.ExpressionEvaluator.Tests
operation: (a, b) => (decimal)a - (decimal)b
);
var result = expr.Eval();
var result = expr.Eval(null);
Assert.AreEqual(-10m, result);
}
@@ -42,7 +42,7 @@ namespace VAR.ExpressionEvaluator.Tests
operation: (a, b) => (decimal)a * (decimal)b
);
var result = expr.Eval();
var result = expr.Eval(null);
Assert.AreEqual(200m, result);
}
@@ -56,7 +56,7 @@ namespace VAR.ExpressionEvaluator.Tests
operation: (a, b) => (decimal)a / (decimal)b
);
var result = expr.Eval();
var result = expr.Eval(null);
Assert.AreEqual(2m, result);
}

View File

@@ -9,21 +9,21 @@ namespace VAR.ExpressionEvaluator.Tests
public void ExpressionNumberNode__One()
{
IExpressionNode node = new ExpressionNumberNode(1);
Assert.AreEqual(1m, node.Eval());
Assert.AreEqual(1m, node.Eval(null));
}
[TestMethod()]
public void ExpressionNumberNode__Two()
{
IExpressionNode node = new ExpressionNumberNode(2);
Assert.AreEqual(2m, node.Eval());
Assert.AreEqual(2m, node.Eval(null));
}
[TestMethod()]
public void ExpressionNumberNode__OneHundredDotFortyFive()
{
IExpressionNode node = new ExpressionNumberNode(100.45m);
Assert.AreEqual(100.45m, node.Eval());
Assert.AreEqual(100.45m, node.Eval(null));
}
}
}

View File

@@ -9,22 +9,21 @@ namespace VAR.ExpressionEvaluator.Tests
public void ExpressionNumberNode__Hello()
{
IExpressionNode node = new ExpressionStringNode("Hello");
Assert.AreEqual("Hello", node.Eval());
Assert.AreEqual("Hello", node.Eval(null));
}
[TestMethod()]
public void ExpressionNumberNode__World()
{
IExpressionNode node = new ExpressionStringNode("World");
Assert.AreEqual("World", node.Eval());
Assert.AreEqual("World", node.Eval(null));
}
[TestMethod()]
public void ExpressionNumberNode__Hello_World()
{
IExpressionNode node = new ExpressionStringNode("Hello World");
Assert.AreEqual("Hello World", node.Eval());
}
Assert.AreEqual("Hello World", node.Eval(null));
}
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace VAR.ExpressionEvaluator.Tests
{
@@ -145,5 +146,48 @@ namespace VAR.ExpressionEvaluator.Tests
#endregion Multiplication and division
#region Variables
[TestMethod()]
public void ParseString__Var1PlusVar2()
{
EvaluationContext evaluationContex = new EvaluationContext();
evaluationContex.SetVariable("v1", 1m);
evaluationContex.SetVariable("v2", 1m);
string expression = "v1 + v2";
object result = Parser.EvaluateString(expression, evaluationContex);
Assert.AreEqual(2m, result);
}
[TestMethod()]
public void ParseString__Var1MultiplyVar2()
{
EvaluationContext evaluationContex = new EvaluationContext();
evaluationContex.SetVariable("v1", 10m);
evaluationContex.SetVariable("v2", 5m);
string expression = "v1 * v2";
object result = Parser.EvaluateString(expression, evaluationContex);
Assert.AreEqual(50m, result);
}
#endregion Variables
#region Funcitions
[TestMethod()]
public void ParseString__MaxFunction()
{
EvaluationContext evaluationContex = new EvaluationContext();
evaluationContex.SetFunction("max", (parameters) =>
{
return parameters.Max(p => (decimal)p);
});
string expression = "max(1,2,10,5)";
object result = Parser.EvaluateString(expression, evaluationContex);
Assert.AreEqual(10m, result);
}
#endregion Functions
}
}

View File

@@ -96,17 +96,17 @@ namespace VAR.ExpressionEvaluator.Tests
var t = new Tokenizer(new StringReader(testString));
// "null"
Assert.AreEqual(t.Token, Token.Keyword);
Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "null");
t.NextToken();
// "true"
Assert.AreEqual(t.Token, Token.Keyword);
Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "true");
t.NextToken();
// "false"
Assert.AreEqual(t.Token, Token.Keyword);
Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "false");
t.NextToken();
@@ -168,7 +168,7 @@ namespace VAR.ExpressionEvaluator.Tests
t.NextToken();
// "false"
Assert.AreEqual(t.Token, Token.Keyword);
Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "false");
t.NextToken();

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
namespace VAR.ExpressionEvaluator
{
public class EvaluationContext : IEvaluationContext
{
private Dictionary<string, Func<object[], object>> _functions = new Dictionary<string, Func<object[], object>>();
private Dictionary<string, object> _variables = new Dictionary<string, object>();
public void CleanFunctions()
{
_functions.Clear();
}
public void SetFunction(string name, Func<object[], object> function)
{
_functions.Add(name, function);
}
public Func<object[], object> GetFunction(string name)
{
return _functions[name];
}
public void CleanVariables()
{
_variables.Clear();
}
public void SetVariable(string name, object value)
{
_variables.Add(name, value);
}
public object GetVariable(string name)
{
return _variables[name];
}
public void Clean()
{
CleanFunctions();
CleanVariables();
}
}
}

View File

@@ -15,10 +15,10 @@ namespace VAR.ExpressionEvaluator
_operation = operation;
}
public object Eval()
public object Eval(IEvaluationContext evaluationContext)
{
object leftValue = _leftNode.Eval();
object rightValue = _rightNode.Eval();
object leftValue = _leftNode.Eval(evaluationContext);
object rightValue = _rightNode.Eval(evaluationContext);
object result = _operation(leftValue, rightValue);
return result;

View File

@@ -0,0 +1,28 @@
using System;
using System.Linq;
namespace VAR.ExpressionEvaluator
{
public class ExpressionFunctionNode : IExpressionNode
{
private readonly string _name;
private IExpressionNode[] _paramNodes;
public ExpressionFunctionNode(string name, IExpressionNode[] paramNodes)
{
_name = name;
_paramNodes = paramNodes;
}
public object Eval(IEvaluationContext evaluationContext)
{
object[] paramValues = _paramNodes.Select(p => p.Eval(evaluationContext)).ToArray();
Func<object[], object> func = evaluationContext.GetFunction(_name);
object result = func(paramValues);
return result;
}
}
}

View File

@@ -9,7 +9,7 @@
_number = number;
}
public object Eval()
public object Eval(IEvaluationContext evaluationContext)
{
return _number;
}

View File

@@ -9,7 +9,7 @@
_string = str;
}
public object Eval()
public object Eval(IEvaluationContext evaluationContext)
{
return _string;
}

View File

@@ -13,9 +13,9 @@ namespace VAR.ExpressionEvaluator
_operation = operation;
}
public object Eval()
public object Eval(IEvaluationContext evaluationContext)
{
object value = _node.Eval();
object value = _node.Eval(evaluationContext);
object result = _operation(value);
return result;

View File

@@ -0,0 +1,17 @@
namespace VAR.ExpressionEvaluator
{
public class ExpressionVariableNode : IExpressionNode
{
private readonly string _name;
public ExpressionVariableNode(string name)
{
_name = name;
}
public object Eval(IEvaluationContext evaluationContext)
{
return evaluationContext.GetVariable(_name);
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace VAR.ExpressionEvaluator
{
public interface IEvaluationContext
{
object GetVariable(string name);
Func<object[], object> GetFunction(string name);
}
}

View File

@@ -2,6 +2,6 @@
{
public interface IExpressionNode
{
object Eval();
object Eval(IEvaluationContext evaluationContext);
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace VAR.ExpressionEvaluator
@@ -101,7 +102,7 @@ namespace VAR.ExpressionEvaluator
if (_tokenizer.Token == Token.ParenthesisStart)
{
_tokenizer.NextToken();
var node = ParsePlusAndMinus();
IExpressionNode node = ParsePlusAndMinus();
if (_tokenizer.Token != Token.ParenthesisEnd)
{
throw new Exception("Missing close parenthesis");
@@ -110,6 +111,40 @@ namespace VAR.ExpressionEvaluator
return node;
}
if (_tokenizer.Token == Token.Identifier)
{
string identifier = _tokenizer.Text;
_tokenizer.NextToken();
if (_tokenizer.Token != Token.ParenthesisStart)
{
IExpressionNode node = new ExpressionVariableNode(identifier);
return node;
}
else
{
_tokenizer.NextToken();
var parameters = new List<IExpressionNode>();
while (true)
{
parameters.Add(ParsePlusAndMinus());
if (_tokenizer.Token == Token.Comma)
{
_tokenizer.NextToken();
continue;
}
break;
}
if (_tokenizer.Token != Token.ParenthesisEnd)
{
throw new Exception("Missing close parenthesis");
}
_tokenizer.NextToken();
IExpressionNode node = new ExpressionFunctionNode(identifier, parameters.ToArray());
return node;
}
}
throw new Exception(string.Format("Unexpected token: {0}", _tokenizer.Token.ToString()));
}
@@ -121,10 +156,10 @@ namespace VAR.ExpressionEvaluator
return parser.ParseExpression();
}
public static object EvaluateString(string str)
public static object EvaluateString(string str, IEvaluationContext evaluationContext = null)
{
IExpressionNode node = ParseString(str);
return node.Eval();
return node.Eval(evaluationContext);
}
}
}

View File

@@ -15,7 +15,8 @@
LessOrEqualThan,
ParenthesisStart,
ParenthesisEnd,
Keyword,
Comma,
Identifier,
String,
Number,
}

View File

@@ -134,9 +134,14 @@ namespace VAR.ExpressionEvaluator
_currentToken = Token.LessOrEqualThan;
}
return;
case ',':
NextChar();
_currentToken = Token.Comma;
return;
}
// Keywords
// Identifier
if (char.IsLetter(_currentChar))
{
var sb = new StringBuilder();
@@ -147,7 +152,7 @@ namespace VAR.ExpressionEvaluator
if (_currentChar == '\0') { break; }
}
_text = sb.ToString();
_currentToken = Token.Keyword;
_currentToken = Token.Identifier;
return;
}

View File

@@ -41,7 +41,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="EvaluationContext.cs" />
<Compile Include="ExpressionDivisionNode.cs" />
<Compile Include="ExpressionFunctionNode.cs" />
<Compile Include="ExpressionMultiplyNode.cs" />
<Compile Include="ExpressionUnaryNode.cs" />
<Compile Include="ExpressionBinaryNode.cs" />
@@ -50,6 +52,8 @@
<Compile Include="ExpressionPlusNode.cs" />
<Compile Include="ExpressionStringNode.cs" />
<Compile Include="ExpressionNumberNode.cs" />
<Compile Include="ExpressionVariableNode.cs" />
<Compile Include="IEvaluationContext.cs" />
<Compile Include="IExpressionNode.cs" />
<Compile Include="ITokenizer.cs" />
<Compile Include="Parser.cs" />