// Programmatic representation of fish grammar.

#ifndef FISH_AST_H
#define FISH_AST_H

#include <array>
#include <tuple>
#include <type_traits>

#include "flog.h"
#include "parse_constants.h"
#include "tokenizer.h"

namespace ast {

/**
 * This defines the fish abstract syntax tree.
 * The fish ast is a tree data structure. The nodes of the tree
 * are divided into three types:
 *
 * - leaf nodes refer to a range of source, and have no child nodes.
 * - branch nodes have ONLY child nodes, and no other fields.
 * - list nodes contain a list of some other node type (branch or leaf).
 *
 * Most clients will be interested in visiting the nodes of an ast.
 * See node_visitation_t below.
 */

struct node_t;

// Our node categories.
// Note these are not stored directly in a node; they are provided in the Category static constexpr
// variable in each node.
enum class category_t : uint8_t {
    branch,
    leaf,
    list,
};

// Declare our type enum.
// For each member of our ast, this creates an enum value.
// For example this creates `type_t::job_list`.
enum class type_t : uint8_t {
#define ELEM(T) T,
#include "ast_node_types.inc"
};

// Helper to return a string description of a type.
const wchar_t *ast_type_to_string(type_t type);

// Forward declare all AST structs.
#define ELEM(T) struct T##_t;
#include "ast_node_types.inc"

/*
 * A FieldVisitor is something which can visit the fields of an ast node.
 * This is used during ast construction.
 *
 * To trigger field visitation, use the accept() function:
 *    MyFieldVisitor v;
 *    node->accept(v);
 *
 * Example FieldVisitor:
 *
 * struct MyFieldVisitor {
 *
 *    /// will_visit (did_visit) is called before (after) a node's fields are visited.
 *    void will_visit_fields_of(node_t &node);
 *    void did_visit_fields_of(node_t &node);
 *
 *    /// These are invoked with the concrete type of each node,
 *    /// so they may be overloaded to distinguish node types.
 *    /// Example:
 *    void will_visit_fields_of(job_t &job);
 *
 *    /// The visitor needs to be prepared for the following four field types.
 *    /// Naturally the vistor may overload visit_field to carve this
 *    /// arbitrarily finely.
 *
 *    /// A field may be a "direct embedding" of a node.
 *    /// That is, an ast node may have another node as a member.
 *    template <typename Node>
 *    void visit_node_field(Node &node);

 *    /// A field may be a list_t of (pointers to) some other node type.
 *    template <type_t List, typename Node>
 *    void visit_list_field(list_t<List, Node> &list);
 *
 *    /// A field may be a unique_ptr to another node.
 *    /// Every such pointer must be non-null after construction.
 *    template <typename Node>
 *    void visit_pointer_field(std::unique_ptr<Node> &ptr);
 *
 *    /// A field may be optional, meaning it may or may not exist.
 *    template <typename Node>
 *    void visit_optional_field(optional_t<NodeT> &opt);
 *
 *    /// A field may be a union pointer, meaning it points to one of
 *    /// a fixed set of node types. A union pointer is never null
 *    /// after construction.
 *    template <typename... Nodes>
 *    void visit_union_field(union_ptr_t<Nodes...> &union_ptr);
 * };
 */

// Our node base type is not virtual, so we must not invoke its destructor directly.
// If you want to delete a node and don't know its concrete type, use this deleter type.
struct node_deleter_t {
    void operator()(node_t *node);
};
using node_unique_ptr_t = std::unique_ptr<node_t, node_deleter_t>;

// A union pointer field is a pointer to one of a fixed set of node types.
// It is never null after construction.
template <typename... Nodes>
struct union_ptr_t {
    node_unique_ptr_t contents{};

    /// \return a pointer to the node contents.
    const node_t *get() const {
        assert(contents && "Null pointer");
        return contents.get();
    }

    /// \return whether we have non-null contents.
    explicit operator bool() const { return contents != nullptr; }

    const node_t *operator->() const { return get(); }

    union_ptr_t() = default;

    // Allow setting a typed unique pointer.
    template <typename Node>
    inline void operator=(std::unique_ptr<Node> n);

    // Construct from a typed unique pointer.
    template <typename Node>
    inline union_ptr_t(std::unique_ptr<Node> n);
};

// A pointer to something, or nullptr if not present.
template <typename AstNode>
struct optional_t {
    std::unique_ptr<AstNode> contents{};

    explicit operator bool() const { return contents != nullptr; }

    AstNode *operator->() const {
        assert(contents && "Null pointer");
        return contents.get();
    }

    const AstNode &operator*() const {
        assert(contents && "Null pointer");
        return *contents;
    }

    bool has_value() const { return contents != nullptr; }
};

namespace template_goo {

// void if B is true, SFINAE'd away otherwise.
template <bool B>
using only_if_t = typename std::enable_if<B>::type;

template <typename FieldVisitor, typename Field>
only_if_t<Field::Category != category_t::list> visit_1_field(FieldVisitor &v, Field &field) {
    v.visit_node_field(field);
}

template <typename FieldVisitor, typename Field>
only_if_t<Field::Category == category_t::list> visit_1_field(FieldVisitor &v, Field &field) {
    v.visit_list_field(field);
}

template <typename FieldVisitor, typename Field>
void visit_1_field(FieldVisitor &v, Field *&field) {
    v.visit_pointer_field(field);
}

template <typename FieldVisitor, typename Field>
void visit_1_field(FieldVisitor &v, optional_t<Field> &field) {
    v.visit_optional_field(field);
}

template <typename FieldVisitor, typename... Nodes>
void visit_1_field(FieldVisitor &v, union_ptr_t<Nodes...> &field) {
    v.visit_union_field(field);
}

// Call the field visit methods on visitor \p v passing field \p field.
template <typename FieldVisitor, typename Field>
void accept_field_visitor(FieldVisitor &v, bool /*reverse*/, Field &field) {
    visit_1_field(v, field);
}

// Call visit_field on visitor \p v, for the field \p field and also \p rest.
template <typename FieldVisitor, typename Field, typename... Rest>
void accept_field_visitor(FieldVisitor &v, bool reverse, Field &field, Rest &...rest) {
    if (!reverse) visit_1_field(v, field);
    accept_field_visitor<FieldVisitor, Rest...>(v, reverse, rest...);
    if (reverse) visit_1_field(v, field);
}

}  // namespace template_goo

#define FIELDS(...)                                                         \
    template <typename FieldVisitor>                                        \
    void accept(FieldVisitor &visitor, bool reversed = false) {             \
        visitor.will_visit_fields_of(*this);                                \
        template_goo::accept_field_visitor(visitor, reversed, __VA_ARGS__); \
        visitor.did_visit_fields_of(*this);                                 \
    }

/// node_t is the base node of all AST nodes.
/// It is not a template: it is possible to work concretely with this type.
struct node_t : noncopyable_t {
    /// The parent node, or null if this is root.
    const node_t *parent{nullptr};

    /// The type of this node.
    const type_t type;

    /// The category of this node.
    const category_t category;

    constexpr explicit node_t(type_t t, category_t c) : type(t), category(c) {}

    /// Cast to a concrete node type, aborting on failure.
    /// Example usage:
    ///   if (node->type == type_t::job_list) node->as<job_list_t>()->...
    template <typename To>
    To *as() {
        assert(this->type == To::AstType && "Invalid type conversion");
        return static_cast<To *>(this);
    }

    template <typename To>
    const To *as() const {
        assert(this->type == To::AstType && "Invalid type conversion");
        return static_cast<const To *>(this);
    }

    /// Try casting to a concrete node type, except returns nullptr on failure.
    /// Example ussage:
    ///     if (const auto *job_list = node->try_as<job_list_t>()) job_list->...
    template <typename To>
    To *try_as() {
        if (this->type == To::AstType) return as<To>();
        return nullptr;
    }

    template <typename To>
    const To *try_as() const {
        if (this->type == To::AstType) return as<To>();
        return nullptr;
    }

    /// Base accept() function which trampolines to overriding implementations for each node type.
    /// This may be used when you don't know what the type of a particular node is.
    template <typename FieldVisitor>
    void base_accept(FieldVisitor &v, bool reverse = false);

    /// \return a helpful string description of this node.
    wcstring describe() const;

    /// \return the source range for this node, or none if unsourced.
    /// This may return none if the parse was incomplete or had an error.
    maybe_t<source_range_t> try_source_range() const;

    /// \return the source range for this node, or an empty range {0, 0} if unsourced.
    source_range_t source_range() const {
        if (auto r = try_source_range()) return *r;
        return source_range_t{0, 0};
    }

    /// \return the source code for this node, or none if unsourced.
    maybe_t<wcstring> try_source(const wcstring &orig) const {
        if (auto r = try_source_range()) return orig.substr(r->start, r->length);
        return none();
    }

    /// \return the source code for this node, or an empty string if unsourced.
    wcstring source(const wcstring &orig) const {
        wcstring res{};
        if (auto s = try_source(orig)) res = s.acquire();
        return res;
    }

    /// \return the source code for this node, or an empty string if unsourced.
    /// This uses \p storage to reduce allocations.
    const wcstring &source(const wcstring &orig, wcstring *storage) const {
        if (auto r = try_source_range()) {
            storage->assign(orig, r->start, r->length);
        } else {
            storage->clear();
        }
        return *storage;
    }

   protected:
    // We are NOT a virtual class - we have no vtable or virtual methods and our destructor is not
    // virtual, so as to keep the size down. Only typed nodes should invoke the destructor.
    // Use node_deleter_t to delete an untyped node.
    ~node_t() = default;
};

// Base class for all "branch" nodes: nodes with at least one ast child.
template <type_t Type>
struct branch_t : public node_t {
    static constexpr type_t AstType = Type;
    static constexpr category_t Category = category_t::branch;

    branch_t() : node_t(Type, Category) {}
};

// Base class for all "leaf" nodes: nodes with no ast children.
// It declares an empty visit method to avoid requiring the CHILDREN macro.
template <type_t Type>
struct leaf_t : public node_t {
    static constexpr type_t AstType = Type;
    static constexpr category_t Category = category_t::leaf;

    // Whether this node is "unsourced." This happens if for whatever reason we are unable to parse
    // the node, either because we had a parse error and recovered, or because we accepted
    // incomplete and the token stream was exhausted.
    bool unsourced{false};

    // The source range.
    source_range_t range{0, 0};

    // Convenience helper to return whether we are not unsourced.
    bool has_source() const { return !unsourced; }

    template <typename FieldVisitor>
    void accept(FieldVisitor &visitor, bool /* reverse */ = false) {
        visitor.will_visit_fields_of(*this);
        visitor.did_visit_fields_of(*this);
    }

    leaf_t() : node_t(Type, Category) {}
};

// A simple fixed-size array, possibly empty.
// Disallow moving as we own a raw pointer.
template <type_t ListType, typename ContentsNode>
struct list_t : public node_t, nonmovable_t {
    static constexpr type_t AstType = ListType;
    static constexpr category_t Category = category_t::list;

    // A list wraps a "contents pointer" which is just a unique_ptr that converts to a reference.
    // This enables more natural iteration:
    //    for (const argument_t &arg : argument_list) ...
    struct contents_ptr_t {
        std::unique_ptr<ContentsNode> ptr{};

        void operator=(std::unique_ptr<ContentsNode> p) { ptr = std::move(p); }

        const ContentsNode *get() const {
            assert(ptr && "Null pointer");
            return ptr.get();
        }

        /* implicit */ operator const ContentsNode &() const { return *get(); }
    };

    // We use a new[]-allocated array to store our contents pointers, to reduce size.
    // This would be a nice use case for std::dynarray.
    uint32_t length{0};
    const contents_ptr_t *contents{};

    /// \return a node at a given index, or nullptr if out of range.
    const ContentsNode *at(size_t idx, bool reverse = false) const {
        if (idx >= count()) return nullptr;
        return contents[reverse ? count() - idx - 1 : idx].get();
    }

    /// \return our count.
    size_t count() const { return length; }

    /// \return whether we are empty.
    bool empty() const { return length == 0; }

    /// Iteration support.
    using iterator = const contents_ptr_t *;
    iterator begin() const { return contents; }
    iterator end() const { return contents + length; }

    // list types pretend their child nodes are direct embeddings.
    // This isn't used during AST construction because we need to construct the list.
    // It is used by node_visitation_t.
    template <typename FieldVisitor>
    void accept(FieldVisitor &visitor, bool reverse = false) {
        visitor.will_visit_fields_of(*this);
        for (size_t i = 0; i < count(); i++) visitor.visit_node_field(*this->at(i, reverse));
        visitor.did_visit_fields_of(*this);
    }

    list_t() : node_t(ListType, Category) {}
    ~list_t() { delete[] contents; }
};

// Fully define all list types, as they are very uniform.
// This is where types like job_list_t come from.
#define ELEM(T)
#define ELEMLIST(ListT, ContentsT) \
    struct ListT##_t final : public list_t<type_t::ListT, ContentsT##_t> {};
#include "ast_node_types.inc"

struct keyword_base_t : public leaf_t<type_t::keyword_base> {
    // The keyword which was parsed.
    parse_keyword_t kw;
};

// A keyword node is a node which contains a keyword, which must be one of the provided values.
template <parse_keyword_t... KWs>
struct keyword_t final : public keyword_base_t {
    static bool allows_keyword(parse_keyword_t);
};

struct token_base_t : public leaf_t<type_t::token_base> {
    // The token type which was parsed.
    parse_token_type_t type{parse_token_type_t::invalid};
};

// A token node is a node which contains a token, which must be one of the provided values.
template <parse_token_type_t... Toks>
struct token_t final : public token_base_t {
    /// \return whether a token type is allowed in this token_t, i.e. is a member of our Toks list.
    static bool allows_token(parse_token_type_t);
};

// Zero or more newlines.
struct maybe_newlines_t final : public leaf_t<type_t::maybe_newlines> {};

// A single newline or semicolon, terminating statements.
// Note this is not a separate type, it is just a convenience typedef.
using semi_nl_t = token_t<parse_token_type_t::end>;

// Convenience typedef for string nodes.
using string_t = token_t<parse_token_type_t::string>;

// An argument is just a node whose source range determines its contents.
// This is a separate type because it is sometimes useful to find all arguments.
struct argument_t final : public leaf_t<type_t::argument> {};

// A redirection has an operator like > or 2>, and a target like /dev/null or &1.
// Note that pipes are not redirections.
struct redirection_t final : public branch_t<type_t::redirection> {
    token_t<parse_token_type_t::redirection> oper;
    string_t target;

    FIELDS(oper, target)
};

// A variable_assignment_t contains a source range like FOO=bar.
struct variable_assignment_t final : public leaf_t<type_t::variable_assignment> {};

// An argument or redirection holds either an argument or redirection.
struct argument_or_redirection_t final : public branch_t<type_t::argument_or_redirection> {
    using contents_ptr_t = union_ptr_t<argument_t, redirection_t>;
    contents_ptr_t contents{};

    /// \return whether this represents an argument.
    bool is_argument() const { return contents->type == type_t::argument; }

    /// \return whether this represents a redirection
    bool is_redirection() const { return contents->type == type_t::redirection; }

    /// \return this as an argument, assuming it wraps one.
    const argument_t &argument() const {
        assert(is_argument() && "Is not an argument");
        return *this->contents.contents->as<argument_t>();
    }

    /// \return this as an argument, assuming it wraps one.
    const redirection_t &redirection() const {
        assert(is_redirection() && "Is not a redirection");
        return *this->contents.contents->as<redirection_t>();
    }

    FIELDS(contents);
};

// A statement is a normal command, or an if / while / etc
struct statement_t final : public branch_t<type_t::statement> {
    using contents_ptr_t = union_ptr_t<not_statement_t, block_statement_t, if_statement_t,
                                       switch_statement_t, decorated_statement_t>;
    contents_ptr_t contents{};

    FIELDS(contents)
};

// A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases
// like if statements, where we require a command).
struct job_t final : public branch_t<type_t::job> {
    // Maybe the time keyword.
    optional_t<keyword_t<parse_keyword_t::kw_time>> time;

    // A (possibly empty) list of variable assignments.
    variable_assignment_list_t variables;

    // The statement.
    statement_t statement;

    // Piped remainder.
    job_continuation_list_t continuation;

    // Maybe backgrounded.
    optional_t<token_t<parse_token_type_t::background>> bg;

    FIELDS(time, variables, statement, continuation, bg)
};

// A job_conjunction is a job followed by a && or || continuations.
struct job_conjunction_t final : public branch_t<type_t::job_conjunction> {
    // The job conjunction decorator.
    using decorator_t = keyword_t<parse_keyword_t::kw_and, parse_keyword_t::kw_or>;
    optional_t<decorator_t> decorator{};

    // The job itself.
    job_t job;

    // The rest of the job conjunction, with && or ||s.
    job_conjunction_continuation_list_t continuations;

    // A terminating semicolon or newline.
    // This is marked optional because it may not be present, for example the command `echo foo` may
    // not have a terminating newline. It will only fail to be present if we ran out of tokens.
    optional_t<semi_nl_t> semi_nl;

    FIELDS(decorator, job, continuations, semi_nl)
};

struct for_header_t final : public branch_t<type_t::for_header> {
    // 'for'
    keyword_t<parse_keyword_t::kw_for> kw_for;

    // var_name
    string_t var_name;

    // 'in'
    keyword_t<parse_keyword_t::kw_in> kw_in;

    // list of arguments
    argument_list_t args;

    // newline or semicolon
    semi_nl_t semi_nl;

    FIELDS(kw_for, var_name, kw_in, args, semi_nl)
};

struct while_header_t final : public branch_t<type_t::while_header> {
    // 'while'
    keyword_t<parse_keyword_t::kw_while> kw_while;

    job_conjunction_t condition{};
    andor_job_list_t andor_tail{};

    FIELDS(kw_while, condition, andor_tail)
};

struct function_header_t final : public branch_t<type_t::function_header> {
    // functions require at least one argument.
    keyword_t<parse_keyword_t::kw_function> kw_function;
    argument_t first_arg;
    argument_list_t args;
    semi_nl_t semi_nl;

    FIELDS(kw_function, first_arg, args, semi_nl)
};

struct begin_header_t final : public branch_t<type_t::begin_header> {
    keyword_t<parse_keyword_t::kw_begin> kw_begin;

    // Note that 'begin' does NOT require a semi or nl afterwards.
    // This is valid: begin echo hi; end
    optional_t<semi_nl_t> semi_nl;

    FIELDS(kw_begin, semi_nl)
};

struct block_statement_t final : public branch_t<type_t::block_statement> {
    // A header like for, while, etc.
    using header_ptr_t =
        union_ptr_t<for_header_t, while_header_t, function_header_t, begin_header_t>;
    header_ptr_t header;

    // List of jobs in this block.
    job_list_t jobs;

    // The 'end' node.
    keyword_t<parse_keyword_t::kw_end> end;

    // Arguments and redirections associated with the block.
    argument_or_redirection_list_t args_or_redirs;

    FIELDS(header, jobs, end, args_or_redirs)
};

// Represents an 'if', either as the first part of an if statement or after an 'else'.
struct if_clause_t final : public branch_t<type_t::if_clause> {
    // The 'if' keyword.
    keyword_t<parse_keyword_t::kw_if> kw_if;

    // The 'if' condition.
    job_conjunction_t condition{};

    // 'and/or' tail.
    andor_job_list_t andor_tail{};

    // The body to execute if the condition is true.
    job_list_t body;

    FIELDS(kw_if, condition, andor_tail, body)
};

struct elseif_clause_t final : public branch_t<type_t::elseif_clause> {
    // The 'else' keyword.
    keyword_t<parse_keyword_t::kw_else> kw_else;

    // The 'if' clause following it.
    if_clause_t if_clause;

    FIELDS(kw_else, if_clause)
};

struct else_clause_t final : public branch_t<type_t::else_clause> {
    // else ; body
    keyword_t<parse_keyword_t::kw_else> kw_else;
    semi_nl_t semi_nl;
    job_list_t body;

    FIELDS(kw_else, semi_nl, body)
};

struct if_statement_t final : public branch_t<type_t::if_statement> {
    // if part
    if_clause_t if_clause;

    // else if list
    elseif_clause_list_t elseif_clauses;

    // else part
    optional_t<else_clause_t> else_clause;

    // literal end
    keyword_t<parse_keyword_t::kw_end> end;

    // block args / redirs
    argument_or_redirection_list_t args_or_redirs;

    FIELDS(if_clause, elseif_clauses, else_clause, end, args_or_redirs)
};

struct case_item_t final : public branch_t<type_t::case_item> {
    // case <arguments> ; body
    keyword_t<parse_keyword_t::kw_case> kw_case;
    argument_list_t arguments;
    semi_nl_t semi_nl;
    job_list_t body;
    FIELDS(kw_case, arguments, semi_nl, body)
};

struct switch_statement_t final : public branch_t<type_t::switch_statement> {
    // switch <argument> ; body ; end args_redirs
    keyword_t<parse_keyword_t::kw_switch> kw_switch;
    argument_t argument;
    semi_nl_t semi_nl;
    case_item_list_t cases;
    keyword_t<parse_keyword_t::kw_end> end;
    argument_or_redirection_list_t args_or_redirs;

    FIELDS(kw_switch, argument, semi_nl, cases, end, args_or_redirs)
};

// A decorated_statement is a command with a list of arguments_or_redirections, possibly with
// "builtin" or "command" or "exec"
struct decorated_statement_t final : public branch_t<type_t::decorated_statement> {
    // An optional decoration (command, builtin, exec, etc).
    using pk = parse_keyword_t;
    using decorator_t = keyword_t<pk::kw_command, pk::kw_builtin, pk::kw_exec>;
    optional_t<decorator_t> opt_decoration;

    // Command to run.
    string_t command;

    // Args and redirs
    argument_or_redirection_list_t args_or_redirs;

    // Helper to return the decoration.
    statement_decoration_t decoration() const;

    FIELDS(opt_decoration, command, args_or_redirs)
};

// A not statement like `not true` or `! true`
struct not_statement_t final : public branch_t<type_t::not_statement> {
    // Keyword, either not or exclam.
    keyword_t<parse_keyword_t::kw_not, parse_keyword_t::kw_exclam> kw;

    variable_assignment_list_t variables;
    optional_t<keyword_t<parse_keyword_t::kw_time>> time{};
    statement_t contents{};

    FIELDS(kw, variables, time, contents)
};

struct job_continuation_t final : public branch_t<type_t::job_continuation> {
    token_t<parse_token_type_t::pipe> pipe;
    maybe_newlines_t newlines;
    variable_assignment_list_t variables;
    statement_t statement;

    FIELDS(pipe, newlines, variables, statement)
};

struct job_conjunction_continuation_t final
    : public branch_t<type_t::job_conjunction_continuation> {
    // The && or || token.
    token_t<parse_token_type_t::andand, parse_token_type_t::oror> conjunction;
    maybe_newlines_t newlines;

    // The job itself.
    job_t job;

    FIELDS(conjunction, newlines, job)
};

// An andor_job just wraps a job, but requires that the job have an 'and' or 'or' job_decorator.
// Note this is only used for andor_job_list; jobs that are not part of an andor_job_list are not
// instances of this.
struct andor_job_t final : public branch_t<type_t::andor_job> {
    job_conjunction_t job;

    FIELDS(job)
};

// A freestanding_argument_list is equivalent to a normal argument list, except it may contain
// TOK_END (newlines, and even semicolons, for historical reasons).
// In practice the tok_ends are ignored by fish code so we do not bother to store them.
struct freestanding_argument_list_t final : public branch_t<type_t::freestanding_argument_list> {
    argument_list_t arguments;
    FIELDS(arguments)
};

template <typename FieldVisitor>
void node_t::base_accept(FieldVisitor &v, bool reverse) {
    switch (this->type) {
#define ELEM(T)                                \
    case type_t::T:                            \
        this->as<T##_t>()->accept(v, reverse); \
        break;

#include "ast_node_types.inc"
    }
}

// static
template <parse_token_type_t... Toks>
bool token_t<Toks...>::allows_token(parse_token_type_t type) {
    for (parse_token_type_t t : {Toks...}) {
        if (type == t) return true;
    }
    return false;
}

// static
template <parse_keyword_t... KWs>
bool keyword_t<KWs...>::allows_keyword(parse_keyword_t kw) {
    for (parse_keyword_t k : {KWs...}) {
        if (k == kw) return true;
    }
    return false;
}

namespace template_goo {
/// \return true if type Type is in the Candidates list.
template <typename Type>
constexpr bool type_in_list() {
    return false;
}

template <typename Type, typename Candidate, typename... Rest>
constexpr bool type_in_list() {
    return std::is_same<Type, Candidate>::value || type_in_list<Type, Rest...>();
}
}  // namespace template_goo

template <typename... Nodes>
template <typename Node>
void union_ptr_t<Nodes...>::operator=(std::unique_ptr<Node> n) {
    static_assert(template_goo::type_in_list<Node, Nodes...>(),
                  "Cannot construct from this node type");
    contents.reset(n.release());
}

template <typename... Nodes>
template <typename Node>
union_ptr_t<Nodes...>::union_ptr_t(std::unique_ptr<Node> n) : contents(n.release()) {
    static_assert(template_goo::type_in_list<Node, Nodes...>(),
                  "Cannot construct from this node type");
}

/**
 * A node visitor is like a field visitor, but adapted to only visit actual nodes, as const
 * references. It calls the visit() function of its visitor with a const reference to each node
 * found under a given node.
 *
 * Example:
 * struct MyNodeVisitor {
 *    template <typename Node>
 *    void visit(const Node &n) {...}
 * };
 */
template <typename NodeVisitor>
class node_visitation_t : noncopyable_t {
   public:
    explicit node_visitation_t(NodeVisitor &v, bool reverse = false) : v_(v), reverse_(reverse) {}

    // Visit the (direct) child nodes of a given node.
    template <typename Node>
    void accept_children_of(const Node &n) {
        // We play fast and loose with const to avoid having to duplicate our FIELDS macros.
        const_cast<Node &>(n).accept(*this, reverse_);
    }

    // Visit the (direct) child nodes of a given node.
    void accept_children_of(const node_t *n) {
        const_cast<node_t *>(n)->base_accept(*this, reverse_);
    }

    // Invoke visit() on our visitor for a given node, resolving that node's type.
    void accept(const node_t *n) {
        assert(n && "Node should not be null");
        switch (n->type) {
#define ELEM(T)                      \
    case type_t::T:                  \
        v_.visit(*(n->as<T##_t>())); \
        break;
#include "ast_node_types.inc"
        }
    }

    // Here is our field visit implementations which adapt to the node visiting.

    // Direct embeddings.
    template <typename Node>
    void visit_node_field(const Node &node) {
        v_.visit(node);
    }

    // Pointer embeddings.
    template <typename Node>
    void visit_pointer_field(const Node *ptr) {
        v_.visit(*ptr);
    }

    // List embeddings.
    template <typename List>
    void visit_list_field(const List &list) {
        v_.visit(list);
    }

    // Optional pointers get visited if not null.
    template <typename Node>
    void visit_optional_field(optional_t<Node> &node) {
        if (node.contents) v_.visit(*node.contents);
    }

    // Define our custom implementations of non-node fields.
    // Union pointers just dispatch to the generic one.
    template <typename... Types>
    void visit_union_field(union_ptr_t<Types...> &ptr) {
        assert(ptr && "Should not have null ptr");
        this->accept(ptr.contents.get());
    }

    void will_visit_fields_of(node_t &) {}
    void did_visit_fields_of(node_t &) {}

   private:
    // Our adapted visitor.
    NodeVisitor &v_;

    // Whether to iterate in reverse order.
    const bool reverse_;
};

// Type-deducing helper.
template <typename NodeVisitor>
node_visitation_t<NodeVisitor> node_visitor(NodeVisitor &nv, bool reverse = false) {
    return node_visitation_t<NodeVisitor>(nv, reverse);
}

// A way to visit nodes iteratively.
// This is pre-order. Each node is visited before its children.
// Example:
//    traversal_t tv(start);
//    while (const node_t *node = tv.next()) {...}
class traversal_t {
   public:
    // Construct starting with a node
    traversal_t(const node_t *n) {
        assert(n && "Should not have null node");
        push(n);
    }

    // \return the next node, or nullptr if exhausted.
    const node_t *next() {
        if (stack_.empty()) return nullptr;
        const node_t *node = stack_.back();
        stack_.pop_back();

        // We want to visit in reverse order so the first child ends up on top of the stack.
        node_visitor(*this, true /* reverse */).accept_children_of(node);
        return node;
    }

   private:
    // Callback for node_visitation_t.
    void visit(const node_t &node) { push(&node); }

    // Construct an empty visitor, used for iterator support.
    traversal_t() = default;

    // Append a node.
    void push(const node_t *n) {
        assert(n && "Should not push null node");
        stack_.push_back(n);
    }

    // Stack of nodes.
    std::vector<const node_t *> stack_{};

    friend class ast_t;
    friend class node_visitation_t<traversal_t>;
};

/// The ast type itself.
class ast_t : noncopyable_t {
   public:
    using source_range_list_t = std::vector<source_range_t>;

    /// Construct an ast by parsing \p src as a job list.
    /// The ast attempts to produce \p type as the result.
    /// \p type may only be job_list or freestanding_argument_list.
    static ast_t parse(const wcstring &src, parse_tree_flags_t flags = parse_flag_none,
                       parse_error_list_t *out_errors = nullptr);

    /// Like parse(), but constructs a freestanding_argument_list.
    static ast_t parse_argument_list(const wcstring &src,
                                     parse_tree_flags_t flags = parse_flag_none,
                                     parse_error_list_t *out_errors = nullptr);

    /// \return a traversal, allowing iteration over the nodes.
    traversal_t walk() const { return traversal_t{top()}; }

    /// \return the top node. This has the type requested in the 'parse' method.
    const node_t *top() const { return top_.get(); }

    /// \return whether any errors were encountered during parsing.
    bool errored() const { return any_error_; }

    /// \return a textual representation of the tree.
    /// Pass the original source as \p orig.
    wcstring dump(const wcstring &orig) const;

    /// Extra source ranges.
    /// These are only generated if the corresponding flags are set.
    struct extras_t {
        /// Set of comments, sorted by offset.
        source_range_list_t comments;

        /// Set of semicolons, sorted by offset.
        source_range_list_t semis;

        /// Set of error ranges, sorted by offset.
        source_range_list_t errors;
    };

    /// Access the set of extraneous source ranges.
    const extras_t &extras() const { return extras_; }

    /// Iterator support.
    class iterator {
       public:
        using iterator_category = std::input_iterator_tag;
        using difference_type = void;
        using value_type = node_t;
        using pointer = const node_t *;
        using reference = const node_t &;

        bool operator==(const iterator &rhs) { return current_ == rhs.current_; }
        bool operator!=(const iterator &rhs) { return !(*this == rhs); }

        iterator &operator++() {
            current_ = v_.next();
            return *this;
        }

        const node_t &operator*() const { return *current_; }

       private:
        explicit iterator(const node_t *start) : v_(start), current_(v_.next()) {}
        iterator() = default;

        traversal_t v_{};
        const node_t *current_{};
        friend ast_t;
    };

    iterator begin() const { return iterator{top()}; }
    iterator end() const { return iterator{}; }

    ast_t(ast_t &&) = default;
    ast_t &operator=(ast_t &&) = default;

   private:
    ast_t() = default;

    // Shared parsing code that takes the top type.
    static ast_t parse_from_top(const wcstring &src, parse_tree_flags_t parse_flags,
                                parse_error_list_t *out_errors, type_t top_type);

    // The top node.
    // Its type depends on what was requested to parse.
    node_unique_ptr_t top_{};

    /// Whether any errors were encountered during parsing.
    bool any_error_{false};

    /// Extra fields.
    extras_t extras_{};
};

}  // namespace ast
#endif  // FISH_AST_H
