Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
EnumExpressionEvaluator.cs645 linesDownload Raw Back to engine
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Diagnostics;6using System.Globalization;7using System.Reflection;8using System.Text;9 10using Dbg = System.Management.Automation;11 12namespace System.Management.Automation13{14    /// <summary>15    /// Takes as input a collection of strings and builds an expression tree from the input.16    /// At the evaluation stage, it walks down the tree and evaluates the result.17    /// </summary>18    public sealed class FlagsExpression<T> where T : struct, IConvertible19    {20        #region Constructors21 22        /// <summary>23        /// Construct the expression from a single string.24        /// </summary>25        /// <param name="expression">26        /// The specified flag attribute expression string.27        /// </param>28        public FlagsExpression(string expression)29        {30            if (!typeof(T).IsEnum)31            {32                throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException),33                    null, "InvalidGenericType", EnumExpressionEvaluatorStrings.InvalidGenericType);34            }35 36            _underType = Enum.GetUnderlyingType(typeof(T));37 38            if (string.IsNullOrWhiteSpace(expression))39            {40                throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException),41                    null, "EmptyInputString", EnumExpressionEvaluatorStrings.EmptyInputString);42            }43 44            List<Token> tokenList = TokenizeInput(expression);45            // Append an OR at the end of the list for construction46            tokenList.Add(new Token(TokenKind.Or));47 48            CheckSyntaxError(tokenList);49 50            Root = ConstructExpressionTree(tokenList);51        }52 53        /// <summary>54        /// Construct the tree from an object collection when arguments are comma separated.55        /// If valid, all elements are OR separated.56        /// </summary>57        /// <param name="expression">58        /// The array of specified flag attribute subexpression strings.59        /// </param>60        public FlagsExpression(object[] expression)61        {62            if (!typeof(T).IsEnum)63            {64                throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException),65                    null, "InvalidGenericType", EnumExpressionEvaluatorStrings.InvalidGenericType);66            }67 68            _underType = Enum.GetUnderlyingType(typeof(T));69 70            if (expression == null)71            {72                throw InterpreterError.NewInterpreterException(null, typeof(ArgumentNullException),73                    null, "EmptyInputString", EnumExpressionEvaluatorStrings.EmptyInputString);74            }75 76            foreach (string inputClause in expression)77            {78                if (string.IsNullOrWhiteSpace(inputClause))79                {80                    throw InterpreterError.NewInterpreterException(expression, typeof(RuntimeException),81                        null, "EmptyInputString", EnumExpressionEvaluatorStrings.EmptyInputString);82                }83            }84 85            List<Token> tokenList = new List<Token>();86 87            foreach (string orClause in expression)88            {89                tokenList.AddRange(TokenizeInput(orClause));90                tokenList.Add(new Token(TokenKind.Or));91            }92            // Unnecessary OR at the end not removed for tree construction93 94            Debug.Assert(tokenList.Count > 0, "Input must not all be white characters.");95 96            CheckSyntaxError(tokenList);97 98            Root = ConstructExpressionTree(tokenList);99        }100 101        #endregion102 103        #region parser tokens104 105        internal enum TokenKind106        {107            Identifier,108            And,109            Or,110            Not111        }112 113        internal class Token114        {115            public string Text { get; set; }116 117            public TokenKind Kind { get; set; }118 119            internal Token(TokenKind kind)120            {121                Kind = kind;122                switch (kind)123                {124                    case TokenKind.Or:125                        Text = "OR";126                        break;127                    case TokenKind.And:128                        Text = "AND";129                        break;130                    case TokenKind.Not:131                        Text = "NOT";132                        break;133                    default:134                        Debug.Fail("Invalid token kind passed in.");135                        break;136                }137            }138 139            internal Token(string identifier)140            {141                Kind = TokenKind.Identifier;142                Text = identifier;143            }144        }145 146        #endregion147 148        #region tree nodes149 150        /// <summary>151        /// Abstract base type for other types of nodes in the tree.152        /// </summary>153        internal abstract class Node154        {155            // Only used in internal nodes holding operators.156 157            public Node Operand1 { get; set; }158 159            internal abstract bool Eval(object val);160 161            internal abstract bool ExistEnum(object enumVal);162        }163 164        /// <summary>165        /// OR node for attributes separated by a comma.166        /// </summary>167        internal class OrNode : Node168        {169            public Node Operand2 { get; set; }170 171            public OrNode(Node n)172            {173                Operand2 = n;174            }175 176            internal override bool Eval(object val)177            {178                // bitwise OR179                bool satisfy = Operand1.Eval(val) || Operand2.Eval(val);180                return satisfy;181            }182 183            internal override bool ExistEnum(object enumVal)184            {185                bool exist = Operand1.ExistEnum(enumVal) || Operand2.ExistEnum(enumVal);186                return exist;187            }188        }189 190        /// <summary>191        /// AND node for attributes separated by a plus(+) operator.192        /// </summary>193        internal class AndNode : Node194        {195            public Node Operand2 { get; set; }196 197            public AndNode(Node n)198            {199                Operand2 = n;200            }201 202            internal override bool Eval(object val)203            {204                // bitwise AND205                bool satisfy = Operand1.Eval(val) && Operand2.Eval(val);206                return satisfy;207            }208 209            internal override bool ExistEnum(object enumVal)210            {211                bool exist = Operand1.ExistEnum(enumVal) || Operand2.ExistEnum(enumVal);212                return exist;213            }214        }215 216        /// <summary>217        /// NOT node for attribute preceded by an exclamation(!) operator.218        /// </summary>219        internal class NotNode : Node220        {221            internal override bool Eval(object val)222            {223                // bitwise NOT224                bool satisfy = !(Operand1.Eval(val));225                return satisfy;226            }227 228            internal override bool ExistEnum(object enumVal)229            {230                bool exist = Operand1.ExistEnum(enumVal);231                return exist;232            }233        }234 235        /// <summary>236        /// Leaf nodes of the expression tree.237        /// </summary>238        internal class OperandNode : Node239        {240            internal object _operandValue;241 242            public object OperandValue243            {244                get245                {246                    return _operandValue;247                }248 249                set250                {251                    _operandValue = value;252                }253            }254 255            /// <summary>256            /// Takes a string value and converts to corresponding enum value.257            /// The string value should be checked at parsing stage prior to258            /// tree construction to ensure it is valid.259            /// </summary>260            internal OperandNode(string enumString)261            {262                Type enumType = typeof(T);263                Type underType = Enum.GetUnderlyingType(enumType);264                FieldInfo enumItem = enumType.GetField(enumString);265                _operandValue = LanguagePrimitives.ConvertTo(enumItem.GetValue(enumType), underType, CultureInfo.InvariantCulture);266            }267 268            internal override bool Eval(object val)269            {270                Type underType = Enum.GetUnderlyingType(typeof(T));271                // bitwise AND checking272                bool satisfy = false;273                if (isUnsigned(underType))274                {275                    ulong valueToCheck = (ulong)LanguagePrimitives.ConvertTo(val, typeof(ulong), CultureInfo.InvariantCulture);276                    ulong operandValue = (ulong)LanguagePrimitives.ConvertTo(_operandValue, typeof(ulong), CultureInfo.InvariantCulture);277                    satisfy = (operandValue == (valueToCheck & operandValue));278                }279                // allow for negative enum value input (though it's not recommended practice for flags attribute)280                else281                {282                    long valueToCheck = (long)LanguagePrimitives.ConvertTo(val, typeof(long), CultureInfo.InvariantCulture);283                    long operandValue = (long)LanguagePrimitives.ConvertTo(_operandValue, typeof(long), CultureInfo.InvariantCulture);284                    satisfy = (operandValue == (valueToCheck & operandValue));285                }286 287                return satisfy;288            }289 290            internal override bool ExistEnum(object enumVal)291            {292                Type underType = Enum.GetUnderlyingType(typeof(T));293                // bitwise AND checking294                bool exist = false;295                if (isUnsigned(underType))296                {297                    ulong valueToCheck = (ulong)LanguagePrimitives.ConvertTo(enumVal, typeof(ulong), CultureInfo.InvariantCulture);298                    ulong operandValue = (ulong)LanguagePrimitives.ConvertTo(_operandValue, typeof(ulong), CultureInfo.InvariantCulture);299                    exist = valueToCheck == (valueToCheck & operandValue);300                }301                // allow for negative enum value input (though it's not recommended practice for flags attribute)302                else303                {304                    long valueToCheck = (long)LanguagePrimitives.ConvertTo(enumVal, typeof(long), CultureInfo.InvariantCulture);305                    long operandValue = (long)LanguagePrimitives.ConvertTo(_operandValue, typeof(long), CultureInfo.InvariantCulture);306                    exist = valueToCheck == (valueToCheck & operandValue);307                }308 309                return exist;310            }311 312            private static bool isUnsigned(Type type)313            {314                return (type == typeof(ulong) || type == typeof(uint) || type == typeof(ushort) || type == typeof(byte));315            }316        }317 318        #endregion319 320        #region private members321 322        private readonly Type _underType = null;323 324        #endregion325 326        #region properties327 328        internal Node Root { get; set; } = null;329 330        #endregion331 332        #region public methods333 334        /// <summary>335        /// Evaluate a given flag enum value against the expression.336        /// </summary>337        /// <param name="value">338        /// The flag enum value to be evaluated.339        /// </param>340        /// <returns>341        /// Whether the enum value satisfy the expression.342        /// </returns>343        public bool Evaluate(T value)344        {345            object val = LanguagePrimitives.ConvertTo(value, _underType, CultureInfo.InvariantCulture);346            return Root.Eval(val);347        }348 349        #endregion350 351        #region internal methods352 353        /// <summary>354        /// Given an enum element, check if the element is present in the expression tree,355        /// which is also present in the input expression.356        /// </summary>357        /// <param name="flagName">358        /// The enum element to be examined.359        /// </param>360        /// <returns>361        /// Whether the enum element is present in the expression.362        /// </returns>363        /// <remarks>364        /// The enum value passed in should be a single enum element value,365        /// not a flag enum value with multiple bits set.366        /// </remarks>367        internal bool ExistsInExpression(T flagName)368        {369            bool exist = false;370            object val = LanguagePrimitives.ConvertTo(flagName, _underType, CultureInfo.InvariantCulture);371            exist = Root.ExistEnum(val);372            return exist;373        }374 375        #endregion376 377        #region parser methods378 379        /// <summary>380        /// Takes a string of input tokenize into a list of ordered tokens.381        /// </summary>382        /// <param name="input">383        /// The input argument string,384        /// could be partial input (one element from the argument collection).385        /// </param>386        /// <returns>387        /// A generic list of tokenized input.388        /// </returns>389        private static List<Token> TokenizeInput(string input)390        {391            List<Token> tokenList = new List<Token>();392            int _offset = 0;393 394            while (_offset < input.Length)395            {396                FindNextToken(input, ref _offset);397                if (_offset < input.Length)398                {399                    tokenList.Add(GetNextToken(input, ref _offset));400                }401            }402 403            return tokenList;404        }405 406        /// <summary>407        /// Find the start of the next token, skipping white spaces.408        /// </summary>409        /// <param name="input">410        /// Input string411        /// </param>412        /// <param name="_offset">413        /// Current offset position for the string parser.414        /// </param>415        private static void FindNextToken(string input, ref int _offset)416        {417            while (_offset < input.Length)418            {419                char cc = input[_offset++];420                if (!char.IsWhiteSpace(cc))421                {422                    _offset--;423                    break;424                }425            }426        }427 428        /// <summary>429        /// Given the start (offset) of the next token, traverse through430        /// the string to find the next token, stripping correctly431        /// enclosed quotes.432        /// </summary>433        /// <param name="input">434        /// Input string435        /// </param>436        /// <param name="_offset">437        /// Current offset position for the string parser.438        /// </param>439        /// <returns>440        /// The next token on the input string441        /// </returns>442        private static Token GetNextToken(string input, ref int _offset)443        {444            StringBuilder sb = new StringBuilder();445            // bool singleQuoted = false;446            // bool doubleQuoted = false;447            bool readingIdentifier = false;448            while (_offset < input.Length)449            {450                char cc = input[_offset++];451                if ((cc == ',') || (cc == '+') || (cc == '!'))452                {453                    if (!readingIdentifier)454                    {455                        sb.Append(cc);456                    }457                    else458                    {459                        _offset--;460                    }461 462                    break;463                }464                else465                {466                    sb.Append(cc);467                    readingIdentifier = true;468                }469            }470 471            string result = sb.ToString().Trim();472            // If resulting identifier is enclosed in paired quotes,473            // remove the only the first pair of quotes from the string474            if (result.Length >= 2 &&475                ((result[0] == '\'' && result[result.Length - 1] == '\'') ||476                (result[0] == '\"' && result[result.Length - 1] == '\"')))477            {478                result = result.Substring(1, result.Length - 2);479            }480 481            result = result.Trim();482 483            // possible empty token because white spaces are enclosed in quotation marks.484            if (string.IsNullOrWhiteSpace(result))485            {486                throw InterpreterError.NewInterpreterException(input, typeof(RuntimeException),487                    null, "EmptyTokenString", EnumExpressionEvaluatorStrings.EmptyTokenString,488                    EnumMinimumDisambiguation.EnumAllValues(typeof(T)));489            }490            else if (result[0] == '(')491            {492                int matchIndex = input.IndexOf(')', _offset);493                if (result[result.Length - 1] == ')' || matchIndex >= 0)494                {495                    throw InterpreterError.NewInterpreterException(input, typeof(RuntimeException),496                        null, "NoIdentifierGroupingAllowed", EnumExpressionEvaluatorStrings.NoIdentifierGroupingAllowed);497                }498            }499 500            if (result.Equals(","))501            {502                return (new Token(TokenKind.Or));503            }504            else if (result.Equals("+"))505            {506                return (new Token(TokenKind.And));507            }508            else if (result.Equals("!"))509            {510                return (new Token(TokenKind.Not));511            }512            else513            {514                return (new Token(result));515            }516        }517 518        /// <summary>519        /// Checks syntax errors on input expression,520        /// as well as performing disambiguation for identifiers.521        /// </summary>522        /// <param name="tokenList">523        /// A list of tokenized input.524        /// </param>525        private static void CheckSyntaxError(List<Token> tokenList)526        {527            // Initialize, assuming preceded by OR528            TokenKind previous = TokenKind.Or;529 530            for (int i = 0; i < tokenList.Count; i++)531            {532                Token token = tokenList[i];533                // Not allowed: ... AND/OR AND/OR ...534                // Allowed: ... AND/OR NOT/ID ...535                if (previous == TokenKind.Or || previous == TokenKind.And)536                {537                    if ((token.Kind == TokenKind.Or) || (token.Kind == TokenKind.And))538                    {539                        throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),540                            null, "SyntaxErrorUnexpectedBinaryOperator", EnumExpressionEvaluatorStrings.SyntaxErrorUnexpectedBinaryOperator);541                    }542                }543                // Not allowed: ... NOT AND/OR/NOT ...544                // Allowed: ... NOT ID ...545                else if (previous == TokenKind.Not)546                {547                    if (token.Kind != TokenKind.Identifier)548                    {549                        throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),550                            null, "SyntaxErrorIdentifierExpected", EnumExpressionEvaluatorStrings.SyntaxErrorIdentifierExpected);551                    }552                }553                // Not allowed: ... ID NOT/ID ...554                // Allowed: ... ID AND/OR ...555                else if (previous == TokenKind.Identifier)556                {557                    if ((token.Kind == TokenKind.Identifier) || (token.Kind == TokenKind.Not))558                    {559                        throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),560                            null, "SyntaxErrorBinaryOperatorExpected", EnumExpressionEvaluatorStrings.SyntaxErrorBinaryOperatorExpected);561                    }562                }563 564                if (token.Kind == TokenKind.Identifier)565                {566                    string text = token.Text;567                    token.Text = EnumMinimumDisambiguation.EnumDisambiguate(text, typeof(T));568                }569 570                previous = token.Kind;571            }572        }573 574        /// <summary>575        /// Takes a list of tokenized input and create the corresponding expression tree.576        /// </summary>577        /// <param name="tokenList">578        /// Tokenized list of the input string.579        /// </param>580        private static Node ConstructExpressionTree(List<Token> tokenList)581        {582            bool notFlag = false;583            Queue<Node> andQueue = new Queue<Node>();584            Queue<Node> orQueue = new Queue<Node>();585 586            for (int i = 0; i < tokenList.Count; i++)587            {588                Token token = tokenList[i];589                TokenKind kind = token.Kind;590                if (kind == TokenKind.Identifier)591                {592                    Node idNode = new OperandNode(token.Text);593                    if (notFlag)    // identifier preceded by NOT594                    {595                        Node notNode = new NotNode();596                        notNode.Operand1 = idNode;597                        notFlag = false;598                        andQueue.Enqueue(notNode);599                    }600                    else601                    {602                        andQueue.Enqueue(idNode);603                    }604                }605                else if (kind == TokenKind.Not)606                {607                    notFlag = true;608                }609                else if (kind == TokenKind.And)610                {611                    // do nothing612                }613                else if (kind == TokenKind.Or)614                {615                    // Dequeue all nodes from AND queue,616                    // create the AND tree, then add to the OR queue.617                    Node andCurrent = andQueue.Dequeue();618                    while (andQueue.Count > 0)619                    {620                        Node andNode = new AndNode(andCurrent);621                        andNode.Operand1 = andQueue.Dequeue();622                        andCurrent = andNode;623                    }624 625                    orQueue.Enqueue(andCurrent);626                }627            }628 629            // Dequeue all nodes from OR queue,630            // create the OR tree (final expression tree)631            Node orCurrent = orQueue.Dequeue();632            while (orQueue.Count > 0)633            {634                Node orNode = new OrNode(orCurrent);635                orNode.Operand1 = orQueue.Dequeue();636                orCurrent = orNode;637            }638 639            return orCurrent;640        }641 642        #endregion643    }644}645