//****************************************************************************************************** // BooleanExpression.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 02/12/2016 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using GSF.Collections; namespace GSF.Parsing { /// /// Represents a boolean expression that can be parsed and executed at runtime. /// /// /// Binary operators have the same level of precedence and are evaluated from right to left. /// public class BooleanExpression { #region [ Members ] // Nested Types /// /// Represents a variable that can be tweaked at runtime. /// public class Variable { /// /// The identifier used to refer to the variable. /// public readonly string Identifier; /// /// The value of the variable. /// public bool Value; /// /// Creates a new instance of the class. /// /// The identifier used to refer to the variable. public Variable(string identifier) { Identifier = identifier; } } // Fields private Dictionary m_variables; private Func m_evaluate; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// /// The expression text to be parsed as a boolean expression. /// /// The default comparer for identifiers is . /// public BooleanExpression(string expressionText) : this(expressionText, StringComparer.OrdinalIgnoreCase) { } /// /// Creates a new instance of the class. /// /// The expression text to be parsed as a boolean expression. /// Comparer used to compare identifiers. public BooleanExpression(string expressionText, IEqualityComparer identifierComparer) { StringBuilder builder; Expression expression; m_variables = new Dictionary(StringComparer.OrdinalIgnoreCase); builder = new StringBuilder(expressionText); expression = ParseExpression(builder); if (builder.Length > 0) throw new FormatException($"Unexpected character '{builder[0]}' in expression. Expected end of expression."); m_evaluate = Expression.Lambda>(expression).Compile(); } #endregion #region [ Properties ] /// /// Gets the list of variables found while parsing the boolean expression. /// public List Variables { get { return m_variables.Values.ToList(); } } /// /// Gets the variable identified by the given identifier. /// /// The identifier used to refer to the variable. /// The variable identified by the given identifier. public Variable this[string identifier] { get { return m_variables[identifier]; } } #endregion #region [ Methods ] /// /// Evaluates the expression using the /// current values of the variables. /// /// The result of the evalulation. public bool Evaluate() { return m_evaluate(); } /// /// Attempts to get the variable identified by the given identifier. /// /// The identifier used to refer to the variable. /// The variable identified by the given identifier. /// True if the variable is present in the expression; false otherwise. public bool TryGetVariable(string identifier, out Variable variable) { return m_variables.TryGetValue(identifier, out variable); } private Expression ParseExpression(StringBuilder builder) { Expression subexpression; char binaryOp; subexpression = ParseSubexpression(builder); ShedWhitespace(builder); if (builder.Length > 0) { binaryOp = builder[0]; if (binaryOp == ')') return subexpression; builder.Remove(0, 1); ShedWhitespace(builder); switch (binaryOp) { case '&': return Expression.And(subexpression, ParseExpression(builder)); case '|': return Expression.Or(subexpression, ParseExpression(builder)); case '^': return Expression.ExclusiveOr(subexpression, ParseExpression(builder)); default: throw new FormatException($"Unexpected character '{binaryOp}' in expression. Expected: '&', '|', or '^'."); } } return subexpression; } private Expression ParseSubexpression(StringBuilder builder) { Expression expression; ShedWhitespace(builder); if (builder.Length == 0) throw new FormatException($"Unexpected end of expression. Expected: '(', '!', '~', or identifier."); switch (builder[0]) { case '(': builder.Remove(0, 1); expression = ParseExpression(builder); if (builder.Length == 0) throw new FormatException($"Unexpected end of expression. Expected: ')'."); if (builder[0] != ')') throw new FormatException($"Unexpected character '{builder[0]}' in expression. Expected: ')'."); builder.Remove(0, 1); return expression; case '!': case '~': builder.Remove(0, 1); return Expression.Not(ParseSubexpression(builder)); } return ParseIdentifier(builder); } private Expression ParseIdentifier(StringBuilder builder) { Variable identifier; StringBuilder nameBuilder = new StringBuilder(); string name; while (builder.Length > 0 && char.IsLetterOrDigit(builder[0])) { nameBuilder.Append(builder[0]); builder.Remove(0, 1); } if (nameBuilder.Length == 0) throw new FormatException($"Unexpected character '{builder[0]}' in expression. Expected identifier."); name = nameBuilder.ToString(); identifier = m_variables.GetOrAdd(name, key => new Variable(key)); return ((Expression>)(() => identifier.Value)).Body; } private void ShedWhitespace(StringBuilder builder) { while (builder.Length > 0 && char.IsWhiteSpace(builder[0])) builder.Remove(0, 1); } #endregion } }