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

View File

@@ -9,21 +9,21 @@ namespace VAR.ExpressionEvaluator.Tests
public void ExpressionNumberNode__One() public void ExpressionNumberNode__One()
{ {
IExpressionNode node = new ExpressionNumberNode(1); IExpressionNode node = new ExpressionNumberNode(1);
Assert.AreEqual(1m, node.Eval()); Assert.AreEqual(1m, node.Eval(null));
} }
[TestMethod()] [TestMethod()]
public void ExpressionNumberNode__Two() public void ExpressionNumberNode__Two()
{ {
IExpressionNode node = new ExpressionNumberNode(2); IExpressionNode node = new ExpressionNumberNode(2);
Assert.AreEqual(2m, node.Eval()); Assert.AreEqual(2m, node.Eval(null));
} }
[TestMethod()] [TestMethod()]
public void ExpressionNumberNode__OneHundredDotFortyFive() public void ExpressionNumberNode__OneHundredDotFortyFive()
{ {
IExpressionNode node = new ExpressionNumberNode(100.45m); 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() public void ExpressionNumberNode__Hello()
{ {
IExpressionNode node = new ExpressionStringNode("Hello"); IExpressionNode node = new ExpressionStringNode("Hello");
Assert.AreEqual("Hello", node.Eval()); Assert.AreEqual("Hello", node.Eval(null));
} }
[TestMethod()] [TestMethod()]
public void ExpressionNumberNode__World() public void ExpressionNumberNode__World()
{ {
IExpressionNode node = new ExpressionStringNode("World"); IExpressionNode node = new ExpressionStringNode("World");
Assert.AreEqual("World", node.Eval()); Assert.AreEqual("World", node.Eval(null));
} }
[TestMethod()] [TestMethod()]
public void ExpressionNumberNode__Hello_World() public void ExpressionNumberNode__Hello_World()
{ {
IExpressionNode node = new ExpressionStringNode("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 Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace VAR.ExpressionEvaluator.Tests namespace VAR.ExpressionEvaluator.Tests
{ {
@@ -145,5 +146,48 @@ namespace VAR.ExpressionEvaluator.Tests
#endregion Multiplication and division #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)); var t = new Tokenizer(new StringReader(testString));
// "null" // "null"
Assert.AreEqual(t.Token, Token.Keyword); Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "null"); Assert.AreEqual(t.Text, "null");
t.NextToken(); t.NextToken();
// "true" // "true"
Assert.AreEqual(t.Token, Token.Keyword); Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "true"); Assert.AreEqual(t.Text, "true");
t.NextToken(); t.NextToken();
// "false" // "false"
Assert.AreEqual(t.Token, Token.Keyword); Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "false"); Assert.AreEqual(t.Text, "false");
t.NextToken(); t.NextToken();
@@ -168,7 +168,7 @@ namespace VAR.ExpressionEvaluator.Tests
t.NextToken(); t.NextToken();
// "false" // "false"
Assert.AreEqual(t.Token, Token.Keyword); Assert.AreEqual(t.Token, Token.Identifier);
Assert.AreEqual(t.Text, "false"); Assert.AreEqual(t.Text, "false");
t.NextToken(); 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; _operation = operation;
} }
public object Eval() public object Eval(IEvaluationContext evaluationContext)
{ {
object leftValue = _leftNode.Eval(); object leftValue = _leftNode.Eval(evaluationContext);
object rightValue = _rightNode.Eval(); object rightValue = _rightNode.Eval(evaluationContext);
object result = _operation(leftValue, rightValue); object result = _operation(leftValue, rightValue);
return result; 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; _number = number;
} }
public object Eval() public object Eval(IEvaluationContext evaluationContext)
{ {
return _number; return _number;
} }

View File

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

View File

@@ -13,9 +13,9 @@ namespace VAR.ExpressionEvaluator
_operation = operation; _operation = operation;
} }
public object Eval() public object Eval(IEvaluationContext evaluationContext)
{ {
object value = _node.Eval(); object value = _node.Eval(evaluationContext);
object result = _operation(value); object result = _operation(value);
return result; 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 public interface IExpressionNode
{ {
object Eval(); object Eval(IEvaluationContext evaluationContext);
} }
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
namespace VAR.ExpressionEvaluator namespace VAR.ExpressionEvaluator
@@ -101,7 +102,7 @@ namespace VAR.ExpressionEvaluator
if (_tokenizer.Token == Token.ParenthesisStart) if (_tokenizer.Token == Token.ParenthesisStart)
{ {
_tokenizer.NextToken(); _tokenizer.NextToken();
var node = ParsePlusAndMinus(); IExpressionNode node = ParsePlusAndMinus();
if (_tokenizer.Token != Token.ParenthesisEnd) if (_tokenizer.Token != Token.ParenthesisEnd)
{ {
throw new Exception("Missing close parenthesis"); throw new Exception("Missing close parenthesis");
@@ -110,6 +111,40 @@ namespace VAR.ExpressionEvaluator
return node; 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())); throw new Exception(string.Format("Unexpected token: {0}", _tokenizer.Token.ToString()));
} }
@@ -121,10 +156,10 @@ namespace VAR.ExpressionEvaluator
return parser.ParseExpression(); return parser.ParseExpression();
} }
public static object EvaluateString(string str) public static object EvaluateString(string str, IEvaluationContext evaluationContext = null)
{ {
IExpressionNode node = ParseString(str); IExpressionNode node = ParseString(str);
return node.Eval(); return node.Eval(evaluationContext);
} }
} }
} }

View File

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

View File

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

View File

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