File: Parsing\SymbolResult.cs
Web Access
Project: src\src\command-line-api\src\System.CommandLine\System.CommandLine.csproj (System.CommandLine)
// 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
{
    /// <summary>
    /// A result produced during parsing for a specific symbol.
    /// </summary>
    public abstract class SymbolResult
    {
        internal readonly SymbolResultTree SymbolResultTree;
        private protected List<Token>? _tokens;

        private protected SymbolResult(SymbolResultTree symbolResultTree, SymbolResult? parent)
        {
            SymbolResultTree = symbolResultTree ?? throw new ArgumentNullException(nameof(symbolResultTree));
            Parent = parent;
        }

        /// <summary>
        /// The parse errors associated with this symbol result.
        /// </summary>
        public IEnumerable<ParseError> Errors
        {
            get
            {
                var parseErrors = SymbolResultTree.Errors;

                if (parseErrors is null)
                {
                    yield break;
                }

                for (var i = 0; i < parseErrors.Count; i++)
                {
                    var parseError = parseErrors[i];
                    if (parseError.SymbolResult == this)
                    {
                        yield return parseError;
                    }
                }
            }
        }

        /// <summary>
        /// The parent symbol result in the parse tree.
        /// </summary>
        public SymbolResult? Parent { get; }

        /// <summary>
        /// The list of tokens associated with this symbol result during parsing.
        /// </summary>
        public IReadOnlyList<Token> Tokens => _tokens is not null ? _tokens : Array.Empty<Token>();

        internal void AddToken(Token token) => (_tokens ??= new()).Add(token);

        /// <summary>
        /// Adds an error message for this symbol result to it's parse tree.
        /// </summary>
        /// <remarks>Setting an error will cause the parser to indicate an error for the user and prevent invocation of the command line.</remarks>
        public virtual void AddError(string errorMessage) => SymbolResultTree.AddError(new ParseError(errorMessage, this));

        /// <summary>
        /// Finds a result for the specific argument anywhere in the parse tree, including parent and child symbol results.
        /// </summary>
        /// <param name="argument">The argument for which to find a result.</param>
        /// <returns>An argument result if the argument was matched by the parser or has a default value; otherwise, <c>null</c>.</returns>
        public ArgumentResult? GetResult(Argument argument) => SymbolResultTree.GetResult(argument);

        /// <summary>
        /// Finds a result for the specific command anywhere in the parse tree, including parent and child symbol results.
        /// </summary>
        /// <param name="command">The command for which to find a result.</param>
        /// <returns>An command result if the command was matched by the parser; otherwise, <c>null</c>.</returns>
        public CommandResult? GetResult(Command command) => SymbolResultTree.GetResult(command);

        /// <summary>
        /// Finds a result for the specific option anywhere in the parse tree, including parent and child symbol results.
        /// </summary>
        /// <param name="option">The option for which to find a result.</param>
        /// <returns>An option result if the option was matched by the parser or has a default value; otherwise, <c>null</c>.</returns>
        public OptionResult? GetResult(Option option) => SymbolResultTree.GetResult(option);

        /// <summary>
        /// Finds a result for the specific directive anywhere in the parse tree.
        /// </summary>
        /// <param name="directive">The directive for which to find a result.</param>
        /// <returns>A directive result if the directive was matched by the parser, <c>null</c> otherwise.</returns>
        public DirectiveResult? GetResult(Directive directive) => SymbolResultTree.GetResult(directive);

        /// <summary>
        /// Finds a result for a <see cref="Symbol" /> having the specified <paramref name="name" /> anywhere in the parse tree.
        /// </summary>
        /// <param name="name">The name of the <see cref="Symbol" /> for which to find a result.</param>
        /// <returns>
        /// A <see cref="SymbolResult" /> if the <see cref="Symbol" /> was matched by the parser or has a default value;
        /// otherwise, <see langword="null" />.
        /// </returns>
        public SymbolResult? GetResult(string name) => 
            SymbolResultTree.GetResult(name);

        /// <inheritdoc cref="ParseResult.GetValue{T}(Argument{T})"/>
        public T? GetValue<T>(Argument<T> argument)
        {
            if (GetResult(argument) is { } result &&
                result.GetValueOrDefault<T>() is { } t)
            {
                return t;
            }

            return Argument<T>.CreateDefaultValue();
        }

        /// <inheritdoc cref="ParseResult.GetValue{T}(Option{T})"/>
        public T? GetValue<T>(Option<T> option)
        {
            if (GetResult(option) is { } result &&
                result.GetValueOrDefault<T>() is { } t)
            {
                return t;
            }

            return Argument<T>.CreateDefaultValue();
        }

        /// <inheritdoc cref="ParseResult.GetRequiredValue{T}(Argument{T})"/>
        public T GetRequiredValue<T>(Argument<T> argument)
            => GetResult(argument) switch
            {
                ArgumentResult argumentResult => argumentResult.GetValueOrDefault<T>(),
                null => Argument<T>.CreateDefaultValue() ?? throw new InvalidOperationException($"{argument.Name} is required but was not provided."),
            };

        /// <inheritdoc cref="ParseResult.GetRequiredValue{T}(Option{T})"/>
        public T GetRequiredValue<T>(Option<T> option)
            => GetResult(option) switch
            {
                OptionResult optionResult => optionResult.GetValueOrDefault<T>(),
                null => throw new InvalidOperationException($"{option.Name} is required but was not provided."),
            };

        /// <summary>
        /// Gets the value for a symbol having the specified name anywhere in the parse tree.
        /// </summary>
        /// <param name="name">The name of the symbol for which to find a result.</param>
        /// <returns>An argument result if the argument was matched by the parser or has a default value; otherwise, <c>null</c>.</returns>
        public T? GetValue<T>(string name)
        {
            if (GetResult(name) is { } result)
            {
                if (result is OptionResult optionResult &&
                    optionResult.GetValueOrDefault<T>() is { } optionValue)
                {
                    return optionValue;
                }

                if (result is ArgumentResult argumentResult &&
                    argumentResult.GetValueOrDefault<T>() is { } argumentValue)
                {
                    return argumentValue;
                }
            }

            return Argument<T>.CreateDefaultValue();
        }

        /// <summary>
        /// Gets the value for a symbol having the specified name anywhere in the parse tree.
        /// </summary>
        /// <param name="name">The name of the symbol for which to find a result.</param>
        /// <returns>An argument result if the argument was matched by the parser or has a default value; otherwise, <c>null</c>.</returns>
        public T GetRequiredValue<T>(string name)
            => GetResult(name) switch
            {
                OptionResult optionResult => optionResult.GetValueOrDefault<T>(),
                ArgumentResult argumentResult => argumentResult.GetValueOrDefault<T>(),
                SymbolResult _ => throw new InvalidOperationException($"{name} is not an option or argument."),
                _ => throw new InvalidOperationException($"{name} is required but was not provided."),
            };

        internal virtual bool UseDefaultValueFor(ArgumentResult argumentResult) => false;
    }
}