/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }