コード例 #1
0
        private static void ProcessOperators(Stack <LicenseExpressionToken> operatorStack, Stack <Tuple <bool, object> > operandStack)
        {
            var op           = operatorStack.Pop();
            var rightOperand = PopIfNotEmpty(operandStack);
            var leftOperand  = PopIfNotEmpty(operandStack);

            if (op.TokenType == LicenseTokenType.WITH)
            {
                if (!(rightOperand.Item1 == leftOperand.Item1 == true))
                {
                    throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_InvalidExpression));
                }
                var right = rightOperand.Item2 as LicenseExpressionToken;
                var left  = leftOperand.Item2 as LicenseExpressionToken;

                var withNode = new WithOperator(NuGetLicense.ParseIdentifier(left.Value), NuGetLicenseException.ParseIdentifier(right.Value));

                operandStack.Push(new Tuple <bool, object>(false, withNode));
            }
            else
            {
                var logicalOperator = op.TokenType == LicenseTokenType.AND ? LogicalOperatorType.And : LogicalOperatorType.Or;

                var right = rightOperand.Item1 ?
                            NuGetLicense.ParseIdentifier(((LicenseExpressionToken)rightOperand.Item2).Value) :
                            (NuGetLicenseExpression)rightOperand.Item2;

                var left = leftOperand.Item1 ?
                           NuGetLicense.ParseIdentifier(((LicenseExpressionToken)leftOperand.Item2).Value) :
                           (NuGetLicenseExpression)leftOperand.Item2;

                var newExpression = new LogicalOperator(logicalOperator, left, right);
                operandStack.Push(new Tuple <bool, object>(false, newExpression));
            }
        }
コード例 #2
0
        /// <summary>
        /// Parses a License Expression if valid.
        /// The expression would be parsed correct, even if non-standard exceptions are encountered. The non-standard Licenses/Exceptions have metadata on them with which the caller can make decisions.
        /// Based on the Shunting Yard algorithm. <see href="https://en.wikipedia.org/wiki/Shunting-yard_algorithm"/>
        /// This method first creates an postfix expression by separating the operators and operands.
        /// Later the postfix expression is evaluated into an object model that represents the expression. Note that brackets are dropped in this conversion and this is not round-trippable.
        /// The token precedence helps make sure that the expression is a valid infix one.
        /// </summary>
        /// <param name="expression">The expression to be parsed.</param>
        /// <returns>Parsed NuGet License Expression model.</returns>
        /// <exception cref="NuGetLicenseExpressionParsingException">If the expression is empty or null.</exception>
        /// <exception cref="NuGetLicenseExpressionParsingException">If the expression has invalid characters</exception>
        /// <exception cref="NuGetLicenseExpressionParsingException">If the expression itself is invalid. Example: MIT OR OR Apache-2.0, or the MIT or Apache-2.0, because the expressions are case sensitive.</exception>
        /// <exception cref="NuGetLicenseExpressionParsingException">If the expression's brackets are mismatched.</exception>
        /// <exception cref="NuGetLicenseExpressionParsingException">If the licenseIdentifier is deprecated.</exception>
        /// <exception cref="NuGetLicenseExpressionParsingException">If the exception identifier is deprecated.</exception>
        internal static NuGetLicenseExpression Parse(string expression)
        {
            try
            {
                var tokens        = GetTokens(expression);
                var operatorStack = new Stack <LicenseExpressionToken>();
                // The operand stack can contain both unprocessed value and complex expressions such as MIT OR Apache-2.0.
                // Complex expressions are valid operands for the logical operators. The first value represents whether it's value or an expression.
                // true => LicenseExpressionToken, false => NuGetLicenseExpression
                var operandStack = new Stack <Tuple <bool, object> >();

                var lastTokenType = LicenseTokenType.IDENTIFIER;
                var firstPass     = true;

                foreach (var token in tokens)
                {
                    var currentTokenType = token.TokenType;
                    switch (token.TokenType)
                    {
                    case LicenseTokenType.IDENTIFIER:
                        if (!firstPass && !token.TokenType.IsValidPrecedingToken(lastTokenType))
                        {
                            throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_InvalidToken, token.Value));
                        }
                        // Add it to the operandstack. Only add it to the expression when you meet an operator
                        operandStack.Push(new Tuple <bool, object>(true, token));
                        break;

                    case LicenseTokenType.OPENING_BRACKET:
                        if (!firstPass && !token.TokenType.IsValidPrecedingToken(lastTokenType))
                        {
                            throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_InvalidToken, token.Value));
                        }
                        operatorStack.Push(token);
                        break;

                    case LicenseTokenType.CLOSING_BRACKET:
                        if (firstPass || !token.TokenType.IsValidPrecedingToken(lastTokenType))
                        {
                            throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_InvalidToken, token.Value));
                        }

                        // pop until we hit the opening bracket
                        while (operatorStack.Count > 0 && operatorStack.Peek().TokenType != LicenseTokenType.OPENING_BRACKET)
                        {
                            ProcessOperators(operatorStack, operandStack);
                        }

                        if (operatorStack.Count > 0)
                        {
                            // pop the bracket
                            operatorStack.Pop();
                        }
                        else
                        {
                            throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_MismatchedParentheses));
                        }
                        break;

                    case LicenseTokenType.WITH:
                    case LicenseTokenType.AND:
                    case LicenseTokenType.OR:
                        if (firstPass && !token.TokenType.IsValidPrecedingToken(lastTokenType))
                        {
                            throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_InvalidToken, token.Value));
                        }
                        if (operatorStack.Count == 0 ||                                           // The operator stack is empty
                            operatorStack.Peek().TokenType == LicenseTokenType.OPENING_BRACKET || // The last token is an opening bracket (treat it the same as empty
                            token.TokenType < operatorStack.Peek().TokenType)                     // An operator that has higher priority than the operator on the stack
                        {
                            operatorStack.Push(token);
                        }
                        // An operator that has lower/same priority than the operator on the stack
                        else if (token.TokenType >= operatorStack.Peek().TokenType)
                        {
                            ProcessOperators(operatorStack, operandStack);
                            operatorStack.Push(token);
                        }
                        break;

                    default:
                        throw new NuGetLicenseExpressionParsingException("Should not happen. File a bug with repro steps on NuGet/Home if seen.");
                    }
                    lastTokenType = currentTokenType;
                    firstPass     = false;
                }

                while (operatorStack.Count > 0)
                {
                    if (operatorStack.Peek().TokenType != LicenseTokenType.OPENING_BRACKET)
                    {
                        ProcessOperators(operatorStack, operandStack);
                    }
                    else
                    {
                        throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_MismatchedParentheses));
                    }
                }

                // This handles the no operators scenario. This check could be simpler, but it's dangerous to assume all scenarios have been handled by the above logic.
                // As written and as tested, you would never have more than 1 operand on the stack

                if (operandStack.Count != 1)
                {
                    throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_InvalidExpression));
                }
                else
                {
                    var value = operandStack.Pop();

                    return(value.Item1 ? NuGetLicense.ParseIdentifier(((LicenseExpressionToken)value.Item2).Value, allowUnlicensed: true) : (NuGetLicenseExpression)value.Item2);
                }
            }
            catch (NuGetLicenseExpressionParsingException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new NuGetLicenseExpressionParsingException(e.Message, e);
            }
        }