Eliminate parse_node_tree::find_nodes

This commit is contained in:
ridiculousfish 2018-01-15 22:13:37 -08:00
parent 242512f0df
commit 194f7f34d9
8 changed files with 80 additions and 111 deletions

View file

@ -1367,7 +1367,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> *out_c
use_implicit_cd);
} else {
// Get all the arguments.
auto all_arguments = tree.find_nodes<grammar::argument>(plain_statement);
auto all_arguments = plain_statement.descendants<grammar::argument>();
// See whether we are in an argument. We may also be in a redirection, or nothing at
// all.

View file

@ -132,12 +132,11 @@ static void prettify_node_recursive(const wcstring &source, const parse_node_tre
if (node.has_comments()) // handle comments, which come before the text
{
const parse_node_tree_t::parse_node_list_t comment_nodes =
(tree.comment_nodes_for_node(node));
for (size_t i = 0; i < comment_nodes.size(); i++) {
const parse_node_t &comment_node = *comment_nodes.at(i);
auto comment_nodes = tree.comment_nodes_for_node(node);
for (const auto &comment : comment_nodes) {
append_whitespace(node_indent, do_indent, *has_new_line, out_result);
out_result->append(source, comment_node.source_start, comment_node.source_length);
auto source_range = comment.source_range();
out_result->append(source, source_range->start, source_range->length);
}
}

View file

@ -2418,7 +2418,6 @@ static void test_autosuggest_suggest_special() {
}
const wcstring wd = L"test/autosuggest_test";
const env_vars_snapshot_t &vars = env_vars_snapshot_t::current();
perform_one_autosuggestion_cd_test(L"cd test/autosuggest_test/0", L"foobar/", __LINE__);
perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/0", L"foobar/", __LINE__);
@ -3394,7 +3393,8 @@ static bool test_1_parse_ll2(const wcstring &src, wcstring *out_cmd, wcstring *o
}
// Get the statement. Should only have one.
auto stmts = tree.find_nodes<grammar::plain_statement>(tree.at(0));
tnode_t<grammar::job_list> job_list{&tree, &tree.at(0)};
auto stmts = job_list.descendants<grammar::plain_statement>();
if (stmts.size() != 1) {
say(L"Unexpected number of statements (%lu) found in '%ls'", stmts.size(), src.c_str());
return false;
@ -3406,15 +3406,34 @@ static bool test_1_parse_ll2(const wcstring &src, wcstring *out_cmd, wcstring *o
*out_cmd = *command_for_plain_statement(stmt, src);
// Return arguments separated by spaces.
const parse_node_tree_t::parse_node_list_t arg_nodes = tree.find_nodes(stmt, symbol_argument);
for (size_t i = 0; i < arg_nodes.size(); i++) {
if (i > 0) out_joined_args->push_back(L' ');
out_joined_args->append(arg_nodes.at(i)->get_source(src));
bool first = true;
for (auto arg_node : stmt.descendants<grammar::argument>()) {
if (!first) out_joined_args->push_back(L' ');
out_joined_args->append(arg_node.get_source(src));
first = false;
}
return true;
}
// Verify that 'function -h' and 'function --help' are plain statements but 'function --foo' is
// not (issue #1240).
template <typename Type>
static void check_function_help(const wchar_t *src) {
parse_node_tree_t tree;
if (!parse_tree_from_string(src, parse_flag_none, &tree, NULL)) {
err(L"Failed to parse '%ls'", src);
}
tnode_t<grammar::job_list> node{&tree, &tree.at(0)};
auto node_list = node.descendants<Type>();
if (node_list.size() == 0) {
err(L"Failed to find node of type '%ls'", token_type_description(Type::token));
} else if (node_list.size() > 1) {
err(L"Found too many nodes of type '%ls'", token_type_description(Type::token));
}
}
// Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and
// command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command
// -help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to
@ -3459,31 +3478,10 @@ static void test_new_parser_ll2(void) {
tests[i].src.c_str(), (int)tests[i].deco, (int)deco, (long)__LINE__);
}
// Verify that 'function -h' and 'function --help' are plain statements but 'function --foo' is
// not (issue #1240).
const struct {
wcstring src;
parse_token_type_t type;
} tests2[] = {
{L"function -h", symbol_plain_statement},
{L"function --help", symbol_plain_statement},
{L"function --foo ; end", symbol_function_header},
{L"function foo ; end", symbol_function_header},
};
for (size_t i = 0; i < sizeof tests2 / sizeof *tests2; i++) {
parse_node_tree_t tree;
if (!parse_tree_from_string(tests2[i].src, parse_flag_none, &tree, NULL)) {
err(L"Failed to parse '%ls'", tests2[i].src.c_str());
}
const parse_node_tree_t::parse_node_list_t node_list =
tree.find_nodes(tree.at(0), tests2[i].type);
if (node_list.size() == 0) {
err(L"Failed to find node of type '%ls'", token_type_description(tests2[i].type));
} else if (node_list.size() > 1) {
err(L"Found too many nodes of type '%ls'", token_type_description(tests2[i].type));
}
}
check_function_help<grammar::plain_statement>(L"function -h");
check_function_help<grammar::plain_statement>(L"function --help");
check_function_help<grammar::function_header>(L"function --foo; end");
check_function_help<grammar::function_header>(L"function foo; end");
}
static void test_new_parser_ad_hoc() {
@ -3500,9 +3498,8 @@ static void test_new_parser_ad_hoc() {
// Expect three case_item_lists: one for each case, and a terminal one. The bug was that we'd
// try to run a command 'case'.
const parse_node_t &root = parse_tree.at(0);
const parse_node_tree_t::parse_node_list_t node_list =
parse_tree.find_nodes(root, symbol_case_item_list);
tnode_t<grammar::job_list> root{&parse_tree, &parse_tree.at(0)};
auto node_list = root.descendants<grammar::case_item_list>();
if (node_list.size() != 3) {
err(L"Expected 3 case item nodes, found %lu", node_list.size());
}

View file

@ -66,13 +66,9 @@ static wcstring profiling_cmd_name_for_redirectable_block(const parse_node_t &no
const size_t src_start = node.source_start;
size_t src_len = node.source_length;
const parse_node_tree_t::parse_node_list_t statement_terminator_nodes =
tree.find_nodes(node, parse_token_type_end, 1);
if (!statement_terminator_nodes.empty()) {
const parse_node_t *term = statement_terminator_nodes.at(0);
assert(term->source_start >= src_start);
src_len = term->source_start - src_start;
}
auto term = tree.find_child<g::end_command>(node);
assert(term.has_source() && term.source_range()->start >= src_start);
src_len = term.source_range()->start - src_start;
wcstring result = wcstring(src, src_start, src_len);
result.append(L"...");

View file

@ -42,6 +42,13 @@ struct keyword {
}
};
// 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"

View file

@ -1231,27 +1231,6 @@ const parse_node_t *parse_node_tree_t::get_parent(const parse_node_t &node,
return result;
}
static void find_nodes_recursive(const parse_node_tree_t &tree, const parse_node_t &parent,
parse_token_type_t type,
parse_node_tree_t::parse_node_list_t *result, size_t max_count) {
if (result->size() < max_count) {
if (parent.type == type) result->push_back(&parent);
for (node_offset_t i = 0; i < parent.child_count; i++) {
const parse_node_t *child = tree.get_child(parent, i);
assert(child != NULL);
find_nodes_recursive(tree, *child, type, result, max_count);
}
}
}
parse_node_tree_t::parse_node_list_t parse_node_tree_t::find_nodes(const parse_node_t &parent,
parse_token_type_t type,
size_t max_count) const {
parse_node_list_t result;
find_nodes_recursive(*this, parent, type, &result, max_count);
return result;
}
/// Return true if the given node has the proposed ancestor as an ancestor (or is itself that
/// ancestor).
static bool node_has_ancestor(const parse_node_tree_t &tree, const parse_node_t &node,
@ -1365,16 +1344,16 @@ bool parse_node_tree_t::statement_is_in_pipeline(const parse_node_t &node,
return result;
}
parse_node_tree_t::parse_node_list_t parse_node_tree_t::comment_nodes_for_node(
std::vector<tnode_t<grammar::comment>> parse_node_tree_t::comment_nodes_for_node(
const parse_node_t &parent) const {
parse_node_list_t result;
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.push_back(&potential_comment);
result.emplace_back(this, &potential_comment);
}
}
}
@ -1428,12 +1407,13 @@ maybe_t<wcstring> command_for_plain_statement(tnode_t<grammar::plain_statement>
return none();
}
arguments_node_list_t get_argument_nodes(tnode_t<grammar::argument_list> list) {
return list.descendants<grammar::argument>();
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) {
return list.descendants<grammar::argument>();
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) {

View file

@ -169,15 +169,6 @@ class parse_node_tree_t : public std::vector<parse_node_t> {
const parse_node_t *get_parent(const parse_node_t &node,
parse_token_type_t expected_type = token_type_invalid) const;
// Find all the nodes of a given type underneath a given node, up to max_count of them.
typedef std::vector<const parse_node_t *> parse_node_list_t;
parse_node_list_t find_nodes(const parse_node_t &parent, parse_token_type_t type,
size_t max_count = size_t(-1)) const;
// Find all the nodes of a given type underneath a given node, up to max_count of them.
template <typename Type>
std::vector<tnode_t<Type>> find_nodes(const parse_node_t &parent, size_t max_count = -1) const;
// Finds the last node of a given type, or empty if it could not be found. If parent is NULL,
// this finds the last node in the tree of that type.
template <typename Type>
@ -196,7 +187,7 @@ class parse_node_tree_t : public std::vector<parse_node_t> {
bool statement_is_in_pipeline(const parse_node_t &node, bool include_first) const;
/// Given a node, return all of its comment nodes.
parse_node_list_t comment_nodes_for_node(const parse_node_t &node) const;
std::vector<tnode_t<grammar::comment>> comment_nodes_for_node(const parse_node_t &node) const;
private:
template <typename Type>
@ -377,7 +368,18 @@ class tnode_t {
template <typename DescendantType>
std::vector<tnode_t<DescendantType>> descendants(size_t max_count = -1) const {
if (!nodeptr) return {};
return tree->find_nodes<DescendantType>(*nodeptr);
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,
@ -402,18 +404,6 @@ tnode_t<Type> parse_node_tree_t::find_last_node(const parse_node_t *parent) cons
return tnode_t<Type>(this, this->find_last_node_of_type(Type::token, parent));
}
template <typename Type>
std::vector<tnode_t<Type>> parse_node_tree_t::find_nodes(const parse_node_t &parent,
size_t max_count) const {
auto ptrs = this->find_nodes(parent, Type::token, max_count);
std::vector<tnode_t<Type>> result;
result.reserve(ptrs.size());
for (const parse_node_t *np : ptrs) {
result.emplace_back(this, np);
}
return result;
}
/// 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,
@ -430,9 +420,11 @@ enum token_type redirection_type(tnode_t<grammar::redirection> redirection, cons
int *out_fd, 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>);
arguments_node_list_t get_argument_nodes(tnode_t<grammar::arguments_or_redirections_list>);
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>);

View file

@ -760,15 +760,13 @@ bool parse_util_argument_is_help(const wchar_t *s) {
}
/// Check if the first argument under the given node is --help.
static bool first_argument_is_help(const parse_node_tree_t &node_tree, const parse_node_t &node,
static bool first_argument_is_help(tnode_t<grammar::plain_statement> statement,
const wcstring &src) {
bool is_help = false;
const parse_node_tree_t::parse_node_list_t arg_nodes =
node_tree.find_nodes(node, symbol_argument, 1);
auto arg_nodes = get_argument_nodes(statement.child<1>());
if (!arg_nodes.empty()) {
// Check the first argument only.
const parse_node_t &arg = *arg_nodes.at(0);
const wcstring first_arg_src = arg.get_source(src);
wcstring first_arg_src = arg_nodes.front().get_source(src);
is_help = parse_util_argument_is_help(first_arg_src.c_str());
}
return is_help;
@ -1167,13 +1165,14 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
errored |= detect_errors_in_backgrounded_job(node_tree, job, &parse_errors);
}
} else if (node.type == symbol_plain_statement) {
using namespace grammar;
tnode_t<plain_statement> statement{&node_tree, &node};
// In a few places below, we want to know if we are in a pipeline.
const bool is_in_pipeline =
node_tree.statement_is_in_pipeline(node, true /* count first */);
node_tree.statement_is_in_pipeline(statement, true /* count first */);
// We need to know the decoration.
const enum parse_statement_decoration_t decoration =
get_decoration({&node_tree, &node});
const enum parse_statement_decoration_t decoration = get_decoration(statement);
// Check that we don't try to pipe through exec.
if (is_in_pipeline && decoration == parse_statement_decoration_exec) {
@ -1183,7 +1182,6 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
if (maybe_t<wcstring> mcommand =
command_for_plain_statement({&node_tree, &node}, buff_src)) {
using namespace grammar;
wcstring command = std::move(*mcommand);
// Check that we can expand the command.
if (!expand_one(command,
@ -1214,7 +1212,7 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
break;
}
}
if (!found_function && !first_argument_is_help(node_tree, node, buff_src)) {
if (!found_function && !first_argument_is_help(statement, buff_src)) {
errored = append_syntax_error(&parse_errors, node.source_start,
INVALID_RETURN_ERR_MSG);
}
@ -1246,7 +1244,7 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
}
}
if (!found_loop && !first_argument_is_help(node_tree, node, buff_src)) {
if (!found_loop && !first_argument_is_help(statement, buff_src)) {
errored = append_syntax_error(
&parse_errors, node.source_start,
(command == L"break" ? INVALID_BREAK_ERR_MSG