From b82500492f0677fee49cf8884ef369de9728c71e Mon Sep 17 00:00:00 2001 From: "Valeriano A.R" Date: Sun, 1 Dec 2019 18:44:10 +0100 Subject: [PATCH] Parser: Variables and functions. --- .../ExpressionBinaryNodeTests.cs | 8 ++-- .../ExpressionNumberNodeTests.cs | 6 +-- .../ExpressionStringNodeTests.cs | 7 ++- VAR.ExpressionEvaluator.Tests/ParserTests.cs | 44 +++++++++++++++++ .../TokenizerTests.cs | 8 ++-- VAR.ExpressionEvaluator/EvaluationContext.cs | 47 +++++++++++++++++++ .../ExpressionBinaryNode.cs | 6 +-- .../ExpressionFunctionNode.cs | 28 +++++++++++ .../ExpressionNumberNode.cs | 2 +- .../ExpressionStringNode.cs | 2 +- .../ExpressionUnaryNode.cs | 4 +- .../ExpressionVariableNode.cs | 17 +++++++ VAR.ExpressionEvaluator/IEvaluationContext.cs | 10 ++++ VAR.ExpressionEvaluator/IExpressionNode.cs | 2 +- VAR.ExpressionEvaluator/Parser.cs | 41 ++++++++++++++-- VAR.ExpressionEvaluator/Token.cs | 3 +- VAR.ExpressionEvaluator/Tokenizer.cs | 9 +++- .../VAR.ExpressionEvaluator.csproj | 4 ++ 18 files changed, 219 insertions(+), 29 deletions(-) create mode 100644 VAR.ExpressionEvaluator/EvaluationContext.cs create mode 100644 VAR.ExpressionEvaluator/ExpressionFunctionNode.cs create mode 100644 VAR.ExpressionEvaluator/ExpressionVariableNode.cs create mode 100644 VAR.ExpressionEvaluator/IEvaluationContext.cs diff --git a/VAR.ExpressionEvaluator.Tests/ExpressionBinaryNodeTests.cs b/VAR.ExpressionEvaluator.Tests/ExpressionBinaryNodeTests.cs index b6f259e..cdd8cac 100644 --- a/VAR.ExpressionEvaluator.Tests/ExpressionBinaryNodeTests.cs +++ b/VAR.ExpressionEvaluator.Tests/ExpressionBinaryNodeTests.cs @@ -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); } diff --git a/VAR.ExpressionEvaluator.Tests/ExpressionNumberNodeTests.cs b/VAR.ExpressionEvaluator.Tests/ExpressionNumberNodeTests.cs index 930a740..303dc36 100644 --- a/VAR.ExpressionEvaluator.Tests/ExpressionNumberNodeTests.cs +++ b/VAR.ExpressionEvaluator.Tests/ExpressionNumberNodeTests.cs @@ -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)); } } } \ No newline at end of file diff --git a/VAR.ExpressionEvaluator.Tests/ExpressionStringNodeTests.cs b/VAR.ExpressionEvaluator.Tests/ExpressionStringNodeTests.cs index fc2c5e2..d3cca8b 100644 --- a/VAR.ExpressionEvaluator.Tests/ExpressionStringNodeTests.cs +++ b/VAR.ExpressionEvaluator.Tests/ExpressionStringNodeTests.cs @@ -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)); } - } } \ No newline at end of file diff --git a/VAR.ExpressionEvaluator.Tests/ParserTests.cs b/VAR.ExpressionEvaluator.Tests/ParserTests.cs index eaa747f..a267c71 100644 --- a/VAR.ExpressionEvaluator.Tests/ParserTests.cs +++ b/VAR.ExpressionEvaluator.Tests/ParserTests.cs @@ -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 + } } \ No newline at end of file diff --git a/VAR.ExpressionEvaluator.Tests/TokenizerTests.cs b/VAR.ExpressionEvaluator.Tests/TokenizerTests.cs index 24224c3..0127905 100644 --- a/VAR.ExpressionEvaluator.Tests/TokenizerTests.cs +++ b/VAR.ExpressionEvaluator.Tests/TokenizerTests.cs @@ -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(); diff --git a/VAR.ExpressionEvaluator/EvaluationContext.cs b/VAR.ExpressionEvaluator/EvaluationContext.cs new file mode 100644 index 0000000..1a9a4b5 --- /dev/null +++ b/VAR.ExpressionEvaluator/EvaluationContext.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace VAR.ExpressionEvaluator +{ + public class EvaluationContext : IEvaluationContext + { + private Dictionary> _functions = new Dictionary>(); + private Dictionary _variables = new Dictionary(); + + public void CleanFunctions() + { + _functions.Clear(); + } + + public void SetFunction(string name, Func function) + { + _functions.Add(name, function); + } + + public Func 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(); + } + } +} diff --git a/VAR.ExpressionEvaluator/ExpressionBinaryNode.cs b/VAR.ExpressionEvaluator/ExpressionBinaryNode.cs index e6fd432..5b0efa8 100644 --- a/VAR.ExpressionEvaluator/ExpressionBinaryNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionBinaryNode.cs @@ -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; diff --git a/VAR.ExpressionEvaluator/ExpressionFunctionNode.cs b/VAR.ExpressionEvaluator/ExpressionFunctionNode.cs new file mode 100644 index 0000000..bd08821 --- /dev/null +++ b/VAR.ExpressionEvaluator/ExpressionFunctionNode.cs @@ -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 func = evaluationContext.GetFunction(_name); + + object result = func(paramValues); + + return result; + } + } +} diff --git a/VAR.ExpressionEvaluator/ExpressionNumberNode.cs b/VAR.ExpressionEvaluator/ExpressionNumberNode.cs index 804f4e3..2e9e797 100644 --- a/VAR.ExpressionEvaluator/ExpressionNumberNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionNumberNode.cs @@ -9,7 +9,7 @@ _number = number; } - public object Eval() + public object Eval(IEvaluationContext evaluationContext) { return _number; } diff --git a/VAR.ExpressionEvaluator/ExpressionStringNode.cs b/VAR.ExpressionEvaluator/ExpressionStringNode.cs index 6553c6e..9a7821c 100644 --- a/VAR.ExpressionEvaluator/ExpressionStringNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionStringNode.cs @@ -9,7 +9,7 @@ _string = str; } - public object Eval() + public object Eval(IEvaluationContext evaluationContext) { return _string; } diff --git a/VAR.ExpressionEvaluator/ExpressionUnaryNode.cs b/VAR.ExpressionEvaluator/ExpressionUnaryNode.cs index e975ec0..c4017a9 100644 --- a/VAR.ExpressionEvaluator/ExpressionUnaryNode.cs +++ b/VAR.ExpressionEvaluator/ExpressionUnaryNode.cs @@ -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; diff --git a/VAR.ExpressionEvaluator/ExpressionVariableNode.cs b/VAR.ExpressionEvaluator/ExpressionVariableNode.cs new file mode 100644 index 0000000..a582a7e --- /dev/null +++ b/VAR.ExpressionEvaluator/ExpressionVariableNode.cs @@ -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); + } + } +} diff --git a/VAR.ExpressionEvaluator/IEvaluationContext.cs b/VAR.ExpressionEvaluator/IEvaluationContext.cs new file mode 100644 index 0000000..377b7b7 --- /dev/null +++ b/VAR.ExpressionEvaluator/IEvaluationContext.cs @@ -0,0 +1,10 @@ +using System; + +namespace VAR.ExpressionEvaluator +{ + public interface IEvaluationContext + { + object GetVariable(string name); + Func GetFunction(string name); + } +} diff --git a/VAR.ExpressionEvaluator/IExpressionNode.cs b/VAR.ExpressionEvaluator/IExpressionNode.cs index 6b30ac4..afbae11 100644 --- a/VAR.ExpressionEvaluator/IExpressionNode.cs +++ b/VAR.ExpressionEvaluator/IExpressionNode.cs @@ -2,6 +2,6 @@ { public interface IExpressionNode { - object Eval(); + object Eval(IEvaluationContext evaluationContext); } } diff --git a/VAR.ExpressionEvaluator/Parser.cs b/VAR.ExpressionEvaluator/Parser.cs index dbf6f4b..b7adf36 100644 --- a/VAR.ExpressionEvaluator/Parser.cs +++ b/VAR.ExpressionEvaluator/Parser.cs @@ -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(); + 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); } } } diff --git a/VAR.ExpressionEvaluator/Token.cs b/VAR.ExpressionEvaluator/Token.cs index 0f58508..8e51910 100644 --- a/VAR.ExpressionEvaluator/Token.cs +++ b/VAR.ExpressionEvaluator/Token.cs @@ -15,7 +15,8 @@ LessOrEqualThan, ParenthesisStart, ParenthesisEnd, - Keyword, + Comma, + Identifier, String, Number, } diff --git a/VAR.ExpressionEvaluator/Tokenizer.cs b/VAR.ExpressionEvaluator/Tokenizer.cs index 8f942c3..a2506b1 100644 --- a/VAR.ExpressionEvaluator/Tokenizer.cs +++ b/VAR.ExpressionEvaluator/Tokenizer.cs @@ -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; } diff --git a/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj b/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj index 95f1893..1722541 100644 --- a/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj +++ b/VAR.ExpressionEvaluator/VAR.ExpressionEvaluator.csproj @@ -41,7 +41,9 @@ + + @@ -50,6 +52,8 @@ + +