mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-04 17:18:45 +00:00
14d2a6d8ff
Let's hope this doesn't causes build failures for e.g. musl: I just know it's good on macOS and our Linux CI. It's been a long time. One fix this brings, is I discovered we #include assert.h or cassert in a lot of places. If those ever happen to be in a file that doesn't include common.h, or we are before common.h gets included, we're unawaringly working with the system 'assert' macro again, which may get disabled for debug builds or at least has different behavior on crash. We undef 'assert' and redefine it in common.h. Those were all eliminated, except in one catch-22 spot for maybe.h: it can't include common.h. A fix might be to make a fish_assert.h that *usually* common.h exports.
1043 lines
33 KiB
C++
1043 lines
33 KiB
C++
// Programmatic representation of fish grammar.
|
|
|
|
#ifndef FISH_AST_H
|
|
#define FISH_AST_H
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <initializer_list>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "common.h"
|
|
#include "maybe.h"
|
|
#include "parse_constants.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 categories:
|
|
*
|
|
* - 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;
|
|
|
|
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);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
return;
|
|
}
|
|
|
|
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
|