private @NonNull CompletableFuture<@Nullable Command<C>> parseCommand(
            final @NonNull List<@NonNull CommandComponent<C>> parsedArguments,
            final @NonNull CommandContext<C> commandContext,
            final @NonNull CommandInput commandInput,
            final @NonNull CommandNode<C> root,
            final @NonNull Executor executor
    ) {
        final Optional<PermissionResult> permissionResult = this.determineAccess(commandContext.sender(), root);
        if (!permissionResult.isPresent()) {
            return CompletableFutures.failedFuture(
                    new InvalidCommandSenderException(
                            commandContext.sender(),
                            root.nodeMeta().get(CommandNode.META_KEY_SENDER_TYPES),
                            this.getComponentChain(root),
                            null
                    )
            );
        }
        if (permissionResult.get().denied()) {
            return CompletableFutures.failedFuture(
                    new NoPermissionException(
                            permissionResult.get(),
                            commandContext.sender(),
                            this.getComponentChain(root)
                    )
            );
        }

        final CompletableFuture<@Nullable Command<C>> parsedChild = this.attemptParseUnambiguousChild(
                parsedArguments,
                commandContext,
                root,
                commandInput,
                executor
        );
        if (parsedChild != null) {
            return parsedChild;
        }

        // There are 0 or more static arguments as children. No variable child arguments are present
        if (root.children().isEmpty()) {
            final CommandComponent<C> rootComponent = root.component();
            if (rootComponent == null || root.command() == null || !commandInput.isEmpty()) {
                // Too many arguments. We have a unique path, so we can send the entire context
                return CompletableFutures.failedFuture(
                        new InvalidSyntaxException(
                                this.commandManager.commandSyntaxFormatter()
                                        .apply(commandContext.sender(), parsedArguments, root),
                                commandContext.sender(), this.getComponentChain(root)
                        )
                );
            }
            return CompletableFuture.completedFuture(root.command());
        }

        CompletableFuture<Command<C>> childCompletable = CompletableFuture.completedFuture(null);
        for (final CommandNode<C> child : new ArrayList<>(root.children())) {
            if (child.component() == null) {
                continue;
            }

            childCompletable = childCompletable.thenCompose(previousResult -> {
                if (previousResult != null) {
                    return CompletableFuture.completedFuture(previousResult);
                }

                final CommandComponent<C> component = Objects.requireNonNull(child.component());
                final ParsingContext<C> parsingContext = commandContext.createParsingContext(component);

                // Skip a single space (argument delimiter)
                commandInput.skipWhitespace(1);
                // Copy the current queue so that we can deduce the captured input.
                final CommandInput currentInput = commandInput.copy();

                parsingContext.markStart();

                return component.parser()
                        .parseFuture(commandContext, commandInput)
                        .thenComposeAsync(result -> {
                            parsingContext.markEnd();
                            parsingContext.success(!result.failure().isPresent());
                            parsingContext.consumedInput(currentInput, commandInput);

                            if (result.parsedValue().isPresent()) {
                                parsedArguments.add(component);
                                return this.parseCommand(parsedArguments, commandContext, commandInput, child, executor);
                            } else if (result.failure().isPresent()) {
                                commandInput.cursor(currentInput.cursor());
                            }
                            // We do not want to respond with a parsing error, as parsing errors are meant to propagate.
                            // Just not being able to parse is not enough.
                            return CompletableFuture.completedFuture(null);
                        }, executor);
            });
        }

        return childCompletable.thenCompose(completedCommand -> {
                    if (completedCommand != null) {
                        return CompletableFuture.completedFuture(completedCommand);
                    }

                    // We could not find a match
                    if (root.equals(this.internalTree)) {
                       return CompletableFutures.failedFuture(
                           new NoSuchCommandException(
                                   commandContext.sender(),
                                   this.getChain(root).stream().map(CommandNode::component).collect(Collectors.toList()),
                                   commandInput.peekString()
                           )
                       );
                   }

                    // If we couldn't match a child, check if there's a command attached and execute it
                    final CommandComponent<C> rootComponent = root.component();
                    if (rootComponent != null && root.command() != null && commandInput.isEmpty()) {
                        final Command<C> command = root.command();
                        final PermissionResult check = this.commandManager.testPermission(
                                commandContext.sender(),
                                command.commandPermission()
                        );
                        if (check.denied()) {
                            return CompletableFutures.failedFuture(
                                    new NoPermissionException(
                                            check,
                                            commandContext.sender(),
                                            this.getComponentChain(root)
                                    )
                            );
                        }
                        return CompletableFuture.completedFuture(root.command());
                    }

                    // We know that there's no command, and we also cannot match any of the children
                    return CompletableFutures.failedFuture(
                            new InvalidSyntaxException(
                                    this.commandManager.commandSyntaxFormatter()
                                            .apply(commandContext.sender(), parsedArguments, root),
                                    commandContext.sender(), this.getComponentChain(root)
                            )
                    );
                });
    }
Attempts to parse a command from the provided input.

@param commandContext  command context instance
@param commandInput    command input
@param parsingExecutor executor to schedule parsing logic on
@return parsed command, if one could be found
cloud-core/src/main/java/org/incendo/cloud/CommandTree.java