diff --git a/src/System.CommandLine/Parsing/CommandArgumentNode.cs b/src/System.CommandLine/Parsing/CommandArgumentNode.cs deleted file mode 100644 index 7c21746ff5..0000000000 --- a/src/System.CommandLine/Parsing/CommandArgumentNode.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Diagnostics; - -namespace System.CommandLine.Parsing -{ - internal sealed class CommandArgumentNode : SyntaxNode - { - public CommandArgumentNode( - Token token, - Argument argument, - CommandNode parent) : base(token) - { - Debug.Assert(token.Type == TokenType.Argument, $"Incorrect token type: {token}"); - - Argument = argument; - ParentCommandNode = parent; - } - - public Argument Argument { get; } - - public CommandNode ParentCommandNode { get; } - } -} diff --git a/src/System.CommandLine/Parsing/CommandNode.cs b/src/System.CommandLine/Parsing/CommandNode.cs deleted file mode 100644 index 63eeb9b589..0000000000 --- a/src/System.CommandLine/Parsing/CommandNode.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Parsing -{ - internal sealed class CommandNode : NonterminalSyntaxNode - { - public CommandNode( - Token token, - Command command) : base(token) - { - Command = command; - } - - public Command Command { get; } - } -} diff --git a/src/System.CommandLine/Parsing/DirectiveNode.cs b/src/System.CommandLine/Parsing/DirectiveNode.cs deleted file mode 100644 index c747212378..0000000000 --- a/src/System.CommandLine/Parsing/DirectiveNode.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Diagnostics; - -namespace System.CommandLine.Parsing -{ - internal sealed class DirectiveNode : SyntaxNode - { - public DirectiveNode( - Token token, - string name, - string? value) : base(token) - { - Debug.Assert(token.Type == TokenType.Directive, $"Incorrect token type: {token}"); - - Name = name; - Value = value; - } - - public string Name { get; } - - public string? Value { get; } - } -} diff --git a/src/System.CommandLine/Parsing/NonterminalSyntaxNode.cs b/src/System.CommandLine/Parsing/NonterminalSyntaxNode.cs deleted file mode 100644 index dcf9dd833d..0000000000 --- a/src/System.CommandLine/Parsing/NonterminalSyntaxNode.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; - -namespace System.CommandLine.Parsing -{ - internal abstract class NonterminalSyntaxNode : SyntaxNode - { - private List? _children; - - protected NonterminalSyntaxNode(Token token) : base(token) - { - } - - public IReadOnlyList? Children => _children; - - internal void AddChildNode(SyntaxNode node) => (_children ??= new()).Add(node); - } -} diff --git a/src/System.CommandLine/Parsing/OptionArgumentNode.cs b/src/System.CommandLine/Parsing/OptionArgumentNode.cs deleted file mode 100644 index f9452cd514..0000000000 --- a/src/System.CommandLine/Parsing/OptionArgumentNode.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Diagnostics; - -namespace System.CommandLine.Parsing -{ - internal sealed class OptionArgumentNode : SyntaxNode - { - public OptionArgumentNode( - Token token, - Argument argument, - OptionNode parent) : base(token) - { - Debug.Assert(token.Type == TokenType.Argument, $"Incorrect token type: {token}"); - - Argument = argument; - ParentOptionNode = parent; - } - - public Argument Argument { get; } - - public OptionNode ParentOptionNode { get; } - } -} diff --git a/src/System.CommandLine/Parsing/OptionNode.cs b/src/System.CommandLine/Parsing/OptionNode.cs deleted file mode 100644 index 25ca29023b..0000000000 --- a/src/System.CommandLine/Parsing/OptionNode.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Parsing -{ - internal sealed class OptionNode : NonterminalSyntaxNode - { - public OptionNode( - Token token, - Option option) : base(token) - { - Option = option; - } - - public Option Option { get; } - } -} \ No newline at end of file diff --git a/src/System.CommandLine/Parsing/ParseOperation.cs b/src/System.CommandLine/Parsing/ParseOperation.cs index da2381f23b..472a75ed63 100644 --- a/src/System.CommandLine/Parsing/ParseOperation.cs +++ b/src/System.CommandLine/Parsing/ParseOperation.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.CommandLine.Help; namespace System.CommandLine.Parsing { @@ -9,22 +10,35 @@ internal sealed class ParseOperation { private readonly List _tokens; private readonly CommandLineConfiguration _configuration; + private readonly string? _rawInput; + private readonly SymbolResultTree _symbolResultTree; + private readonly CommandResult _rootCommandResult; + private int _index; + private Dictionary>? _directives; + private CommandResult _innermostCommandResult; + private bool _isHelpRequested; public ParseOperation( List tokens, - CommandLineConfiguration configuration) + CommandLineConfiguration configuration, + List? tokenizeErrors, + string? rawInput) { _tokens = tokens; _configuration = configuration; + _rawInput = rawInput; + _symbolResultTree = new(configuration.LocalizationResources, tokenizeErrors); + _innermostCommandResult = _rootCommandResult = new CommandResult( + _configuration.RootCommand, + CurrentToken, + _symbolResultTree); + + Advance(); } private Token CurrentToken => _tokens[_index]; - public CommandNode? RootCommandNode { get; private set; } - - public List? UnmatchedTokens { get; private set; } - private void Advance() => _index++; private bool More(out TokenType currentTokenType) @@ -34,38 +48,46 @@ private bool More(out TokenType currentTokenType) return result; } - public void Parse() - { - RootCommandNode = ParseRootCommand(); - } - - private CommandNode ParseRootCommand() + internal ParseResult Parse(Parser parser) { - var rootCommandNode = new CommandNode( - CurrentToken, - _configuration.RootCommand); + ParseDirectives(); - Advance(); + ParseCommandChildren(); - ParseDirectives(rootCommandNode); - - ParseCommandChildren(rootCommandNode); + if (!_isHelpRequested) + { + Validate(); + } - return rootCommandNode; + return new( + parser, + _rootCommandResult, + _innermostCommandResult, + _directives, + _tokens, + _symbolResultTree.UnmatchedTokens, + _symbolResultTree.Errors, + _rawInput); } - private void ParseSubcommand(CommandNode parentNode) + private void ParseSubcommand() { - var commandNode = new CommandNode(CurrentToken, (Command)CurrentToken.Symbol!); + Command command = (Command)CurrentToken.Symbol!; - Advance(); + _innermostCommandResult = new CommandResult( + command, + CurrentToken, + _symbolResultTree, + _innermostCommandResult); - ParseCommandChildren(commandNode); + _symbolResultTree.Add(command, _innermostCommandResult); + + Advance(); - parentNode.AddChildNode(commandNode); + ParseCommandChildren(); } - private void ParseCommandChildren(CommandNode parent) + private void ParseCommandChildren() { int currentArgumentCount = 0; int currentArgumentIndex = 0; @@ -74,15 +96,15 @@ private void ParseCommandChildren(CommandNode parent) { if (currentTokenType == TokenType.Command) { - ParseSubcommand(parent); + ParseSubcommand(); } else if (currentTokenType == TokenType.Option) { - ParseOption(parent); + ParseOption(); } else if (currentTokenType == TokenType.Argument) { - ParseCommandArguments(parent, ref currentArgumentCount, ref currentArgumentIndex); + ParseCommandArguments(ref currentArgumentCount, ref currentArgumentIndex); } else { @@ -92,13 +114,13 @@ private void ParseCommandChildren(CommandNode parent) } } - private void ParseCommandArguments(CommandNode commandNode, ref int currentArgumentCount, ref int currentArgumentIndex) + private void ParseCommandArguments(ref int currentArgumentCount, ref int currentArgumentIndex) { while (More(out TokenType currentTokenType) && currentTokenType == TokenType.Argument) { - while (commandNode.Command.HasArguments && currentArgumentIndex < commandNode.Command.Arguments.Count) + while (_innermostCommandResult.Command.HasArguments && currentArgumentIndex < _innermostCommandResult.Command.Arguments.Count) { - Argument argument = commandNode.Command.Arguments[currentArgumentIndex]; + Argument argument = _innermostCommandResult.Command.Arguments[currentArgumentIndex]; if (currentArgumentCount < argument.Arity.MaximumNumberOfValues) { @@ -108,12 +130,20 @@ private void ParseCommandArguments(CommandNode commandNode, ref int currentArgum CurrentToken.Symbol = argument; } - var argumentNode = new CommandArgumentNode( - CurrentToken, - argument, - commandNode); + if (!(_symbolResultTree.TryGetValue(argument, out var symbolResult) + && symbolResult is ArgumentResult argumentResult)) + { + argumentResult = + new ArgumentResult( + argument, + _symbolResultTree, + _innermostCommandResult); + + _symbolResultTree.Add(argument, argumentResult); + } - commandNode.AddChildNode(argumentNode); + argumentResult.AddToken(CurrentToken); + _innermostCommandResult.AddToken(CurrentToken); currentArgumentCount++; @@ -136,22 +166,39 @@ private void ParseCommandArguments(CommandNode commandNode, ref int currentArgum } } - private void ParseOption(CommandNode parent) + private void ParseOption() { - OptionNode optionNode = new( - CurrentToken, - (Option)CurrentToken.Symbol!); + Option option = (Option)CurrentToken.Symbol!; + OptionResult optionResult; - Advance(); + if (!_symbolResultTree.TryGetValue(option, out SymbolResult? symbolResult)) + { + if (option.DisallowBinding && option is HelpOption) + { + _isHelpRequested = true; + } + + optionResult = new OptionResult( + option, + _symbolResultTree, + CurrentToken, + _innermostCommandResult); - ParseOptionArguments(optionNode); + _symbolResultTree.Add(option, optionResult); + } + else + { + optionResult = (OptionResult)symbolResult; + } - parent.AddChildNode(optionNode); + Advance(); + + ParseOptionArguments(optionResult); } - private void ParseOptionArguments(OptionNode optionNode) + private void ParseOptionArguments(OptionResult optionResult) { - var argument = optionNode.Option.Argument; + var argument = optionResult.Option.Argument; var contiguousTokens = 0; int argumentCount = 0; @@ -162,24 +209,32 @@ private void ParseOptionArguments(OptionNode optionNode) { if (contiguousTokens > 0) { - return; + break; } if (argument.Arity.MaximumNumberOfValues == 0) { - return; + break; } } else if (argument.ValueType == typeof(bool) && !bool.TryParse(CurrentToken.Value, out _)) { - return; + break; } - optionNode.AddChildNode( - new OptionArgumentNode( - CurrentToken, - argument, - optionNode)); + if (!(_symbolResultTree.TryGetValue(argument, out SymbolResult? symbolResult) + && symbolResult is ArgumentResult argumentResult)) + { + argumentResult = new ArgumentResult( + argument, + _symbolResultTree, + optionResult); + + _symbolResultTree.Add(argument, argumentResult); + } + + argumentResult.AddToken(CurrentToken); + optionResult.AddToken(CurrentToken); argumentCount++; @@ -187,21 +242,27 @@ private void ParseOptionArguments(OptionNode optionNode) Advance(); - if (!optionNode.Option.AllowMultipleArgumentsPerToken) + if (!optionResult.Option.AllowMultipleArgumentsPerToken) { return; } } + + if (argumentCount == 0) + { + ArgumentResult argumentResult = new(optionResult.Option.Argument, _symbolResultTree, optionResult); + _symbolResultTree.Add(optionResult.Option.Argument, argumentResult); + } } - private void ParseDirectives(CommandNode rootCommandNode) + private void ParseDirectives() { while (More(out TokenType currentTokenType) && currentTokenType == TokenType.Directive) { - ParseDirective(rootCommandNode); // kept in separate method to avoid JIT + ParseDirective(); // kept in separate method to avoid JIT } - void ParseDirective(CommandNode parent) + void ParseDirective() { var token = CurrentToken; ReadOnlySpan withoutBrackets = token.Value.AsSpan(1, token.Value.Length - 2); @@ -213,9 +274,17 @@ void ParseDirective(CommandNode parent) ? withoutBrackets.Slice(indexOfColon + 1).ToString() : null; - var directiveNode = new DirectiveNode(token, key, value); + if (_directives is null || !_directives.TryGetValue(key, out var values)) + { + values = new List(); - parent.AddChildNode(directiveNode); + (_directives ??= new()).Add(key, values); + } + + if (value is not null) + { + ((List)values).Add(value); + } Advance(); } @@ -228,7 +297,22 @@ private void AddCurrentTokenToUnmatched() return; } - (UnmatchedTokens ??= new()).Add(CurrentToken); + _symbolResultTree.AddUnmatchedToken(CurrentToken); + } + + private void Validate() + { + // Only the inner most command goes through complete validation, + // for other commands only a subset of options is checked. + _innermostCommandResult.Validate(completeValidation: true); + + CommandResult? currentResult = _innermostCommandResult.Parent as CommandResult; + while (currentResult is not null) + { + currentResult.Validate(completeValidation: false); + + currentResult = currentResult.Parent as CommandResult; + } } } } \ No newline at end of file diff --git a/src/System.CommandLine/Parsing/ParseResultVisitor.cs b/src/System.CommandLine/Parsing/ParseResultVisitor.cs deleted file mode 100644 index 923b1dd479..0000000000 --- a/src/System.CommandLine/Parsing/ParseResultVisitor.cs +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.CommandLine.Help; - -namespace System.CommandLine.Parsing -{ - internal sealed class ParseResultVisitor - { - private readonly Parser _parser; - private readonly List _tokens; - private readonly string? _rawInput; - private readonly SymbolResultTree _symbolResultTree; - private readonly CommandResult _rootCommandResult; - - private Dictionary>? _directives; - private CommandResult _innermostCommandResult; - private bool _isHelpRequested; - - internal ParseResultVisitor( - Parser parser, - List tokens, - List? tokenizeErrors, - List? unmatchedTokens, - string? rawInput, - CommandNode rootCommandNode) - { - _parser = parser; - _tokens = tokens; - _rawInput = rawInput; - _symbolResultTree = new(_parser.Configuration.LocalizationResources, tokenizeErrors, unmatchedTokens); - _innermostCommandResult = _rootCommandResult = new CommandResult( - rootCommandNode.Command, - rootCommandNode.Token, - _symbolResultTree); - } - - internal void Visit(CommandNode rootCommandNode) - { - VisitChildren(rootCommandNode); - - if (!_isHelpRequested) - { - Validate(); - } - } - - internal ParseResult CreateResult() => - new(_parser, - _rootCommandResult, - _innermostCommandResult, - _directives, - _tokens, - _symbolResultTree.UnmatchedTokens, - _symbolResultTree.Errors, - _rawInput); - - private void VisitSyntaxNode(SyntaxNode node) - { - if (node is OptionNode optionNode) - { - VisitOptionNode(optionNode); - } - else if (node is OptionArgumentNode optionArgumentNode) - { - VisitOptionArgumentNode(optionArgumentNode); - } - else if (node is CommandArgumentNode commandArgumentNode) - { - VisitCommandArgumentNode(commandArgumentNode); - } - else if (node is CommandNode commandNode) - { - VisitCommandNode(commandNode); - } - else if (node is DirectiveNode directiveNode) - { - VisitDirectiveNode(directiveNode); - } - } - - private void VisitCommandNode(CommandNode commandNode) - { - _symbolResultTree.Add(commandNode.Command, _innermostCommandResult = new CommandResult( - commandNode.Command, - commandNode.Token, - _symbolResultTree, - _innermostCommandResult)); - - VisitChildren(commandNode); - } - - private void VisitCommandArgumentNode(CommandArgumentNode argumentNode) - { - if (!(_symbolResultTree.TryGetValue(argumentNode.Argument, out var symbolResult) - && symbolResult is ArgumentResult argumentResult)) - { - argumentResult = - new ArgumentResult( - argumentNode.Argument, - _symbolResultTree, - _innermostCommandResult); - - _symbolResultTree.Add(argumentNode.Argument, argumentResult); - } - - argumentResult.AddToken(argumentNode.Token); - _innermostCommandResult.AddToken(argumentNode.Token); - } - - private void VisitOptionNode(OptionNode optionNode) - { - if (!_symbolResultTree.ContainsKey(optionNode.Option)) - { - if (optionNode.Option.DisallowBinding && optionNode.Option is HelpOption) - { - _isHelpRequested = true; - } - - var optionResult = new OptionResult( - optionNode.Option, - _symbolResultTree, - optionNode.Token, - _innermostCommandResult); - - _symbolResultTree.Add(optionNode.Option, optionResult); - - if (optionNode.Children is null) // no Arguments - { - ArgumentResult argumentResult = new (optionResult.Option.Argument, _symbolResultTree, optionResult); - _symbolResultTree.Add(optionResult.Option.Argument, argumentResult); - } - } - - VisitChildren(optionNode); - } - - private void VisitOptionArgumentNode(OptionArgumentNode argumentNode) - { - OptionResult optionResult = (OptionResult)_symbolResultTree[argumentNode.ParentOptionNode.Option]; - - var argument = argumentNode.Argument; - - if (!(_symbolResultTree.TryGetValue(argument, out SymbolResult? symbolResult) - && symbolResult is ArgumentResult argumentResult)) - { - argumentResult = new ArgumentResult( - argumentNode.Argument, - _symbolResultTree, - optionResult); - - _symbolResultTree.Add(argument, argumentResult); - } - - argumentResult.AddToken(argumentNode.Token); - optionResult.AddToken(argumentNode.Token); - } - - private void VisitDirectiveNode(DirectiveNode directiveNode) - { - if (_directives is null || !_directives.TryGetValue(directiveNode.Name, out var values)) - { - values = new List(); - - (_directives ??= new()).Add(directiveNode.Name, values); - } - - if (directiveNode.Value is not null) - { - ((List)values).Add(directiveNode.Value); - } - } - - private void VisitChildren(NonterminalSyntaxNode parentNode) - { - if (parentNode.Children is not null) - { - for (var i = 0; i < parentNode.Children.Count; i++) - { - VisitSyntaxNode(parentNode.Children[i]); - } - } - } - - private void Validate() - { - // Only the inner most command goes through complete validation, - // for other commands only a subset of options is checked. - _innermostCommandResult.Validate(completeValidation: true); - - CommandResult? currentResult = _innermostCommandResult.Parent as CommandResult; - while (currentResult is not null) - { - currentResult.Validate(completeValidation: false); - - currentResult = currentResult.Parent as CommandResult; - } - } - } -} diff --git a/src/System.CommandLine/Parsing/Parser.cs b/src/System.CommandLine/Parsing/Parser.cs index acc05bea1d..61046b2f90 100644 --- a/src/System.CommandLine/Parsing/Parser.cs +++ b/src/System.CommandLine/Parsing/Parser.cs @@ -47,21 +47,11 @@ public ParseResult Parse( var operation = new ParseOperation( tokens, - Configuration); - - operation.Parse(); - - var visitor = new ParseResultVisitor( - this, - tokens, + Configuration, tokenizationErrors, - operation.UnmatchedTokens, - rawInput, - operation.RootCommandNode!); - - visitor.Visit(operation.RootCommandNode!); + rawInput); - return visitor.CreateResult(); + return operation.Parse(this); } } } diff --git a/src/System.CommandLine/Parsing/SymbolResultTree.cs b/src/System.CommandLine/Parsing/SymbolResultTree.cs index e31a5237b2..d59935f020 100644 --- a/src/System.CommandLine/Parsing/SymbolResultTree.cs +++ b/src/System.CommandLine/Parsing/SymbolResultTree.cs @@ -11,10 +11,9 @@ internal sealed class SymbolResultTree : Dictionary internal List? Errors; internal List? UnmatchedTokens; - internal SymbolResultTree(LocalizationResources localizationResources, List? tokenizeErrors, List? unmatchedTokens) + internal SymbolResultTree(LocalizationResources localizationResources, List? tokenizeErrors) { LocalizationResources = localizationResources; - UnmatchedTokens = unmatchedTokens; if (tokenizeErrors is not null) { diff --git a/src/System.CommandLine/Parsing/SyntaxNode.cs b/src/System.CommandLine/Parsing/SyntaxNode.cs deleted file mode 100644 index 5ad049f415..0000000000 --- a/src/System.CommandLine/Parsing/SyntaxNode.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.CommandLine.Parsing -{ - internal abstract class SyntaxNode - { - protected SyntaxNode(Token token) - { - Token = token; - } - - public Token Token { get; } - - public override string ToString() => Token.Value; - } -}