From 36a8821f3ed5460c72876da51ad4569ba79dbe7f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 2 Sep 2020 12:03:46 -0700 Subject: [PATCH] Fix behavior of the last child operator in tree queries --- cli/src/tests/query_test.rs | 35 +++++++++++ lib/src/query.c | 10 +-- lib/src/tree_cursor.c | 117 ++++++++++++++++++++++++------------ lib/src/tree_cursor.h | 2 +- 4 files changed, 121 insertions(+), 43 deletions(-) diff --git a/cli/src/tests/query_test.rs b/cli/src/tests/query_test.rs index b857467b..f3521bb5 100644 --- a/cli/src/tests/query_test.rs +++ b/cli/src/tests/query_test.rs @@ -667,6 +667,41 @@ fn test_query_matches_with_immediate_siblings() { (2, vec![("first-element", "1")]), ], ); + + let query = Query::new( + language, + " + (block . (_) @first-stmt) + (block (_) @stmt) + (block (_) @last-stmt .) + ", + ) + .unwrap(); + + assert_query_matches( + language, + &query, + " + if a: + b() + c() + if d(): e(); f() + g() + ", + &[ + (0, vec![("first-stmt", "b()")]), + (1, vec![("stmt", "b()")]), + (1, vec![("stmt", "c()")]), + (1, vec![("stmt", "if d(): e(); f()")]), + (0, vec![("first-stmt", "e()")]), + (1, vec![("stmt", "e()")]), + (1, vec![("stmt", "f()")]), + (2, vec![("last-stmt", "f()")]), + (1, vec![("stmt", "g()")]), + (2, vec![("last-stmt", "g()")]), + ], + ); + }); } diff --git a/lib/src/query.c b/lib/src/query.c index b629af51..45aa3877 100644 --- a/lib/src/query.c +++ b/lib/src/query.c @@ -2549,11 +2549,13 @@ static inline bool ts_query_cursor__advance( if (symbol != ts_builtin_sym_error && self->query->symbol_map) { symbol = self->query->symbol_map[symbol]; } - bool can_have_later_siblings; + bool has_later_siblings; + bool has_later_named_siblings; bool can_have_later_siblings_with_this_field; TSFieldId field_id = ts_tree_cursor_current_status( &self->cursor, - &can_have_later_siblings, + &has_later_siblings, + &has_later_named_siblings, &can_have_later_siblings_with_this_field ); LOG( @@ -2613,11 +2615,11 @@ static inline bool ts_query_cursor__advance( step->symbol == symbol || step->symbol == WILDCARD_SYMBOL || (step->symbol == NAMED_WILDCARD_SYMBOL && is_named); - bool later_sibling_can_match = can_have_later_siblings; + bool later_sibling_can_match = has_later_siblings; if ((step->is_immediate && is_named) || state->seeking_immediate_match) { later_sibling_can_match = false; } - if (step->is_last_child && can_have_later_siblings) { + if (step->is_last_child && has_later_named_siblings) { node_does_match = false; } if (step->field) { diff --git a/lib/src/tree_cursor.c b/lib/src/tree_cursor.c index 06c724d2..b193a754 100644 --- a/lib/src/tree_cursor.c +++ b/lib/src/tree_cursor.c @@ -244,14 +244,18 @@ TSNode ts_tree_cursor_current_node(const TSTreeCursor *_self) { ); } +// Private - Get various facts about the current node that are needed +// when executing tree queries. TSFieldId ts_tree_cursor_current_status( const TSTreeCursor *_self, - bool *can_have_later_siblings, + bool *has_later_siblings, + bool *has_later_named_siblings, bool *can_have_later_siblings_with_this_field ) { const TreeCursor *self = (const TreeCursor *)_self; TSFieldId result = 0; - *can_have_later_siblings = false; + *has_later_siblings = false; + *has_later_named_siblings = false; *can_have_later_siblings_with_this_field = false; // Walk up the tree, visiting the current node and its invisible ancestors, @@ -260,49 +264,86 @@ TSFieldId ts_tree_cursor_current_status( TreeCursorEntry *entry = &self->stack.contents[i]; TreeCursorEntry *parent_entry = &self->stack.contents[i - 1]; - // Stop walking up when a visible ancestor is found. - if (i != self->stack.size - 1) { - if (ts_subtree_visible(*entry->subtree)) break; - if ( - !ts_subtree_extra(*entry->subtree) && - ts_language_alias_at( - self->tree->language, - parent_entry->subtree->ptr->production_id, - entry->structural_child_index - ) - ) break; - } - - if (ts_subtree_child_count(*parent_entry->subtree) > entry->child_index + 1) { - *can_have_later_siblings = true; - } - - if (ts_subtree_extra(*entry->subtree)) break; - - const TSFieldMapEntry *field_map, *field_map_end; - ts_language_field_map( + const TSSymbol *alias_sequence = ts_language_alias_sequence( self->tree->language, - parent_entry->subtree->ptr->production_id, - &field_map, &field_map_end + parent_entry->subtree->ptr->production_id ); - // Look for a field name associated with the current node. - if (!result) { - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (!i->inherited && i->child_index == entry->structural_child_index) { - result = i->field_id; - *can_have_later_siblings_with_this_field = false; - break; + // If the subtree is visible, return its public-facing symbol. + // Otherwise, return zero. + #define subtree_visible_symbol(subtree, structural_child_index) \ + (( \ + !ts_subtree_extra(subtree) && \ + alias_sequence && \ + alias_sequence[structural_child_index] \ + ) ? \ + alias_sequence[structural_child_index] : \ + ts_subtree_visible(subtree) ? \ + ts_subtree_symbol(subtree) : \ + 0) \ + + // Stop walking up when a visible ancestor is found. + if ( + i != self->stack.size - 1 && + subtree_visible_symbol(*entry->subtree, entry->structural_child_index) + ) break; + + // Determine if the current node has later siblings. + if (!*has_later_siblings) { + unsigned sibling_count = parent_entry->subtree->ptr->child_count; + unsigned structural_child_index = entry->structural_child_index; + if (!ts_subtree_extra(*entry->subtree)) structural_child_index++; + for (unsigned j = entry->child_index + 1; j < sibling_count; j++) { + Subtree sibling = parent_entry->subtree->ptr->children[j]; + if (ts_subtree_visible_child_count(sibling) > 0) { + *has_later_siblings = true; + if (*has_later_named_siblings) break; + if (sibling.ptr->named_child_count > 0) { + *has_later_named_siblings = true; + break; + } } + TSSymbol visible_symbol = subtree_visible_symbol(sibling, structural_child_index); + if (visible_symbol) { + *has_later_siblings = true; + if (*has_later_named_siblings) break; + if (ts_language_symbol_metadata(self->tree->language, visible_symbol).named) { + *has_later_named_siblings = true; + break; + } + } + if (!ts_subtree_extra(sibling)) structural_child_index++; } } - // Determine if there other later siblings with the same field name. - if (result) { - for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { - if (i->field_id == result && i->child_index > entry->structural_child_index) { - *can_have_later_siblings_with_this_field = true; - break; + #undef subtree_visible_symbol + + if (!ts_subtree_extra(*entry->subtree)) { + const TSFieldMapEntry *field_map, *field_map_end; + ts_language_field_map( + self->tree->language, + parent_entry->subtree->ptr->production_id, + &field_map, &field_map_end + ); + + // Look for a field name associated with the current node. + if (!result) { + for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { + if (!i->inherited && i->child_index == entry->structural_child_index) { + result = i->field_id; + *can_have_later_siblings_with_this_field = false; + break; + } + } + } + + // Determine if the current node can have later siblings with the same field name. + if (result) { + for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) { + if (i->field_id == result && i->child_index > entry->structural_child_index) { + *can_have_later_siblings_with_this_field = true; + break; + } } } } diff --git a/lib/src/tree_cursor.h b/lib/src/tree_cursor.h index 5a39dd27..0bb486d7 100644 --- a/lib/src/tree_cursor.h +++ b/lib/src/tree_cursor.h @@ -16,6 +16,6 @@ typedef struct { } TreeCursor; void ts_tree_cursor_init(TreeCursor *, TSNode); -TSFieldId ts_tree_cursor_current_status(const TSTreeCursor *, bool *, bool *); +TSFieldId ts_tree_cursor_current_status(const TSTreeCursor *, bool *, bool *, bool *); #endif // TREE_SITTER_TREE_CURSOR_H_