예제 #1
0
    /// <summary>
    /// Determines whether a node matches the current state of the tokenizer.
    /// </summary>
    /// <param name="node">The node.</param>
    /// <param name="tokenizer">The tokenizer.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>true if the node matches; otherwise, false.</returns>
    private bool IsNodeMatch(IChildNode node, TokenizingEnumerator tokenizer, TreeSearchOptions searchOptions)
    {
        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        if (tokenizer.Current.Type != TokenType.Value)
        {
            return(false);
        }

        if (tokenizer.Current.Value.Equals(node.Key, searchOptions.KeyComparison))
        {
            return(true);
        }

        foreach (var alias in node.Aliases)
        {
            if (tokenizer.Current.Value.Equals(alias, searchOptions.KeyComparison))
            {
                return(true);
            }
        }

        return(false);
    }
    /// <inheritdoc />
    public virtual bool Matches
    (
        TokenizingEnumerator tokenizer,
        out ulong consumedTokens,
        TreeSearchOptions?searchOptions = null
    )
    {
        consumedTokens = 0;

        // Eat at least one value token
        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        if (tokenizer.Current.Type is not Value)
        {
            return(false);
        }

        ulong consumedValueTokens = 1;

        while (tokenizer.MoveNext() && tokenizer.Current.Type is Value)
        {
            consumedValueTokens++;
        }

        consumedTokens = consumedValueTokens;
        return(true);
    }
예제 #3
0
    /// <summary>
    /// Performs a depth-first search of the given node.
    /// </summary>
    /// <param name="parentNode">The node.</param>
    /// <param name="tokenizer">The tokenizer.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>The matching nodes.</returns>
    private IEnumerable <BoundCommandNode> Search
    (
        IParentNode parentNode,
        TokenizingEnumerator tokenizer,
        TreeSearchOptions searchOptions
    )
    {
        var boundCommandNodes = new List <BoundCommandNode>();

        foreach (var child in parentNode.Children)
        {
            if (!IsNodeMatch(child, tokenizer, searchOptions))
            {
                continue;
            }

            switch (child)
            {
            case CommandNode commandNode:
            {
                if (!commandNode.TryBind(tokenizer, out var boundCommandShape))
                {
                    continue;
                }

                boundCommandNodes.Add(boundCommandShape);
                break;
            }

            case IParentNode groupNode:
            {
                // Consume the token
                if (!tokenizer.MoveNext())
                {
                    // No more tokens, so we can't continue searching
                    return(boundCommandNodes);
                }

                var nestedResults = Search(groupNode, tokenizer, searchOptions);
                boundCommandNodes.AddRange(nestedResults);

                continue;
            }

            default:
            {
                throw new InvalidOperationException
                      (
                          "Unknown node type encountered; tree is invalid and the search cannot continue."
                      );
            }
            }
        }

        return(boundCommandNodes);
    }
예제 #4
0
    /// <summary>
    /// Searches the command tree for a command that matches the shape of the given command string.
    /// </summary>
    /// <param name="commandString">The raw command string.</param>
    /// <param name="tokenizerOptions">The tokenizer options to use.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>A search result which may or may not have succeeded.</returns>
    public IEnumerable <BoundCommandNode> Search
    (
        ReadOnlySpan <char> commandString,
        TokenizerOptions?tokenizerOptions = null,
        TreeSearchOptions?searchOptions   = null
    )
    {
        tokenizerOptions ??= new TokenizerOptions();
        searchOptions ??= new TreeSearchOptions();

        var tokenizer = new TokenizingEnumerator(commandString, tokenizerOptions);

        return(Search(this.Root, tokenizer, searchOptions));
    }
예제 #5
0
    /// <inheritdoc />
    public virtual bool Matches
    (
        TokenizingEnumerator tokenizer,
        out ulong consumedTokens,
        TreeSearchOptions?searchOptions = null
    )
    {
        consumedTokens = 0;

        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        if (tokenizer.Current.Type != Value)
        {
            return(false);
        }

        consumedTokens = 1;
        return(true);
    }
예제 #6
0
    /// <inheritdoc />
    public override bool Matches
    (
        TokenizingEnumerator tokenizer,
        out ulong consumedTokens,
        TreeSearchOptions?searchOptions = null
    )
    {
        consumedTokens = 0;

        ulong itemCount = 0;

        while (this.Max is null || itemCount < this.Max.Value)
        {
            if (!tokenizer.MoveNext())
            {
                break;
            }

            if (tokenizer.Current.Type != Value)
            {
                break;
            }

            ++itemCount;
        }

        if (this.Min is not null)
        {
            if (itemCount < this.Min.Value)
            {
                return(false);
            }
        }

        consumedTokens = itemCount;
        return(true);
    }
예제 #7
0
    /// <summary>
    /// Attempts to bind the command shape to the given tokenizer, materializing values for its parameters.
    /// </summary>
    /// <param name="tokenizer">The token sequence.</param>
    /// <param name="boundCommandShape">The resulting shape, if any.</param>
    /// <param name="searchOptions">A set of search options.</param>
    /// <returns>true if the shape matches; otherwise, false.</returns>
    public bool TryBind
    (
        TokenizingEnumerator tokenizer,
        [NotNullWhen(true)] out BoundCommandNode?boundCommandShape,
        TreeSearchOptions?searchOptions = null
    )
    {
        searchOptions ??= new TreeSearchOptions();
        boundCommandShape = null;

        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        var parametersToCheck = new List <IParameterShape>(this.Shape.Parameters);

        var boundParameters = new List <BoundParameterShape>();

        while (parametersToCheck.Count > 0)
        {
            var matchedParameters = new List <IParameterShape>();
            foreach (var parameterToCheck in parametersToCheck)
            {
                if (!parameterToCheck.Matches(tokenizer, out var consumedTokens, searchOptions))
                {
                    continue;
                }

                // gobble up the tokens
                matchedParameters.Add(parameterToCheck);

                var boundTokens = new List <string>();
                for (ulong i = 0; i < consumedTokens; ++i)
                {
                    if (!tokenizer.MoveNext())
                    {
                        return(false);
                    }

                    var type = tokenizer.Current.Type;
                    if (type != TokenType.Value)
                    {
                        // skip names, we've already checked them
                        continue;
                    }

                    var value = tokenizer.Current.Value.ToString();
                    boundTokens.Add(value);
                }

                boundParameters.Add(new BoundParameterShape(parameterToCheck, boundTokens));
            }

            if (matchedParameters.Count == 0)
            {
                // Check if all remaining parameters are optional
                if (!parametersToCheck.All(p => p.IsOmissible(searchOptions)))
                {
                    return(false);
                }

                boundCommandShape = new BoundCommandNode(this, boundParameters);
                return(true);
            }

            foreach (var matchedParameter in matchedParameters)
            {
                parametersToCheck.Remove(matchedParameter);
            }
        }

        // if there are more tokens to come, we don't match
        if (tokenizer.MoveNext())
        {
            return(false);
        }

        boundCommandShape = new BoundCommandNode(this, boundParameters);
        return(true);
    }
예제 #8
0
    /// <inheritdoc/>
    public override bool Matches
    (
        TokenizingEnumerator tokenizer,
        out ulong consumedTokens,
        TreeSearchOptions?searchOptions = null
    )
    {
        searchOptions ??= new TreeSearchOptions();
        consumedTokens = 0;

        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        switch (tokenizer.Current.Type)
        {
        case TokenType.LongName:
        {
            if (this.LongName is null)
            {
                return(false);
            }

            if (!tokenizer.Current.Value.Equals(this.LongName, searchOptions.KeyComparison))
            {
                return(false);
            }

            break;
        }

        case TokenType.ShortName:
        {
            if (this.ShortName is null)
            {
                return(false);
            }

            if (tokenizer.Current.Value.Length != 1)
            {
                return(false);
            }

            if (tokenizer.Current.Value[0] != this.ShortName.Value)
            {
                return(false);
            }

            break;
        }

        case TokenType.Value:
        {
            return(false);
        }

        default:
        {
            throw new ArgumentOutOfRangeException();
        }
        }

        ulong itemCount = 0;

        while (this.Max is null || itemCount < this.Max.Value)
        {
            if (!tokenizer.MoveNext())
            {
                break;
            }

            if (tokenizer.Current.Type != TokenType.Value)
            {
                break;
            }

            ++itemCount;
        }

        if (this.Min is not null)
        {
            if (itemCount < this.Min.Value)
            {
                return(false);
            }
        }

        consumedTokens = itemCount + 1;
        return(true);
    }
예제 #9
0
    /// <inheritdoc/>
    public virtual bool Matches
    (
        TokenizingEnumerator tokenizer,
        out ulong consumedTokens,
        TreeSearchOptions?searchOptions = null
    )
    {
        searchOptions ??= new TreeSearchOptions();
        consumedTokens = 0;

        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        switch (tokenizer.Current.Type)
        {
        case TokenType.LongName:
        {
            if (this.LongName is null)
            {
                return(false);
            }

            if (!tokenizer.Current.Value.Equals(this.LongName, searchOptions.KeyComparison))
            {
                return(false);
            }

            break;
        }

        case TokenType.ShortName:
        {
            if (this.ShortName is null)
            {
                return(false);
            }

            if (tokenizer.Current.Value.Length != 1)
            {
                return(false);
            }

            if (tokenizer.Current.Value[0] != this.ShortName.Value)
            {
                return(false);
            }

            break;
        }

        case TokenType.Value:
        {
            return(false);
        }

        default:
        {
            throw new ArgumentOutOfRangeException();
        }
        }

        // Eat at least one value token
        if (!tokenizer.MoveNext())
        {
            return(false);
        }

        if (tokenizer.Current.Type != TokenType.Value)
        {
            return(false);
        }

        ulong consumedValueTokens = 1;

        while (tokenizer.MoveNext() && tokenizer.Current.Type is TokenType.Value)
        {
            consumedValueTokens++;
        }

        consumedTokens = consumedValueTokens + 1;
        return(true);
    }