diff --git a/CMakeLists.txt b/CMakeLists.txt index 162ae60cb..eba9b553f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ SET(FISH_SRCS src/parse_execution.cpp src/parse_productions.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/tokenizer.cpp src/utf8.cpp src/util.cpp + src/signal.cpp src/tnode.cpp src/tokenizer.cpp src/utf8.cpp src/util.cpp src/wcstringutil.cpp src/wgetopt.cpp src/wildcard.cpp src/wutil.cpp ) diff --git a/Makefile.in b/Makefile.in index 6ba7e14a3..d34d9f286 100644 --- a/Makefile.in +++ b/Makefile.in @@ -124,8 +124,8 @@ FISH_OBJS := obj/autoload.o obj/builtin.o obj/builtin_bg.o obj/builtin_bind.o ob obj/iothread.o obj/kill.o obj/output.o obj/pager.o obj/parse_execution.o \ obj/parse_productions.o obj/parse_tree.o obj/parse_util.o obj/parser.o \ obj/parser_keywords.o obj/path.o obj/postfork.o obj/proc.o obj/reader.o \ - obj/sanity.o obj/screen.o obj/signal.o obj/tokenizer.o obj/utf8.o obj/util.o \ - obj/wcstringutil.o obj/wgetopt.o obj/wildcard.o obj/wutil.o + obj/sanity.o obj/screen.o obj/signal.o obj/tokenizer.o obj/tnode.o obj/utf8.o \ + obj/util.o obj/wcstringutil.o obj/wgetopt.o obj/wildcard.o obj/wutil.o FISH_INDENT_OBJS := obj/fish_indent.o obj/print_help.o $(FISH_OBJS) diff --git a/fish.xcodeproj/project.pbxproj b/fish.xcodeproj/project.pbxproj index 1067cbeb8..d0156b45c 100644 --- a/fish.xcodeproj/project.pbxproj +++ b/fish.xcodeproj/project.pbxproj @@ -67,6 +67,9 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 4F2D55CF2013ECDD00822920 /* tnode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4F2D55CE2013ECDD00822920 /* tnode.cpp */; }; + 4F2D55D02013ECDD00822920 /* tnode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4F2D55CE2013ECDD00822920 /* tnode.cpp */; }; + 4F2D55D12013ED0100822920 /* tnode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4F2D55CE2013ECDD00822920 /* tnode.cpp */; }; 63A2C0E91CC60F3B00973404 /* pcre2_find_bracket.c in Sources */ = {isa = PBXBuildFile; fileRef = 63A2C0E81CC5F9FB00973404 /* pcre2_find_bracket.c */; }; 9C7A55271DCD651F0049C25D /* fallback.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0853E13B3ACEE0099B651 /* fallback.cpp */; }; 9C7A552F1DCD65820049C25D /* util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0A0855E13B3ACEE0099B651 /* util.cpp */; }; @@ -690,6 +693,8 @@ /* Begin PBXFileReference section */ 4E142D731B56B5D7008783C8 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config.h; path = ../osx/config.h; sourceTree = ""; }; + 4F2D55CD2013ECDD00822920 /* tnode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tnode.h; sourceTree = ""; }; + 4F2D55CE2013ECDD00822920 /* tnode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tnode.cpp; sourceTree = ""; }; 63A2C0E81CC5F9FB00973404 /* pcre2_find_bracket.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pcre2_find_bracket.c; sourceTree = ""; }; 9C7A55721DCD71330049C25D /* fish_key_reader */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fish_key_reader; sourceTree = BUILT_PRODUCTS_DIR; }; 9C7A557C1DCD717C0049C25D /* fish_key_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fish_key_reader.cpp; sourceTree = ""; }; @@ -1256,6 +1261,8 @@ D0A0855C13B3ACEE0099B651 /* signal.cpp */, D0A0852513B3ACEE0099B651 /* tokenizer.h */, D0A0855D13B3ACEE0099B651 /* tokenizer.cpp */, + 4F2D55CD2013ECDD00822920 /* tnode.h */, + 4F2D55CE2013ECDD00822920 /* tnode.cpp */, D0C9733A18DE5451002D7C81 /* utf8.h */, D0C9733718DE5449002D7C81 /* utf8.cpp */, D0A0852613B3ACEE0099B651 /* util.h */, @@ -1755,6 +1762,7 @@ 9C7A55491DCD71330049C25D /* postfork.cpp in Sources */, 9C7A554A1DCD71330049C25D /* screen.cpp in Sources */, 9C7A554B1DCD71330049C25D /* signal.cpp in Sources */, + 4F2D55D12013ED0100822920 /* tnode.cpp in Sources */, 9C7A554C1DCD71330049C25D /* utf8.cpp in Sources */, 9C7A554E1DCD71330049C25D /* function.cpp in Sources */, 9C7A554F1DCD71330049C25D /* complete.cpp in Sources */, @@ -1974,6 +1982,7 @@ D05F59B41F041AE4003EE978 /* builtin_contains.cpp in Sources */, D030FC081A4A38F300F7ADA0 /* pager.cpp in Sources */, D030FC091A4A38F300F7ADA0 /* parse_util.cpp in Sources */, + 4F2D55D02013ECDD00822920 /* tnode.cpp in Sources */, D0D02AD9159864A6008E62BD /* parser_keywords.cpp in Sources */, D02960E71FBD726200CA3985 /* builtin_wait.cpp in Sources */, D05F59A51F041AE4003EE978 /* builtin_fg.cpp in Sources */, @@ -2057,6 +2066,7 @@ D0D02A6E15983838008E62BD /* kill.cpp in Sources */, D0D02A6F1598383E008E62BD /* parser.cpp in Sources */, D05F59771F041AE4003EE978 /* builtin_string.cpp in Sources */, + 4F2D55CF2013ECDD00822920 /* tnode.cpp in Sources */, D05F597A1F041AE4003EE978 /* builtin_status.cpp in Sources */, D0D02A8F15983D8F008E62BD /* parser_keywords.cpp in Sources */, D0D02A7015983842008E62BD /* proc.cpp in Sources */, diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 551b0fdc7..620873cce 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -36,14 +36,13 @@ #include "maybe.h" #include "parse_constants.h" #include "parse_execution.h" -#include "parse_tree.h" #include "parse_util.h" #include "parser.h" #include "path.h" #include "proc.h" #include "reader.h" -#include "tokenizer.h" #include "tnode.h" +#include "tokenizer.h" #include "util.h" #include "wildcard.h" #include "wutil.h" diff --git a/src/parse_tree.cpp b/src/parse_tree.cpp index e049b229b..b06bb54c2 100644 --- a/src/parse_tree.cpp +++ b/src/parse_tree.cpp @@ -1246,23 +1246,6 @@ static bool node_has_ancestor(const parse_node_tree_t &tree, const parse_node_t return node_has_ancestor(tree, tree.at(node.parent), proposed_ancestor); } -const parse_node_t *parse_node_tree_t::find_last_node_of_type(parse_token_type_t type, - const parse_node_t *parent) const { - const parse_node_t *result = NULL; - // Find nodes of the given type in the tree, working backwards. - size_t idx = this->size(); - while (idx--) { - const parse_node_t &node = this->at(idx); - bool expected_type = (node.type == type); - if (expected_type && (parent == NULL || node_has_ancestor(*this, node, *parent))) { - // The types match and it has the right parent. - result = &node; - break; - } - } - return result; -} - const parse_node_t *parse_node_tree_t::find_node_matching_source_location( parse_token_type_t type, size_t source_loc, const parse_node_t *parent) const { const parse_node_t *result = NULL; @@ -1287,130 +1270,20 @@ const parse_node_t *parse_node_tree_t::find_node_matching_source_location( return result; } -enum parse_statement_decoration_t get_decoration(tnode_t stmt) { - parse_statement_decoration_t decoration = parse_statement_decoration_none; - if (auto decorated_statement = stmt.try_get_parent()) { - decoration = static_cast(decorated_statement.tag()); - } - return decoration; -} - -enum parse_bool_statement_type_t bool_statement_type(tnode_t stmt) { - return static_cast(stmt.tag()); -} - -enum token_type redirection_type(tnode_t redirection, const wcstring &src, - int *out_fd, wcstring *out_target) { - assert(redirection && "redirection is missing"); - enum token_type result = TOK_NONE; - tnode_t prim = redirection.child<0>(); // like 2> - assert(prim && "expected to have primitive"); - - if (prim.has_source()) { - result = redirection_type_for_string(prim.get_source(src), out_fd); - } - if (out_target != NULL) { - tnode_t target = redirection.child<1>(); // like &1 or file path - *out_target = target ? target.get_source(src) : wcstring(); - } - return result; -} - -std::vector> parse_node_tree_t::comment_nodes_for_node( - const parse_node_t &parent) const { - std::vector> 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); - } +const parse_node_t *parse_node_tree_t::find_last_node_of_type(parse_token_type_t type, + const parse_node_t *parent) const { + const parse_node_t *result = NULL; + // Find nodes of the given type in the tree, working backwards. + size_t idx = this->size(); + while (idx--) { + const parse_node_t &node = this->at(idx); + bool expected_type = (node.type == type); + if (expected_type && (parent == NULL || node_has_ancestor(*this, node, *parent))) { + // The types match and it has the right parent. + result = &node; + break; } } return result; } -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 = NULL; - - // 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 == NULL && list_cursor != NULL) { - const parse_node_t *next_cursor = NULL; - - // 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 == NULL || list_cursor->type == list_type); - assert(list_entry == NULL || list_entry->type == entry_type); - if (out_list_tail != NULL) *out_list_tail = list_cursor; - return list_entry; -} - -maybe_t command_for_plain_statement(tnode_t stmt, - const wcstring &src) { - tnode_t 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 list, size_t max) { - return list.descendants(max); -} - -arguments_node_list_t get_argument_nodes(tnode_t list, - size_t max) { - return list.descendants(max); -} - -bool job_node_is_background(tnode_t job) { - tnode_t bg = job.child<2>(); - return bg.tag() == parse_background; -} - -bool statement_is_in_pipeline(tnode_t st, bool include_first) { - using namespace grammar; - if (!st) { - return false; - } - - // If we're part of a job continuation, we're definitely in a pipeline. - if (st.try_get_parent()) { - return true; - } - - // If include_first is set, check if we're the beginning of a job, and if so, whether that job - // has a non-empty continuation. - if (include_first) { - tnode_t jc = st.try_get_parent().child<1>(); - if (jc.try_get_child()) { - return true; - } - } - return false; -} diff --git a/src/tnode.cpp b/src/tnode.cpp new file mode 100644 index 000000000..630309891 --- /dev/null +++ b/src/tnode.cpp @@ -0,0 +1,129 @@ +#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 = NULL; + + // 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 == NULL && list_cursor != NULL) { + const parse_node_t *next_cursor = NULL; + + // 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 == NULL || list_cursor->type == list_type); + assert(list_entry == NULL || list_entry->type == entry_type); + if (out_list_tail != NULL) *out_list_tail = list_cursor; + return list_entry; +} + +enum parse_statement_decoration_t get_decoration(tnode_t stmt) { + parse_statement_decoration_t decoration = parse_statement_decoration_none; + if (auto decorated_statement = stmt.try_get_parent()) { + decoration = static_cast(decorated_statement.tag()); + } + return decoration; +} + +enum parse_bool_statement_type_t bool_statement_type(tnode_t stmt) { + return static_cast(stmt.tag()); +} + +enum token_type redirection_type(tnode_t redirection, const wcstring &src, + int *out_fd, wcstring *out_target) { + assert(redirection && "redirection is missing"); + enum token_type result = TOK_NONE; + tnode_t prim = redirection.child<0>(); // like 2> + assert(prim && "expected to have primitive"); + + if (prim.has_source()) { + result = redirection_type_for_string(prim.get_source(src), out_fd); + } + if (out_target != NULL) { + tnode_t target = redirection.child<1>(); // like &1 or file path + *out_target = target ? target.get_source(src) : wcstring(); + } + return result; +} + +std::vector> parse_node_tree_t::comment_nodes_for_node( + const parse_node_t &parent) const { + std::vector> 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; +} + +maybe_t command_for_plain_statement(tnode_t stmt, + const wcstring &src) { + tnode_t 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 list, size_t max) { + return list.descendants(max); +} + +arguments_node_list_t get_argument_nodes(tnode_t list, + size_t max) { + return list.descendants(max); +} + +bool job_node_is_background(tnode_t job) { + tnode_t bg = job.child<2>(); + return bg.tag() == parse_background; +} + +bool statement_is_in_pipeline(tnode_t st, bool include_first) { + using namespace grammar; + if (!st) { + return false; + } + + // If we're part of a job continuation, we're definitely in a pipeline. + if (st.try_get_parent()) { + return true; + } + + // If include_first is set, check if we're the beginning of a job, and if so, whether that job + // has a non-empty continuation. + if (include_first) { + tnode_t jc = st.try_get_parent().child<1>(); + if (jc.try_get_child()) { + return true; + } + } + return false; +} diff --git a/src/tnode.h b/src/tnode.h index 9dccdde3b..3c663ffc3 100644 --- a/src/tnode.h +++ b/src/tnode.h @@ -28,8 +28,21 @@ constexpr bool child_type_possible() { child_type_possible_at_index(); } -/// A helper for type-safe manipulation of parse nodes. -/// This is a lightweight value-type class. +/// 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 class tnode_t { /// The tree containing our node. @@ -65,7 +78,7 @@ class tnode_t { /* implicit */ operator const parse_node_t *() const { return nodeptr; } - /// Return the underlying (type-erased) node. + /// \return the underlying (type-erased) node. const parse_node_t *node() const { return nodeptr; } /// Check whether we're populated.