Remove the old parser bits

Now that everything has been migrated to the new AST, remove as much of
the parse_tree bits as possible
This commit is contained in:
ridiculousfish 2020-07-02 14:51:45 -07:00
parent 3534c07584
commit 0c22f67bde
23 changed files with 14 additions and 2575 deletions

View File

@ -114,10 +114,10 @@ set(FISH_SRCS
src/fallback.cpp src/fish_version.cpp src/function.cpp src/highlight.cpp
src/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp src/intern.cpp
src/io.cpp src/iothread.cpp src/kill.cpp src/output.cpp src/pager.cpp
src/parse_execution.cpp src/parse_productions.cpp src/parse_tree.cpp
src/parse_execution.cpp src/parse_tree.cpp
src/parse_util.cpp src/parser.cpp src/parser_keywords.cpp src/path.cpp
src/postfork.cpp src/proc.cpp src/reader.cpp src/sanity.cpp src/screen.cpp
src/signal.cpp src/tinyexpr.cpp src/tnode.cpp src/tokenizer.cpp src/utf8.cpp src/util.cpp
src/signal.cpp src/tinyexpr.cpp src/tokenizer.cpp src/utf8.cpp src/util.cpp
src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp src/wutil.cpp
src/future_feature_flags.cpp src/redirection.cpp src/topic_monitor.cpp
src/flog.cpp src/trace.cpp src/timer.cpp src/null_terminated_array.cpp

View File

@ -45,7 +45,6 @@
#include "path.h"
#include "proc.h"
#include "reader.h"
#include "tnode.h"
#include "util.h"
#include "wcstringutil.h"
#include "wildcard.h"

View File

@ -46,7 +46,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#include "parse_constants.h"
#include "parse_util.h"
#include "print_help.h"
#include "tnode.h"
#include "wutil.h" // IWYU pragma: keep
// The number of spaces per indent isn't supposed to be configurable.

View File

@ -76,7 +76,6 @@
#include "signal.h"
#include "termsize.h"
#include "timer.h"
#include "tnode.h"
#include "tokenizer.h"
#include "topic_monitor.h"
#include "utf8.h"

View File

@ -64,9 +64,6 @@ class category_list_t {
category_t exec_fork{L"exec-fork", L"Calls to fork()"};
category_t output_invalid{L"output-invalid", L"Trying to print invalid output"};
category_t parse_productions{L"parse-productions", L"Resolving tokens"};
category_t parse_productions_chatty{L"parse-productions-chatty",
L"Resolving tokens (chatty messages)"};
category_t ast_construction{L"ast-construction", L"Parsing fish AST"};
category_t proc_job_run{L"proc-job-run", L"Jobs getting started or continued"};

View File

@ -11,7 +11,6 @@
#include "env.h"
#include "event.h"
#include "parse_tree.h"
#include "tnode.h"
class parser_t;

View File

@ -45,7 +45,6 @@
#include "parser.h"
#include "path.h"
#include "reader.h"
#include "tnode.h"
#include "wcstringutil.h"
#include "wildcard.h" // IWYU pragma: keep
#include "wutil.h" // IWYU pragma: keep

View File

@ -6,7 +6,6 @@
#include "common.h"
#define PARSE_ASSERT(a) assert(a)
#define PARSER_DIE() \
do { \
FLOG(error, L"Parser dying!"); \
@ -27,44 +26,7 @@ struct source_range_t {
// IMPORTANT: If the following enum table is modified you must also update token_enum_map below.
enum parse_token_type_t : uint8_t {
token_type_invalid = 1,
// Non-terminal tokens
symbol_job_list,
symbol_job_conjunction,
symbol_job_conjunction_continuation,
symbol_job_decorator,
symbol_job,
symbol_job_continuation,
symbol_statement,
symbol_block_statement,
symbol_block_header,
symbol_for_header,
symbol_while_header,
symbol_begin_header,
symbol_function_header,
symbol_if_statement,
symbol_if_clause,
symbol_else_clause,
symbol_else_continuation,
symbol_switch_statement,
symbol_case_item_list,
symbol_case_item,
symbol_not_statement,
symbol_decorated_statement,
symbol_plain_statement,
symbol_variable_assignment,
symbol_variable_assignments,
symbol_arguments_or_redirections_list,
symbol_andor_job_list,
symbol_argument_list,
// Freestanding argument lists are parsed from the argument list supplied to 'complete -a'.
// They are not generated by parse trees rooted in symbol_job_list.
symbol_freestanding_argument_list,
symbol_argument,
symbol_redirection,
symbol_optional_background,
symbol_optional_newlines,
symbol_optional_time,
symbol_end_command,
// Terminal types.
parse_token_type_string,
parse_token_type_pipe,
@ -79,13 +41,6 @@ enum parse_token_type_t : uint8_t {
parse_special_type_parse_error,
parse_special_type_tokenizer_error,
parse_special_type_comment,
LAST_TOKEN_TYPE = parse_special_type_comment,
FIRST_TERMINAL_TYPE = parse_token_type_string,
LAST_TERMINAL_TYPE = parse_token_type_terminate,
LAST_TOKEN_OR_SYMBOL = parse_token_type_terminate,
FIRST_PARSE_TOKEN_TYPE = parse_token_type_string,
LAST_PARSE_TOKEN_TYPE = parse_token_type_end
};
const enum_map<parse_token_type_t> token_enum_map[] = {
@ -100,9 +55,6 @@ const enum_map<parse_token_type_t> token_enum_map[] = {
{parse_token_type_andand, L"parse_token_type_andand"},
{parse_token_type_oror, L"parse_token_type_oror"},
{parse_token_type_terminate, L"parse_token_type_terminate"},
// Define all symbols
#define ELEM(sym) {symbol_##sym, L"symbol_" #sym},
#include "parse_grammar_elements.inc"
{token_type_invalid, L"token_type_invalid"},
{token_type_invalid, nullptr}};
#define token_enum_map_len (sizeof token_enum_map / sizeof *token_enum_map)
@ -158,7 +110,7 @@ const enum_map<parse_keyword_t> keyword_enum_map[] = {{parse_keyword_t::kw_excla
// Node tag values.
// Statement decorations, stored in node tag.
// Statement decorations.
enum parse_statement_decoration_t {
parse_statement_decoration_none,
parse_statement_decoration_command,
@ -166,19 +118,6 @@ enum parse_statement_decoration_t {
parse_statement_decoration_exec,
};
// Job decorations, stored in node tag.
enum parse_job_decoration_t {
parse_job_decoration_none,
parse_job_decoration_and,
parse_job_decoration_or,
};
// Whether a statement is backgrounded.
enum parse_optional_background_t { parse_no_background, parse_background };
// Whether a job is prefixed with "time".
enum parse_optional_time_t { parse_optional_time_no_time, parse_optional_time_time };
// Parse error code list.
enum parse_error_code_t {
parse_error_none,
@ -255,6 +194,13 @@ wcstring token_type_user_presentable_description(parse_token_type_t type,
/// errors in a substring of a larger source buffer.
void parse_error_offset_source_start(parse_error_list_t *errors, size_t amt);
// The location of a pipeline.
enum class pipeline_position_t {
none, // not part of a pipeline
first, // first command in a pipeline
subsequent // second or further command in a pipeline
};
/// Maximum number of function calls.
#define FISH_MAX_STACK_DEPTH 128

View File

@ -1,11 +1,4 @@
// Provides the "linkage" between a parse_node_tree_t and actual execution structures (job_t, etc.)
//
// A note on error handling: fish has two kind of errors, fatal parse errors non-fatal runtime
// errors. A fatal error prevents execution of the entire file, while a non-fatal error skips that
// job.
//
// Non-fatal errors are printed as soon as they are encountered; otherwise you would have to wait
// for the execution to finish to see them.
// Provides the "linkage" between an ast and actual execution structures (job_t, etc.)
#include "config.h" // IWYU pragma: keep
#include "parse_execution.h"
@ -1416,19 +1409,6 @@ end_execution_reason_t parse_execution_context_t::run_job_conjunction(
return result;
}
bool parse_execution_context_t::should_skip(parse_job_decoration_t type) const {
switch (type) {
case parse_job_decoration_and:
// AND. Skip if the last job failed.
return parser->get_last_status() != 0;
case parse_job_decoration_or:
// OR. Skip if the last job succeeded.
return parser->get_last_status() == 0;
default:
return false;
}
}
end_execution_reason_t parse_execution_context_t::test_and_run_1_job_conjunction(
const ast::job_conjunction_t &jc, const block_t *associated_block) {
// Test this job conjunction if it has an 'and' or 'or' decorator.

View File

@ -1,4 +1,4 @@
// Provides the "linkage" between a parse_node_tree_t and actual execution structures (job_t, etc.).
// Provides the "linkage" between an ast and actual execution structures (job_t, etc.).
#ifndef FISH_PARSE_EXECUTION_H
#define FISH_PARSE_EXECUTION_H
@ -79,9 +79,6 @@ class parse_execution_context_t {
end_execution_reason_t expand_command(const ast::decorated_statement_t &statement,
wcstring *out_cmd, wcstring_list_t *out_args) const;
/// Return whether we should skip a job with the given bool statement type.
bool should_skip(parse_job_decoration_t type) const;
/// Indicates whether a job is a simple block (one block, no redirections).
bool job_is_simple_block(const ast::job_t &job) const;

View File

@ -1,401 +0,0 @@
// Programmatic representation of fish grammar
#ifndef FISH_PARSE_GRAMMAR_H
#define FISH_PARSE_GRAMMAR_H
#include <array>
#include <tuple>
#include <type_traits>
#include "parse_constants.h"
#include "tokenizer.h"
struct parse_token_t;
typedef uint8_t parse_node_tag_t;
using parse_node_tag_t = uint8_t;
struct parse_token_t;
namespace grammar {
using production_element_t = uint8_t;
enum {
// The maximum length of any seq production.
MAX_PRODUCTION_LENGTH = 6
};
// Define primitive types.
template <enum parse_token_type_t Token>
struct primitive {
using type_tuple = std::tuple<>;
static constexpr parse_token_type_t token = Token;
static constexpr production_element_t element() { return Token; }
};
using tok_end = primitive<parse_token_type_end>;
using tok_string = primitive<parse_token_type_string>;
using tok_pipe = primitive<parse_token_type_pipe>;
using tok_background = primitive<parse_token_type_background>;
using tok_redirection = primitive<parse_token_type_redirection>;
using tok_andand = primitive<parse_token_type_andand>;
using tok_oror = primitive<parse_token_type_oror>;
// Define keyword types.
template <parse_keyword_t Keyword>
struct keyword {
using type_tuple = std::tuple<>;
static constexpr parse_token_type_t token = parse_token_type_string;
static constexpr production_element_t element() {
// Convert a parse_keyword_t enum to a production_element_t enum.
return static_cast<uint32_t>(Keyword) + LAST_TOKEN_OR_SYMBOL + 1;
}
};
// Define special types.
// Comments are not emitted as part of productions, but specially by the parser.
struct comment {
using type_tuple = std::tuple<>;
static constexpr parse_token_type_t token = parse_special_type_comment;
};
// Forward declare all the symbol types.
#define ELEM(T) struct T;
#include "parse_grammar_elements.inc"
// A production is a sequence of production elements.
// +1 to hold the terminating token_type_invalid
template <size_t Count>
using production_t = std::array<const production_element_t, Count + 1>;
// This is an ugly hack to avoid ODR violations
// Given some type, return a pointer to its production.
template <typename T>
const production_element_t *production_for() {
static constexpr auto prod = T::production;
return prod.data();
}
// Get some production element.
template <typename T>
constexpr production_element_t element() {
return T::element();
}
// Template goo.
namespace detail {
template <typename T, typename Tuple>
struct tuple_contains;
template <typename T>
struct tuple_contains<T, std::tuple<>> : std::false_type {};
template <typename T, typename U, typename... Ts>
struct tuple_contains<T, std::tuple<U, Ts...>> : tuple_contains<T, std::tuple<Ts...>> {};
template <typename T, typename... Ts>
struct tuple_contains<T, std::tuple<T, Ts...>> : std::true_type {};
struct void_type {
using type = void;
};
// Support for checking whether the index N is valid for T::type_tuple.
template <size_t N, typename T>
static constexpr bool index_valid() {
return N < std::tuple_size<typename T::type_tuple>::value;
}
// Get the Nth type of T::type_tuple.
template <size_t N, typename T>
using tuple_element = std::tuple_element<N, typename T::type_tuple>;
// Get the Nth type of T::type_tuple, or void if N is out of bounds.
template <size_t N, typename T>
using tuple_element_or_void =
typename std::conditional<index_valid<N, T>(), tuple_element<N, T>, void_type>::type::type;
// Make a tuple by mapping the Nth item of a list of 'seq's.
template <size_t N, typename... Ts>
struct tuple_nther {
// A tuple of the Nth types of tuples (or voids).
using type = std::tuple<tuple_element_or_void<N, Ts>...>;
};
// Given a list of Options, each one a seq, check to see if any of them contain type Desired at
// index Index.
template <typename Desired, size_t Index, typename... Options>
inline constexpr bool type_possible() {
using nths = typename tuple_nther<Index, Options...>::type;
return tuple_contains<Desired, nths>::value;
}
} // namespace detail
// Partial specialization hack.
#define ELEM(T) \
template <> \
constexpr production_element_t element<T>() { \
return symbol_##T; \
}
#include "parse_grammar_elements.inc"
// Empty produces nothing.
struct empty {
using type_tuple = std::tuple<>;
static constexpr production_t<0> production = {{token_type_invalid}};
static const production_element_t *resolve(const parse_token_t &, const parse_token_t &,
parse_node_tag_t *) {
return production_for<empty>();
}
};
// Sequence represents a list of (at least two) productions.
template <class T0, class... Ts>
struct seq {
static constexpr production_t<1 + sizeof...(Ts)> production = {
{element<T0>(), element<Ts>()..., token_type_invalid}};
static_assert(1 + sizeof...(Ts) <= MAX_PRODUCTION_LENGTH, "MAX_PRODUCTION_LENGTH too small");
using type_tuple = std::tuple<T0, Ts...>;
template <typename Desired, size_t Index>
static constexpr bool type_possible() {
using element_t = detail::tuple_element_or_void<Index, seq>;
return std::is_same<Desired, element_t>::value;
}
static const production_element_t *resolve(const parse_token_t &, const parse_token_t &,
parse_node_tag_t *) {
return production_for<seq>();
}
};
template <class... Args>
using produces_sequence = seq<Args...>;
// Ergonomic way to create a production for a single element.
template <class T>
using single = seq<T>;
template <class T>
using produces_single = single<T>;
// Alternative represents a choice.
struct alternative {};
// Following are the grammar productions.
#define BODY(T) static constexpr parse_token_type_t token = symbol_##T;
#define DEF(T) struct T : public
#define DEF_ALT(T) struct T : public alternative
#define ALT_BODY(T, ...) \
BODY(T) \
using type_tuple = std::tuple<>; \
template <typename Desired, size_t Index> \
static constexpr bool type_possible() { \
return detail::type_possible<Desired, Index, __VA_ARGS__>(); \
} \
static const production_element_t *resolve(const parse_token_t &, const parse_token_t &, \
parse_node_tag_t *)
// A job_list is a list of job_conjunctions, separated by semicolons or newlines
DEF_ALT(job_list) {
using normal = seq<job_decorator, job_conjunction, job_list>;
using empty_line = seq<tok_end, job_list>;
using empty = grammar::empty;
ALT_BODY(job_list, normal, empty_line, empty);
};
// Job decorators are 'and' and 'or'. These apply to the whole job.
DEF_ALT(job_decorator) {
using ands = single<keyword<parse_keyword_t::kw_and>>;
using ors = single<keyword<parse_keyword_t::kw_or>>;
using empty = grammar::empty;
ALT_BODY(job_decorator, ands, ors, empty);
};
// A job_conjunction is a job followed by a continuation.
DEF(job_conjunction) produces_sequence<job, job_conjunction_continuation>{BODY(job_conjunction)};
DEF_ALT(job_conjunction_continuation) {
using andands = seq<tok_andand, optional_newlines, job_conjunction>;
using orors = seq<tok_oror, optional_newlines, job_conjunction>;
using empty = grammar::empty;
ALT_BODY(job_conjunction_continuation, andands, orors, empty);
};
/// The time builtin.
DEF_ALT(optional_time) {
using empty = grammar::empty;
using time = single<keyword<parse_keyword_t::kw_time>>;
ALT_BODY(optional_time, empty, time);
};
// 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). To represent "non-empty", we require a
// statement, followed by a possibly empty job_continuation, and then optionally a background
// specifier '&'
DEF(job)
produces_sequence<optional_time, variable_assignments, statement, job_continuation,
optional_background>{BODY(job)};
DEF_ALT(job_continuation) {
using piped =
seq<tok_pipe, optional_newlines, variable_assignments, statement, job_continuation>;
using empty = grammar::empty;
ALT_BODY(job_continuation, piped, empty);
};
// A list of assignments like HOME=$PWD
DEF_ALT(variable_assignments) {
using empty = grammar::empty;
using var = seq<variable_assignment, variable_assignments>;
ALT_BODY(variable_assignments, empty, var);
};
// A string token like VAR=value
DEF(variable_assignment) produces_single<tok_string>{BODY(variable_assignment)};
// A statement is a normal command, or an if / while / etc
DEF_ALT(statement) {
using nots = single<not_statement>;
using block = single<block_statement>;
using ifs = single<if_statement>;
using switchs = single<switch_statement>;
using decorated = single<decorated_statement>;
ALT_BODY(statement, nots, block, ifs, switchs, decorated);
};
// A block is a conditional, loop, or begin/end
DEF(if_statement)
produces_sequence<if_clause, else_clause, end_command, arguments_or_redirections_list>{
BODY(if_statement)};
DEF(if_clause)
produces_sequence<keyword<parse_keyword_t::kw_if>, job_conjunction, tok_end, andor_job_list,
job_list>{BODY(if_clause)};
DEF_ALT(else_clause) {
using empty = grammar::empty;
using else_cont = seq<keyword<parse_keyword_t::kw_else>, else_continuation>;
ALT_BODY(else_clause, empty, else_cont);
};
DEF_ALT(else_continuation) {
using else_if = seq<if_clause, else_clause>;
using else_only = seq<tok_end, job_list>;
ALT_BODY(else_continuation, else_if, else_only);
};
DEF(switch_statement)
produces_sequence<keyword<parse_keyword_t::kw_switch>, argument, tok_end, case_item_list,
end_command, arguments_or_redirections_list>{BODY(switch_statement)};
DEF_ALT(case_item_list) {
using empty = grammar::empty;
using case_items = seq<case_item, case_item_list>;
using blank_line = seq<tok_end, case_item_list>;
ALT_BODY(case_item_list, empty, case_items, blank_line);
};
DEF(case_item)
produces_sequence<keyword<parse_keyword_t::kw_case>, argument_list, tok_end, job_list>{
BODY(case_item)};
DEF(block_statement)
produces_sequence<block_header, job_list, end_command, arguments_or_redirections_list>{
BODY(block_statement)};
DEF_ALT(block_header) {
using forh = single<for_header>;
using whileh = single<while_header>;
using funch = single<function_header>;
using beginh = single<begin_header>;
ALT_BODY(block_header, forh, whileh, funch, beginh);
};
DEF(for_header)
produces_sequence<keyword<parse_keyword_t::kw_for>, tok_string, keyword<parse_keyword_t::kw_in>,
argument_list, tok_end>{BODY(for_header)};
DEF(while_header)
produces_sequence<keyword<parse_keyword_t::kw_while>, job_conjunction, tok_end, andor_job_list>{
BODY(while_header)};
DEF(begin_header) produces_single<keyword<parse_keyword_t::kw_begin>>{BODY(begin_header)};
// Functions take arguments, and require at least one (the name). No redirections allowed.
DEF(function_header)
produces_sequence<keyword<parse_keyword_t::kw_function>, argument, argument_list, tok_end>{
BODY(function_header)};
DEF_ALT(not_statement) {
using nots =
seq<keyword<parse_keyword_t::kw_not>, variable_assignments, optional_time, statement>;
using exclams =
seq<keyword<parse_keyword_t::kw_exclam>, variable_assignments, optional_time, statement>;
ALT_BODY(not_statement, nots, exclams);
};
// An andor_job_list is zero or more job lists, where each starts with an `and` or `or` boolean
// statement.
DEF_ALT(andor_job_list) {
using empty = grammar::empty;
using andor_job = seq<job_decorator, job_conjunction, andor_job_list>;
using empty_line = seq<tok_end, andor_job_list>;
ALT_BODY(andor_job_list, empty, andor_job, empty_line);
};
// A decorated_statement is a command with a list of arguments_or_redirections, possibly with
// "builtin" or "command" or "exec"
DEF_ALT(decorated_statement) {
using plains = single<plain_statement>;
using cmds = seq<keyword<parse_keyword_t::kw_command>, plain_statement>;
using builtins = seq<keyword<parse_keyword_t::kw_builtin>, plain_statement>;
using execs = seq<keyword<parse_keyword_t::kw_exec>, plain_statement>;
ALT_BODY(decorated_statement, plains, cmds, builtins, execs);
};
DEF(plain_statement)
produces_sequence<tok_string, arguments_or_redirections_list>{BODY(plain_statement)};
DEF_ALT(argument_list) {
using empty = grammar::empty;
using arg = seq<argument, argument_list>;
ALT_BODY(argument_list, empty, arg);
};
DEF_ALT(arguments_or_redirections_list) {
using empty = grammar::empty;
using arg = seq<argument, arguments_or_redirections_list>;
using redir = seq<redirection, arguments_or_redirections_list>;
ALT_BODY(arguments_or_redirections_list, empty, arg, redir);
};
DEF(argument) produces_single<tok_string>{BODY(argument)};
DEF(redirection) produces_sequence<tok_redirection, tok_string>{BODY(redirection)};
DEF_ALT(optional_background) {
using empty = grammar::empty;
using background = single<tok_background>;
ALT_BODY(optional_background, empty, background);
};
DEF(end_command) produces_single<keyword<parse_keyword_t::kw_end>>{BODY(end_command)};
// Note optional_newlines only allows newline-style tok_end, not semicolons.
DEF_ALT(optional_newlines) {
using empty = grammar::empty;
using newlines = seq<tok_end, optional_newlines>;
ALT_BODY(optional_newlines, empty, newlines);
};
// A freestanding_argument_list is equivalent to a normal argument list, except it may contain
// TOK_END (newlines, and even semicolons, for historical reasons)
DEF_ALT(freestanding_argument_list) {
using empty = grammar::empty;
using arg = seq<argument, freestanding_argument_list>;
using semicolon = seq<tok_end, freestanding_argument_list>;
ALT_BODY(freestanding_argument_list, empty, arg, semicolon);
};
} // namespace grammar
#endif

View File

@ -1,37 +0,0 @@
// Define ELEM before including this file.
ELEM(job_list)
ELEM(job)
ELEM(job_decorator)
ELEM(job_conjunction)
ELEM(job_conjunction_continuation)
ELEM(job_continuation)
ELEM(statement)
ELEM(if_statement)
ELEM(if_clause)
ELEM(else_clause)
ELEM(else_continuation)
ELEM(switch_statement)
ELEM(case_item_list)
ELEM(case_item)
ELEM(block_statement)
ELEM(block_header)
ELEM(for_header)
ELEM(while_header)
ELEM(begin_header)
ELEM(function_header)
ELEM(not_statement)
ELEM(andor_job_list)
ELEM(decorated_statement)
ELEM(variable_assignment)
ELEM(variable_assignments)
ELEM(plain_statement)
ELEM(argument_list)
ELEM(arguments_or_redirections_list)
ELEM(argument)
ELEM(redirection)
ELEM(optional_background)
ELEM(optional_newlines)
ELEM(optional_time)
ELEM(end_command)
ELEM(freestanding_argument_list)
#undef ELEM

View File

@ -1,466 +0,0 @@
#include "config.h" // IWYU pragma: keep
#include "parse_productions.h"
#include <stdio.h>
#include "common.h"
#include "flog.h"
#include "parse_constants.h"
#include "parse_grammar.h"
#include "parse_tree.h"
using namespace parse_productions;
using namespace grammar;
#define NO_PRODUCTION nullptr
// Herein are encoded the productions for our LL2 fish grammar.
//
// Each symbol (e.g. symbol_job_list) has a corresponding function (e.g. resolve_job_list). The
// function accepts two tokens, representing the first and second lookahead, and returns a
// production representing the rule, or NULL on error. There is also a tag value which is returned
// by reference; the tag is a sort of node annotation.
//
// Productions are generally a static const array, and we return a pointer to the array (yes,
// really).
#define RESOLVE(SYM) \
const production_element_t *SYM::resolve( \
const parse_token_t &token1, const parse_token_t &token2, parse_node_tag_t *out_tag)
/// A job_list is a list of jobs, separated by semicolons or newlines.
RESOLVE(job_list) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.type) {
case parse_token_type_string: {
// Some keywords are special.
switch (token1.keyword) {
case parse_keyword_t::kw_end:
case parse_keyword_t::kw_else:
case parse_keyword_t::kw_case: {
return production_for<empty>(); // end this job list
}
default: {
return production_for<normal>(); // normal string
}
}
}
case parse_token_type_pipe:
case parse_token_type_redirection:
case parse_token_type_background: {
return production_for<normal>();
}
case parse_token_type_end: {
return production_for<empty_line>();
}
case parse_token_type_terminate: {
return production_for<empty>(); // no more commands, just transition to empty
}
default: {
return NO_PRODUCTION;
}
}
}
// A job decorator is AND or OR
RESOLVE(job_decorator) {
// If it's followed by --help, it's not a decoration.
if (token2.is_help_argument) {
*out_tag = parse_job_decoration_none;
return production_for<empty>();
}
switch (token1.keyword) {
case parse_keyword_t::kw_and: {
*out_tag = parse_job_decoration_and;
return production_for<ands>();
}
case parse_keyword_t::kw_or: {
*out_tag = parse_job_decoration_or;
return production_for<ors>();
}
default: {
*out_tag = parse_job_decoration_none;
return production_for<empty>();
}
}
}
RESOLVE(job_conjunction_continuation) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.type) {
case parse_token_type_andand:
*out_tag = parse_job_decoration_and;
return production_for<andands>();
case parse_token_type_oror:
*out_tag = parse_job_decoration_or;
return production_for<orors>();
default:
return production_for<empty>();
}
}
RESOLVE(job_continuation) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.type) {
case parse_token_type_pipe: {
return production_for<piped>(); // pipe, continuation
}
default: {
return production_for<empty>(); // not a pipe, no job continuation
}
}
}
// A statement is a normal command, or an if / while / and etc.
RESOLVE(statement) {
UNUSED(out_tag);
// The only block-like builtin that takes any parameters is 'function' So go to decorated
// statements if the subsequent token looks like '--'. The logic here is subtle:
//
// If we are 'begin', then we expect to be invoked with no arguments.
// If we are 'function', then we are a non-block if we are invoked with -h or --help
// If we are anything else, we require an argument, so do the same thing if the subsequent token
// is a statement terminator.
if (token1.type == parse_token_type_string) {
// If we are a function, then look for help arguments. Otherwise, if the next token looks
// like an option (starts with a dash), then parse it as a decorated statement.
if (token1.keyword == parse_keyword_t::kw_function && token2.is_help_argument) {
return production_for<decorated>();
} else if (token1.keyword != parse_keyword_t::kw_function && token2.has_dash_prefix) {
return production_for<decorated>();
}
// Likewise if the next token doesn't look like an argument at all. This corresponds to e.g.
// a "naked if".
bool naked_invocation_invokes_help = (token1.keyword != parse_keyword_t::kw_begin &&
token1.keyword != parse_keyword_t::kw_end);
if (naked_invocation_invokes_help &&
(token2.type == parse_token_type_end || token2.type == parse_token_type_terminate)) {
return production_for<decorated>();
}
}
switch (token1.type) {
case parse_token_type_string: {
switch (token1.keyword) {
case parse_keyword_t::kw_not:
case parse_keyword_t::kw_exclam: {
return production_for<nots>();
}
case parse_keyword_t::kw_for:
case parse_keyword_t::kw_while:
case parse_keyword_t::kw_function:
case parse_keyword_t::kw_begin: {
return production_for<block>();
}
case parse_keyword_t::kw_if: {
return production_for<ifs>();
}
case parse_keyword_t::kw_else: {
return NO_PRODUCTION;
}
case parse_keyword_t::kw_switch: {
return production_for<switchs>();
}
case parse_keyword_t::kw_end: {
return NO_PRODUCTION;
}
// All other keywords fall through to decorated statement.
default: {
return production_for<decorated>();
}
}
}
case parse_token_type_pipe:
case parse_token_type_redirection:
case parse_token_type_background:
case parse_token_type_terminate: {
return NO_PRODUCTION;
}
default: {
return NO_PRODUCTION;
}
}
}
RESOLVE(else_clause) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.keyword) {
case parse_keyword_t::kw_else: {
return production_for<else_cont>();
}
default: {
return production_for<empty>();
}
}
}
RESOLVE(else_continuation) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.keyword) {
case parse_keyword_t::kw_if: {
return production_for<else_if>();
}
default: {
return production_for<else_only>();
}
}
}
RESOLVE(case_item_list) {
UNUSED(token2);
UNUSED(out_tag);
if (token1.keyword == parse_keyword_t::kw_case)
return production_for<case_items>();
else if (token1.type == parse_token_type_end)
return production_for<blank_line>();
else
return production_for<empty>();
}
RESOLVE(not_statement) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.keyword) {
case parse_keyword_t::kw_not:
return production_for<nots>();
case parse_keyword_t::kw_exclam:
return production_for<exclams>();
default:
return NO_PRODUCTION;
}
}
RESOLVE(andor_job_list) {
UNUSED(out_tag);
if (token1.type == parse_token_type_end) {
return production_for<empty_line>();
} else if (token1.keyword == parse_keyword_t::kw_and ||
token1.keyword == parse_keyword_t::kw_or) {
// Check that the argument to and/or is a string that's not help. Otherwise it's either 'and
// --help' or a naked 'and', and not part of this list.
if (token2.type == parse_token_type_string && !token2.is_help_argument) {
return production_for<andor_job>();
}
}
// All other cases end the list.
return production_for<empty>();
}
RESOLVE(argument_list) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.type) {
case parse_token_type_string: {
return production_for<arg>();
}
default: {
return production_for<empty>();
}
}
}
RESOLVE(freestanding_argument_list) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.type) {
case parse_token_type_string: {
return production_for<arg>();
}
case parse_token_type_end: {
return production_for<semicolon>();
}
default: {
return production_for<empty>();
}
}
}
RESOLVE(block_header) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.keyword) {
case parse_keyword_t::kw_for: {
return production_for<forh>();
}
case parse_keyword_t::kw_while: {
return production_for<whileh>();
}
case parse_keyword_t::kw_function: {
return production_for<funch>();
}
case parse_keyword_t::kw_begin: {
return production_for<beginh>();
}
default: {
return NO_PRODUCTION;
}
}
}
RESOLVE(variable_assignments) {
UNUSED(token2);
UNUSED(out_tag);
if (token1.may_be_variable_assignment) {
assert(token1.type == parse_token_type_string);
return production_for<var>();
}
return production_for<empty>();
}
RESOLVE(decorated_statement) {
// and/or are typically parsed in job_conjunction at the beginning of a job
// However they may be reached here through e.g. true && and false.
// Refuse to parse them as a command except for --help. See #6089.
if ((token1.keyword == parse_keyword_t::kw_and || token1.keyword == parse_keyword_t::kw_or) &&
!token2.is_help_argument) {
return NO_PRODUCTION;
}
// If this is e.g. 'command --help' then the command is 'command' and not a decoration. If the
// second token is not a string, then this is a naked 'command' and we should execute it as
// undecorated.
if (token2.type != parse_token_type_string || token2.has_dash_prefix) {
return production_for<plains>();
}
switch (token1.keyword) {
case parse_keyword_t::kw_command: {
*out_tag = parse_statement_decoration_command;
return production_for<cmds>();
}
case parse_keyword_t::kw_builtin: {
*out_tag = parse_statement_decoration_builtin;
return production_for<builtins>();
}
case parse_keyword_t::kw_exec: {
*out_tag = parse_statement_decoration_exec;
return production_for<execs>();
}
default: {
*out_tag = parse_statement_decoration_none;
return production_for<plains>();
}
}
}
RESOLVE(arguments_or_redirections_list) {
UNUSED(token2);
UNUSED(out_tag);
switch (token1.type) {
case parse_token_type_string:
return production_for<arg>();
case parse_token_type_redirection:
return production_for<redir>();
default:
return production_for<empty>();
}
}
RESOLVE(optional_newlines) {
UNUSED(token2);
UNUSED(out_tag);
if (token1.is_newline) return production_for<newlines>();
return production_for<empty>();
}
RESOLVE(optional_background) {
UNUSED(token2);
switch (token1.type) {
case parse_token_type_background: {
*out_tag = parse_background;
return production_for<background>();
}
default: {
*out_tag = parse_no_background;
return production_for<empty>();
}
}
}
RESOLVE(optional_time) {
if (token1.keyword == parse_keyword_t::kw_time && !token2.is_help_argument) {
*out_tag = parse_optional_time_time;
return production_for<time>();
}
*out_tag = parse_optional_time_no_time;
return production_for<empty>();
}
const production_element_t *parse_productions::production_for_token(parse_token_type_t node_type,
const parse_token_t &input1,
const parse_token_t &input2,
parse_node_tag_t *out_tag) {
// this is **extremely** chatty
FLOGF(parse_productions_chatty, L"Resolving production for %ls with input token <%ls>",
token_type_description(node_type), input1.describe().c_str());
// Fetch the function to resolve the list of productions.
const production_element_t *(*resolver)(const parse_token_t &input1, //!OCLINT(unused param)
const parse_token_t &input2, //!OCLINT(unused param)
parse_node_tag_t *out_tag) = //!OCLINT(unused param)
nullptr;
switch (node_type) {
// Handle all of our grammar elements
#define ELEM(SYM) \
case (symbol_##SYM): \
resolver = SYM::resolve; \
break;
#include "parse_grammar_elements.inc"
// Everything else is an error.
case parse_token_type_string:
case parse_token_type_pipe:
case parse_token_type_redirection:
case parse_token_type_background:
case parse_token_type_andand:
case parse_token_type_oror:
case parse_token_type_end:
case parse_token_type_terminate: {
FLOGF(error, L"Terminal token type %ls passed to %s", token_type_description(node_type),
__FUNCTION__);
PARSER_DIE();
break;
}
case parse_special_type_parse_error:
case parse_special_type_tokenizer_error:
case parse_special_type_comment: {
FLOGF(error, L"Special type %ls passed to %s\n", token_type_description(node_type),
__FUNCTION__);
PARSER_DIE();
break;
}
case token_type_invalid: {
FLOGF(error, L"token_type_invalid passed to %s", __FUNCTION__);
PARSER_DIE();
break;
}
}
PARSE_ASSERT(resolver != nullptr);
const production_element_t *result = resolver(input1, input2, out_tag);
if (result == nullptr) {
FLOGF(parse_productions, L"Node type '%ls' has no production for input '%ls' (in %s)",
token_type_description(node_type), input1.describe().c_str(), __FUNCTION__);
}
return result;
}

View File

@ -1,50 +0,0 @@
// Programmatic representation of fish code.
#ifndef FISH_PARSE_TREE_CONSTRUCTION_H
#define FISH_PARSE_TREE_CONSTRUCTION_H
#include <sys/types.h>
#include "ast.h"
#include "parse_constants.h"
struct parse_token_t;
namespace parse_productions {
// A production is an array of unsigned char. Symbols are encoded directly as their symbol value.
// Keywords are encoded with an offset of LAST_TOKEN_OR_SYMBOL + 1. So essentially we glom together
// keywords and symbols.
typedef uint8_t production_element_t;
/// Resolve the type from a production element.
inline parse_token_type_t production_element_type(production_element_t elem) {
if (elem > LAST_TOKEN_OR_SYMBOL) {
return parse_token_type_string;
} else {
return static_cast<parse_token_type_t>(elem);
}
}
/// Resolve the keyword from a production element.
inline parse_keyword_t production_element_keyword(production_element_t elem) {
if (elem > LAST_TOKEN_OR_SYMBOL) {
// First keyword is LAST_TOKEN_OR_SYMBOL + 1.
return static_cast<parse_keyword_t>(elem - LAST_TOKEN_OR_SYMBOL - 1);
} else {
return parse_keyword_t::none;
}
}
/// Check if an element is valid.
inline bool production_element_is_valid(production_element_t elem) {
return elem != token_type_invalid;
}
/// Fetch a production. We are passed two input tokens. The first input token is guaranteed to not
/// be invalid; the second token may be invalid if there's no more tokens. We may also set flags.
const production_element_t *production_for_token(parse_token_type_t node_type,
const parse_token_t &input1,
const parse_token_t &input2, uint8_t *out_tag);
} // namespace parse_productions
#endif

File diff suppressed because it is too large Load Diff

View File

@ -13,15 +13,8 @@
#include "common.h"
#include "maybe.h"
#include "parse_constants.h"
#include "parse_grammar.h"
#include "tokenizer.h"
class parse_node_tree_t;
typedef uint32_t node_offset_t;
#define NODE_OFFSET_INVALID (static_cast<node_offset_t>(-1))
typedef uint32_t source_offset_t;
constexpr source_offset_t SOURCE_OFFSET_INVALID = static_cast<source_offset_t>(-1);
@ -61,8 +54,6 @@ struct parse_token_t {
/// Return a new parse token, advancing the tokenizer.
parse_token_t next_parse_token(tokenizer_t *tok, maybe_t<tok_t> *out_token, wcstring *storage);
wcstring parse_dump_tree(const parse_node_tree_t &nodes, const wcstring &src);
const wchar_t *token_type_description(parse_token_type_t type);
const wchar_t *keyword_description(parse_keyword_t type);
@ -83,129 +74,6 @@ typedef uint8_t parse_node_flags_t;
/// Node-type specific tag value.
typedef uint8_t parse_node_tag_t;
/// Class for nodes of a parse tree. Since there's a lot of these, the size and order of the fields
/// is important.
class parse_node_t {
public:
// Start in the source code.
source_offset_t source_start{SOURCE_OFFSET_INVALID};
// Length of our range in the source code.
source_offset_t source_length{0};
// Parent
node_offset_t parent{NODE_OFFSET_INVALID};
// Children
node_offset_t child_start{0};
// Number of children.
uint8_t child_count{0};
// Type of the node.
enum parse_token_type_t type;
// Keyword associated with node.
enum parse_keyword_t keyword { parse_keyword_t::none };
// Node flags.
parse_node_flags_t flags : 4;
// This is used to store e.g. the statement decoration.
parse_node_tag_t tag : 4;
// Description
wcstring describe() const;
// Constructor
explicit parse_node_t(parse_token_type_t ty) : type(ty), flags(0), tag(0) {}
node_offset_t child_offset(node_offset_t which) const {
PARSE_ASSERT(which < child_count);
return child_start + which;
}
/// Indicate if this node has a range of source code associated with it.
bool has_source() const {
// Should never have a nonempty range with an invalid offset.
assert(this->source_start != SOURCE_OFFSET_INVALID || this->source_length == 0);
return this->source_length > 0;
}
/// Indicate if the node has comment nodes.
bool has_comments() const { return this->flags & parse_node_flag_has_comments; }
/// Indicates if we have a preceding escaped newline.
bool has_preceding_escaped_newline() const {
return this->flags & parse_node_flag_preceding_escaped_nl;
}
source_range_t source_range() const {
assert(has_source());
return {source_start, source_length};
}
/// Gets source for the node, or the empty string if it has no source.
wcstring get_source(const wcstring &str) const {
if (!has_source())
return wcstring();
else
return wcstring(str, this->source_start, this->source_length);
}
/// Returns whether the given location is within the source range or at its end.
bool location_in_or_at_end_of_source_range(size_t loc) const {
return has_source() && source_start <= loc && loc - source_start <= source_length;
}
};
template <typename Type>
class tnode_t;
/// The parse tree itself.
class parse_node_tree_t : public std::vector<parse_node_t> {
public:
parse_node_tree_t() {}
parse_node_tree_t(parse_node_tree_t &&) = default;
parse_node_tree_t &operator=(parse_node_tree_t &&) = default;
parse_node_tree_t(const parse_node_tree_t &) = delete; // no copying
parse_node_tree_t &operator=(const parse_node_tree_t &) = delete; // no copying
// Get the node corresponding to a child of the given node, or NULL if there is no such child.
// If expected_type is provided, assert that the node has that type.
const parse_node_t *get_child(const parse_node_t &parent, node_offset_t which,
parse_token_type_t expected_type = token_type_invalid) const;
// Find the first direct child of the given node of the given type. asserts on failure.
const parse_node_t &find_child(const parse_node_t &parent, parse_token_type_t type) const;
template <typename Type>
tnode_t<Type> find_child(const parse_node_t &parent) const;
// Get the node corresponding to the parent of the given node, or NULL if there is no such
// child. If expected_type is provided, only returns the parent if it is of that type. Note the
// asymmetry: get_child asserts since the children are known, but get_parent does not, since the
// parent may not be known.
const parse_node_t *get_parent(const parse_node_t &node,
parse_token_type_t expected_type = token_type_invalid) const;
// Finds a node containing the given source location. If 'parent' is not NULL, it must be an
// ancestor.
const parse_node_t *find_node_matching_source_location(parse_token_type_t type,
size_t source_loc,
const parse_node_t *parent) const;
// Utilities
/// Given a node, return all of its comment nodes.
std::vector<tnode_t<grammar::comment>> comment_nodes_for_node(const parse_node_t &parent) const;
private:
template <typename Type>
friend class tnode_t;
/// Given a node list (e.g. of type symbol_job_list) and a node type (e.g. symbol_job), return
/// the next element of the given type in that list, and the tail (by reference). Returns NULL
/// if we've exhausted the list.
const parse_node_t *next_node_in_node_list(const parse_node_t &node_list,
parse_token_type_t entry_type,
const parse_node_t **list_tail) const;
};
/// The big entry point. Parse a string, attempting to produce a tree for the given goal type.
bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags,
parse_node_tree_t *output, parse_error_list_t *errors,
parse_token_type_t goal = symbol_job_list);
namespace ast {
class ast_t;
}

View File

@ -23,7 +23,6 @@
#include "parse_constants.h"
#include "parse_util.h"
#include "parser.h"
#include "tnode.h"
#include "tokenizer.h"
#include "wcstringutil.h"
#include "wildcard.h"
@ -1203,7 +1202,6 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
parse_error_list_t *out_errors,
bool allow_incomplete,
parsed_source_ref_t *out_pstree) {
namespace g = grammar;
parse_error_list_t parse_errors;
parser_test_error_bits_t res = 0;

View File

@ -131,7 +131,6 @@ std::vector<int> parse_util_compute_indents(const wcstring &src);
/// incomplete (e.g. an unclosed quote), an error is not returned and the PARSER_TEST_INCOMPLETE bit
/// is set in the return value. If allow_incomplete is not set, then incomplete strings result in an
/// error. If out_pstree is not NULL, the resulting tree is returned by reference.
class parse_node_tree_t;
parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
parse_error_list_t *out_errors = nullptr,
bool allow_incomplete = true,

View File

@ -26,7 +26,6 @@
#include "proc.h"
#include "reader.h"
#include "sanity.h"
#include "tnode.h"
#include "wutil.h" // IWYU pragma: keep
class io_chain_t;

View File

@ -21,7 +21,6 @@
#include "global_safety.h"
#include "io.h"
#include "parse_tree.h"
#include "tnode.h"
#include "topic_monitor.h"
/// Types of processes.

View File

@ -75,7 +75,6 @@
#include "screen.h"
#include "signal.h"
#include "termsize.h"
#include "tnode.h"
#include "tokenizer.h"
#include "wutil.h" // IWYU pragma: keep

View File

@ -1,152 +0,0 @@
#include "tnode.h"
const parse_node_t *parse_node_tree_t::next_node_in_node_list(
const parse_node_t &node_list, parse_token_type_t entry_type,
const parse_node_t **out_list_tail) const {
parse_token_type_t list_type = node_list.type;
// Paranoia - it doesn't make sense for a list type to contain itself.
assert(list_type != entry_type);
const parse_node_t *list_cursor = &node_list;
const parse_node_t *list_entry = nullptr;
// Loop while we don't have an item but do have a list. Note that some nodes may contain
// nothing; e.g. job_list contains blank lines as a production.
while (list_entry == nullptr && list_cursor != nullptr) {
const parse_node_t *next_cursor = nullptr;
// Walk through the children.
for (node_offset_t i = 0; i < list_cursor->child_count; i++) {
const parse_node_t *child = this->get_child(*list_cursor, i);
if (child->type == entry_type) {
// This is the list entry.
list_entry = child;
} else if (child->type == list_type) {
// This is the next in the list.
next_cursor = child;
}
}
// Go to the next entry, even if it's NULL.
list_cursor = next_cursor;
}
// Return what we got.
assert(list_cursor == nullptr || list_cursor->type == list_type);
assert(list_entry == nullptr || list_entry->type == entry_type);
if (out_list_tail != nullptr) *out_list_tail = list_cursor;
return list_entry;
}
enum parse_statement_decoration_t get_decoration(tnode_t<grammar::plain_statement> stmt) {
parse_statement_decoration_t decoration = parse_statement_decoration_none;
if (auto decorated_statement = stmt.try_get_parent<grammar::decorated_statement>()) {
decoration = static_cast<parse_statement_decoration_t>(decorated_statement.tag());
}
return decoration;
}
enum parse_job_decoration_t bool_statement_type(tnode_t<grammar::job_decorator> stmt) {
return static_cast<parse_job_decoration_t>(stmt.tag());
}
enum parse_job_decoration_t bool_statement_type(
tnode_t<grammar::job_conjunction_continuation> cont) {
return static_cast<parse_job_decoration_t>(cont.tag());
}
maybe_t<pipe_or_redir_t> redirection_for_node(tnode_t<grammar::redirection> redirection,
const wcstring &src, wcstring *out_target) {
assert(redirection && "redirection is missing");
tnode_t<grammar::tok_redirection> prim = redirection.child<0>(); // like 2>
assert(prim && "expected to have primitive");
maybe_t<pipe_or_redir_t> result{};
if (prim.has_source()) {
result = pipe_or_redir_t::from_string(prim.get_source(src));
assert(result.has_value() && "Failed to parse valid redirection");
assert(!result->is_pipe && "Should not be a pipe");
}
if (out_target != nullptr) {
tnode_t<grammar::tok_string> target = redirection.child<1>(); // like 1 or file path
*out_target = target.has_source() ? target.get_source(src) : wcstring();
}
return result;
}
std::vector<tnode_t<grammar::comment>> parse_node_tree_t::comment_nodes_for_node(
const parse_node_t &parent) const {
std::vector<tnode_t<grammar::comment>> result;
if (parent.has_comments()) {
// Walk all our nodes, looking for comment nodes that have the given node as a parent.
for (size_t i = 0; i < this->size(); i++) {
const parse_node_t &potential_comment = this->at(i);
if (potential_comment.type == parse_special_type_comment &&
this->get_parent(potential_comment) == &parent) {
result.emplace_back(this, &potential_comment);
}
}
}
return result;
}
variable_assignment_node_list_t get_variable_assignment_nodes(
tnode_t<grammar::variable_assignments> list, size_t max) {
return list.descendants<grammar::variable_assignment>(max);
}
maybe_t<wcstring> command_for_plain_statement(tnode_t<grammar::plain_statement> stmt,
const wcstring &src) {
tnode_t<grammar::tok_string> cmd = stmt.child<0>();
if (cmd && cmd.has_source()) {
return cmd.get_source(src);
}
return none();
}
arguments_node_list_t get_argument_nodes(tnode_t<grammar::argument_list> list, size_t max) {
return list.descendants<grammar::argument>(max);
}
arguments_node_list_t get_argument_nodes(tnode_t<grammar::arguments_or_redirections_list> list,
size_t max) {
return list.descendants<grammar::argument>(max);
}
bool job_node_is_background(tnode_t<grammar::job> job) {
tnode_t<grammar::optional_background> bg = job.child<4>();
return bg.tag() == parse_background;
}
parse_job_decoration_t get_decorator(tnode_t<grammar::job_conjunction> conj) {
using namespace grammar;
tnode_t<job_decorator> dec;
// We have two possible parents: job_list and andor_job_list.
if (auto p = conj.try_get_parent<job_list>()) {
dec = p.require_get_child<job_decorator, 0>();
} else if (auto p = conj.try_get_parent<andor_job_list>()) {
dec = p.require_get_child<job_decorator, 0>();
}
// note this returns 0 (none) if dec is empty.
return bool_statement_type(dec);
}
pipeline_position_t get_pipeline_position(tnode_t<grammar::statement> st) {
using namespace grammar;
if (!st) {
return pipeline_position_t::none;
}
// If we're part of a job continuation, we're definitely in a pipeline.
if (st.try_get_parent<job_continuation>()) {
return pipeline_position_t::subsequent;
}
// Check if we're the beginning of a job, and if so, whether that job
// has a non-empty continuation.
tnode_t<job_continuation> jc = st.try_get_parent<job>().child<3>();
if (jc.try_get_child<statement, 3>()) {
return pipeline_position_t::first;
}
return pipeline_position_t::none;
}

View File

@ -1,278 +0,0 @@
// Type-safe access to fish parse trees.
#ifndef FISH_TNODE_H
#define FISH_TNODE_H
#include "parse_grammar.h"
#include "parse_tree.h"
// Check if a child type is possible for a parent type at a given index.
template <typename Parent, typename Child, size_t Index>
constexpr bool child_type_possible_at_index() {
return Parent::template type_possible<Child, Index>();
}
// Check if a child type is possible for a parent type at any index.
// The number of cases here should match MAX_PRODUCTION_LENGTH.
template <typename Parent, typename Child>
constexpr bool child_type_possible() {
return child_type_possible_at_index<Parent, Child, 0>() ||
child_type_possible_at_index<Parent, Child, 1>() ||
child_type_possible_at_index<Parent, Child, 2>() ||
child_type_possible_at_index<Parent, Child, 3>() ||
child_type_possible_at_index<Parent, Child, 4>() ||
child_type_possible_at_index<Parent, Child, 5>();
}
/// tnode_t ("typed node") is type-safe access to a parse_tree. A tnode_t holds both a pointer to a
/// parse_node_tree_t and a pointer to a parse_node_t. (Note that the parse_node_tree_t is unowned;
/// the caller must ensure that the tnode does not outlive the tree.
///
/// tnode_t is a lightweight value-type class. It ought to be passed by value. A tnode_t may also be
/// "missing", associated with a null parse_node_t pointer. operator bool() may be used to check if
/// a tnode_t is misisng.
///
/// A tnode_t is parametrized by a grammar element, and uses the fish grammar to statically
/// type-check accesses to children and parents. Any particular tnode either corresponds to a
/// sequence (a single child) or an alternation (multiple possible children). A sequence may have
/// its children accessed directly via child(), which is templated on the index (and returns a
/// tnode of the proper type). Alternations may be disambiguated via try_get_child(), which returns
/// an empty child if the child has the wrong type, or require_get_child() which aborts if the child
/// has the wrong type.
template <typename Type>
class tnode_t {
/// The tree containing our node.
const parse_node_tree_t *tree = nullptr;
/// The node in the tree
const parse_node_t *nodeptr = nullptr;
// Helper to get a child type at a given index.
template <class Element, uint32_t Index>
using child_at = typename std::tuple_element<Index, typename Element::type_tuple>::type;
public:
tnode_t() = default;
tnode_t(const parse_node_tree_t *t, const parse_node_t *n) : tree(t), nodeptr(n) {
assert(t && "tree cannot be null in this constructor");
assert((!n || n->type == Type::token) && "node has wrong type");
}
// Try to create a tnode from the given tree and parse node.
// Returns an empty node if the parse node is null, or has the wrong type.
static tnode_t try_create(const parse_node_tree_t *tree, const parse_node_t *node) {
assert(tree && "tree cannot be null");
return tnode_t(tree, node && node->type == Type::token ? node : nullptr);
}
/// Temporary conversion to parse_node_t to assist in migration.
/* implicit */ operator const parse_node_t &() const {
assert(nodeptr && "Empty tnode_t");
return *nodeptr;
}
/* implicit */ operator const parse_node_t *() const { return nodeptr; }
/// \return the underlying (type-erased) node.
const parse_node_t *node() const { return nodeptr; }
/// Check whether we're populated.
explicit operator bool() const { return nodeptr != nullptr; }
bool operator==(const tnode_t &rhs) const { return tree == rhs.tree && nodeptr == rhs.nodeptr; }
bool operator!=(const tnode_t &rhs) const { return !(*this == rhs); }
// Helper to return whether the given tree is the same as ours.
bool matches_node_tree(const parse_node_tree_t &t) const { return &t == tree; }
const parse_node_tree_t *get_tree() const { return tree; }
bool has_source() const { return nodeptr && nodeptr->has_source(); }
// return the tag, or 0 if missing.
parse_node_tag_t tag() const { return nodeptr ? nodeptr->tag : 0; }
// return the number of children, or 0 if missing.
uint8_t child_count() const { return nodeptr ? nodeptr->child_count : 0; }
maybe_t<source_range_t> source_range() const {
if (!nodeptr || nodeptr->source_start == NODE_OFFSET_INVALID) return none();
return source_range_t{nodeptr->source_start, nodeptr->source_length};
}
wcstring get_source(const wcstring &str) const {
if (!nodeptr) {
return L"";
}
return nodeptr->get_source(str);
}
bool location_in_or_at_end_of_source_range(size_t loc) const {
return nodeptr && nodeptr->location_in_or_at_end_of_source_range(loc);
}
static tnode_t find_node_matching_source_location(const parse_node_tree_t *tree,
size_t source_loc,
const parse_node_t *parent) {
assert(tree && "null tree");
return tnode_t{tree,
tree->find_node_matching_source_location(Type::token, source_loc, parent)};
}
/// Type-safe access to a child at the given index.
template <node_offset_t Index>
tnode_t<child_at<Type, Index>> child() const {
using child_type = child_at<Type, Index>;
const parse_node_t *child = nullptr;
if (nodeptr) child = tree->get_child(*nodeptr, Index, child_type::token);
return tnode_t<child_type>{tree, child};
}
/// Return a parse_node_t for a child.
/// This is used to disambiguate alts.
template <node_offset_t Index>
const parse_node_t &get_child_node() const {
assert(nodeptr && "receiver is missing in get_child_node");
return *tree->get_child(*nodeptr, Index);
}
/// If the child at the given index has the given type, return it; otherwise return an empty
/// child. Note this will refuse to compile if the child type is not possible.
/// This is used for e.g. alternations.
template <class ChildType, node_offset_t Index>
tnode_t<ChildType> try_get_child() const {
static_assert(child_type_possible_at_index<Type, ChildType, Index>(),
"Cannot contain a child of this type");
const parse_node_t *child = nullptr;
if (nodeptr) child = tree->get_child(*nodeptr, Index);
if (child && child->type == ChildType::token) return {tree, child};
return {tree, nullptr};
}
/// assert that this is not empty and that the child at index Index has the given type, then
/// return that child. Note this will refuse to compile if the child type is not possible.
template <class ChildType, node_offset_t Index>
tnode_t<ChildType> require_get_child() const {
assert(nodeptr && "receiver is missing in require_get_child()");
auto result = try_get_child<ChildType, Index>();
assert(result && "require_get_child(): wrong child type");
return result;
}
/// Find the first direct child of the given node of the given type. asserts on failure.
template <class ChildType>
tnode_t<ChildType> find_child() const {
static_assert(child_type_possible<Type, ChildType>(), "Cannot have that type as a child");
assert(nodeptr && "receiver is missing in find_child()");
tnode_t<ChildType> result{tree, &tree->find_child(*nodeptr, ChildType::token)};
assert(result && "cannot find child");
return result;
}
/// Type-safe access to a node's parent.
/// If the parent exists and has type ParentType, return it.
/// Otherwise return a missing tnode.
template <class ParentType>
tnode_t<ParentType> try_get_parent() const {
static_assert(child_type_possible<ParentType, Type>(), "Parent cannot have us as a child");
if (!nodeptr) return {};
return {tree, tree->get_parent(*nodeptr, ParentType::token)};
}
/// Finds all descendants (up to max_count) under this node of the given type.
template <typename DescendantType>
std::vector<tnode_t<DescendantType>> descendants(size_t max_count = -1) const {
if (!nodeptr) return {};
std::vector<tnode_t<DescendantType>> result;
std::vector<const parse_node_t *> stack{nodeptr};
while (!stack.empty() && result.size() < max_count) {
const parse_node_t *node = stack.back();
if (node->type == DescendantType::token) result.emplace_back(tree, node);
stack.pop_back();
node_offset_t index = node->child_count;
while (index--) {
stack.push_back(tree->get_child(*node, index));
}
}
return result;
}
/// Given that we are a list type, \return the next node of some Item in some node list,
/// adjusting 'this' to be the remainder of the list.
/// Returns an empty item on failure.
template <class ItemType>
tnode_t<ItemType> next_in_list() {
// We require that we can contain ourselves, and ItemType as well.
static_assert(child_type_possible<Type, Type>(), "Is not a list");
static_assert(child_type_possible<Type, ItemType>(), "Is not a list of that type");
if (!nodeptr) return {tree, nullptr};
const parse_node_t *next =
tree->next_node_in_node_list(*nodeptr, ItemType::token, &nodeptr);
return {tree, next};
}
};
template <typename Type>
tnode_t<Type> parse_node_tree_t::find_child(const parse_node_t &parent) const {
return tnode_t<Type>(this, &this->find_child(parent, Type::token));
}
/// Return the arguments under an arguments_list or arguments_or_redirection_list
/// Do not return more than max.
using variable_assignment_node_list_t = std::vector<tnode_t<grammar::variable_assignment>>;
variable_assignment_node_list_t get_variable_assignment_nodes(
tnode_t<grammar::variable_assignments>, size_t max = -1);
/// Given a plain statement, get the command from the child node. Returns the command string on
/// success, none on failure.
maybe_t<wcstring> command_for_plain_statement(tnode_t<grammar::plain_statement> stmt,
const wcstring &src);
/// Return the decoration for a plain statement.
parse_statement_decoration_t get_decoration(tnode_t<grammar::plain_statement> stmt);
/// Return the type for a boolean statement.
parse_job_decoration_t bool_statement_type(tnode_t<grammar::job_decorator> stmt);
parse_job_decoration_t bool_statement_type(tnode_t<grammar::job_conjunction_continuation> cont);
/// Given a redirection node, get the parsed redirection and target of the redirection (file path,
/// or fd).
maybe_t<pipe_or_redir_t> redirection_for_node(tnode_t<grammar::redirection> redirection,
const wcstring &src, wcstring *out_target);
/// Return the arguments under an arguments_list or arguments_or_redirection_list
/// Do not return more than max.
using arguments_node_list_t = std::vector<tnode_t<grammar::argument>>;
arguments_node_list_t get_argument_nodes(tnode_t<grammar::argument_list>, size_t max = -1);
arguments_node_list_t get_argument_nodes(tnode_t<grammar::arguments_or_redirections_list>,
size_t max = -1);
/// Return whether the given job is background because it has a & symbol.
bool job_node_is_background(tnode_t<grammar::job>);
/// If the conjunction is has a decorator (and/or), return it; otherwise return none. This only
/// considers the leading conjunction, e.g. in `and true || false` only the 'true' conjunction will
/// return 'and'.
parse_job_decoration_t get_decorator(tnode_t<grammar::job_conjunction>);
/// Return whether the statement is part of a pipeline.
/// This doesn't detect e.g. pipelines involving our parent's block statements.
enum class pipeline_position_t {
none, // not part of a pipeline
first, // first command in a pipeline
subsequent // second or further command in a pipeline
};
pipeline_position_t get_pipeline_position(tnode_t<grammar::statement> st);
/// Check whether an argument_list is a root list.
inline bool argument_list_is_root(tnode_t<grammar::argument_list> list) {
return !list.try_get_parent<grammar::argument_list>();
}
inline bool argument_list_is_root(tnode_t<grammar::arguments_or_redirections_list> list) {
return !list.try_get_parent<grammar::arguments_or_redirections_list>();
}
#endif