Merge pull request #722 from tree-sitter/query-last-child-fix

Fix behavior of the last child operator in tree queries
This commit is contained in:
Max Brunsfeld 2020-09-02 12:35:39 -07:00 committed by GitHub
commit 85e4636c80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 43 deletions

View file

@ -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()")]),
],
);
});
}

View file

@ -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) {

View file

@ -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;
}
}
}
}

View file

@ -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_