1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-16 10:50:49 -08:00

Handle signals gracefully in tree-sitter query predicates

Before this change, predicate functions can signal, which will cause
Ftreesit_query_capture to skip freeing the query and cursor object.
We make predicate functions return the signal data rather than
directly signal.

* src/treesit.c (treesit_predicate_capture_name_to_node)
(treesit_predicate_capture_name_to_text)
(treesit_predicate_equal)
(treesit_predicate_match)
(treesit_predicate_pred)
(treesit_eval_predicates): Return signal rather than signaling
directly.
(Ftreesit_query_capture): Check for returned signal data.
This commit is contained in:
Yuan Fu 2023-03-24 12:19:25 -07:00
parent f446bfc819
commit a37f19b14a
No known key found for this signature in database
GPG key ID: 56E19BC57664A442

View file

@ -2407,87 +2407,111 @@ treesit_predicates_for_pattern (TSQuery *query, uint32_t pattern_index)
return Fnreverse (result); return Fnreverse (result);
} }
/* Translate a capture NAME (symbol) to a node. /* Translate a capture NAME (symbol) to a node. If everything goes
Signals treesit-query-error if such node is not captured. */ fine, set NODE and return true; if error occurs (e.g., when there
static Lisp_Object is no node for the capture name), set NODE to Qnil, SIGNAL_DATA to
a suitable signal data, and return false. */
static bool
treesit_predicate_capture_name_to_node (Lisp_Object name, treesit_predicate_capture_name_to_node (Lisp_Object name,
struct capture_range captures) struct capture_range captures,
Lisp_Object *node,
Lisp_Object *signal_data)
{ {
Lisp_Object node = Qnil; *node = Qnil;
for (Lisp_Object tail = captures.start; !EQ (tail, captures.end); for (Lisp_Object tail = captures.start; !EQ (tail, captures.end);
tail = XCDR (tail)) tail = XCDR (tail))
{ {
if (EQ (XCAR (XCAR (tail)), name)) if (EQ (XCAR (XCAR (tail)), name))
{ {
node = XCDR (XCAR (tail)); *node = XCDR (XCAR (tail));
break; break;
} }
} }
if (NILP (node)) if (NILP (*node))
xsignal3 (Qtreesit_query_error, {
build_string ("Cannot find captured node"), *signal_data = list3 (build_string ("Cannot find captured node"),
name, build_string ("A predicate can only refer" name, build_string ("A predicate can only refer"
" to captured nodes in the " " to captured nodes in the "
"same pattern")); "same pattern"));
return node; return false;
}
return true;
} }
/* Translate a capture NAME (symbol) to the text of the captured node. /* Translate a capture NAME (symbol) to the text of the captured node.
Signals treesit-query-error if such node is not captured. */ If everything goes fine, set TEXT to the text and return true;
static Lisp_Object otherwise set TEXT to Qnil and set SIGNAL_DATA to a suitable signal
data. */
static bool
treesit_predicate_capture_name_to_text (Lisp_Object name, treesit_predicate_capture_name_to_text (Lisp_Object name,
struct capture_range captures) struct capture_range captures,
Lisp_Object *text,
Lisp_Object *signal_data)
{ {
Lisp_Object node = treesit_predicate_capture_name_to_node (name, captures); Lisp_Object node = Qnil;
if (!treesit_predicate_capture_name_to_node (name, captures, &node, signal_data))
return false;
struct buffer *old_buffer = current_buffer; struct buffer *old_buffer = current_buffer;
set_buffer_internal (XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer)); set_buffer_internal (XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer));
Lisp_Object text = Fbuffer_substring (Ftreesit_node_start (node), *text = Fbuffer_substring (Ftreesit_node_start (node),
Ftreesit_node_end (node)); Ftreesit_node_end (node));
set_buffer_internal (old_buffer); set_buffer_internal (old_buffer);
return text; return true;
} }
/* Handles predicate (#equal A B). Return true if A equals B; return /* Handles predicate (#equal A B). Return true if A equals B; return
false otherwise. A and B can be either string, or a capture name. false otherwise. A and B can be either string, or a capture name.
The capture name evaluates to the text its captured node spans in The capture name evaluates to the text its captured node spans in
the buffer. */ the buffer. If everything goes fine, don't touch SIGNAL_DATA; if
error occurs, set it to a suitable signal data. */
static bool static bool
treesit_predicate_equal (Lisp_Object args, struct capture_range captures) treesit_predicate_equal (Lisp_Object args, struct capture_range captures,
Lisp_Object *signal_data)
{ {
if (XFIXNUM (Flength (args)) != 2) if (XFIXNUM (Flength (args)) != 2)
xsignal2 (Qtreesit_query_error, {
build_string ("Predicate `equal' requires " *signal_data = list2 (build_string ("Predicate `equal' requires "
"two arguments but only given"), "two arguments but only given"),
Flength (args)); Flength (args));
return false;
}
Lisp_Object arg1 = XCAR (args); Lisp_Object arg1 = XCAR (args);
Lisp_Object arg2 = XCAR (XCDR (args)); Lisp_Object arg2 = XCAR (XCDR (args));
Lisp_Object text1 = (STRINGP (arg1) Lisp_Object text1 = arg1;
? arg1 Lisp_Object text2 = arg2;
: treesit_predicate_capture_name_to_text (arg1, if (SYMBOLP (arg1))
captures)); {
Lisp_Object text2 = (STRINGP (arg2) if (!treesit_predicate_capture_name_to_text (arg1, captures, &text1,
? arg2 signal_data))
: treesit_predicate_capture_name_to_text (arg2, return false;
captures)); }
if (SYMBOLP (arg2))
{
if (!treesit_predicate_capture_name_to_text (arg2, captures, &text2,
signal_data))
return false;
}
return !NILP (Fstring_equal (text1, text2)); return !NILP (Fstring_equal (text1, text2));
} }
/* Handles predicate (#match "regexp" @node). Return true if "regexp" /* Handles predicate (#match "regexp" @node). Return true if "regexp"
matches the text spanned by @node; return false otherwise. Matching matches the text spanned by @node; return false otherwise.
is case-sensitive. */ Matching is case-sensitive. If everything goes fine, don't touch
SIGNAL_DATA; if error occurs, set it to a suitable signal data. */
static bool static bool
treesit_predicate_match (Lisp_Object args, struct capture_range captures) treesit_predicate_match (Lisp_Object args, struct capture_range captures,
Lisp_Object *signal_data)
{ {
if (XFIXNUM (Flength (args)) != 2) if (XFIXNUM (Flength (args)) != 2)
xsignal2 (Qtreesit_query_error, {
build_string ("Predicate `match' requires two " *signal_data = list2 (build_string ("Predicate `match' requires two "
"arguments but only given"), "arguments but only given"),
Flength (args)); Flength (args));
return false;
}
Lisp_Object regexp = XCAR (args); Lisp_Object regexp = XCAR (args);
Lisp_Object capture_name = XCAR (XCDR (args)); Lisp_Object capture_name = XCAR (XCDR (args));
@ -2504,8 +2528,10 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures)
build_string ("The second argument to `match' should " build_string ("The second argument to `match' should "
"be a capture name, not a string")); "be a capture name, not a string"));
Lisp_Object node = treesit_predicate_capture_name_to_node (capture_name, Lisp_Object node = Qnil;
captures); if (!treesit_predicate_capture_name_to_node (capture_name, captures, &node,
signal_data))
return false;
struct buffer *old_buffer = current_buffer; struct buffer *old_buffer = current_buffer;
struct buffer *buffer = XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer); struct buffer *buffer = XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer);
@ -2544,54 +2570,66 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures)
/* Handles predicate (#pred FN ARG...). Return true if FN returns /* Handles predicate (#pred FN ARG...). Return true if FN returns
non-nil; return false otherwise. The arity of FN must match the non-nil; return false otherwise. The arity of FN must match the
number of ARGs */ number of ARGs. If everything goes fine, don't touch SIGNAL_DATA;
if error occurs, set it to a suitable signal data. */
static bool static bool
treesit_predicate_pred (Lisp_Object args, struct capture_range captures) treesit_predicate_pred (Lisp_Object args, struct capture_range captures,
Lisp_Object *signal_data)
{ {
if (XFIXNUM (Flength (args)) < 2) if (XFIXNUM (Flength (args)) < 2)
xsignal2 (Qtreesit_query_error, {
build_string ("Predicate `pred' requires " *signal_data = list2 (build_string ("Predicate `pred' requires "
"at least two arguments, " "at least two arguments, "
"but was only given"), "but was only given"),
Flength (args)); Flength (args));
return false;
}
Lisp_Object fn = Fintern (XCAR (args), Qnil); Lisp_Object fn = Fintern (XCAR (args), Qnil);
Lisp_Object nodes = Qnil; Lisp_Object nodes = Qnil;
Lisp_Object tail = XCDR (args); Lisp_Object tail = XCDR (args);
FOR_EACH_TAIL (tail) FOR_EACH_TAIL (tail)
nodes = Fcons (treesit_predicate_capture_name_to_node (XCAR (tail), {
captures), Lisp_Object node = Qnil;
nodes); if (!treesit_predicate_capture_name_to_node (XCAR (tail), captures, &node,
signal_data))
return false;
nodes = Fcons (node, nodes);
}
nodes = Fnreverse (nodes); nodes = Fnreverse (nodes);
return !NILP (CALLN (Fapply, fn, nodes)); return !NILP (CALLN (Fapply, fn, nodes));
} }
/* If all predicates in PREDICATES passes, return true; otherwise /* If all predicates in PREDICATES passes, return true; otherwise
return false. */ return false. If everything goes fine, don't touch SIGNAL_DATA; if
error occurs, set it to a suitable signal data. */
static bool static bool
treesit_eval_predicates (struct capture_range captures, Lisp_Object predicates) treesit_eval_predicates (struct capture_range captures, Lisp_Object predicates,
Lisp_Object *signal_data)
{ {
bool pass = true; bool pass = true;
/* Evaluate each predicates. */ /* Evaluate each predicates. */
for (Lisp_Object tail = predicates; for (Lisp_Object tail = predicates;
!NILP (tail); tail = XCDR (tail)) pass && !NILP (tail); tail = XCDR (tail))
{ {
Lisp_Object predicate = XCAR (tail); Lisp_Object predicate = XCAR (tail);
Lisp_Object fn = XCAR (predicate); Lisp_Object fn = XCAR (predicate);
Lisp_Object args = XCDR (predicate); Lisp_Object args = XCDR (predicate);
if (!NILP (Fstring_equal (fn, Vtreesit_str_equal))) if (!NILP (Fstring_equal (fn, Vtreesit_str_equal)))
pass &= treesit_predicate_equal (args, captures); pass &= treesit_predicate_equal (args, captures, signal_data);
else if (!NILP (Fstring_equal (fn, Vtreesit_str_match))) else if (!NILP (Fstring_equal (fn, Vtreesit_str_match)))
pass &= treesit_predicate_match (args, captures); pass &= treesit_predicate_match (args, captures, signal_data);
else if (!NILP (Fstring_equal (fn, Vtreesit_str_pred))) else if (!NILP (Fstring_equal (fn, Vtreesit_str_pred)))
pass &= treesit_predicate_pred (args, captures); pass &= treesit_predicate_pred (args, captures, signal_data);
else else
xsignal3 (Qtreesit_query_error, {
build_string ("Invalid predicate"), *signal_data = list3 (build_string ("Invalid predicate"),
fn, build_string ("Currently Emacs only supports" fn, build_string ("Currently Emacs only supports"
" equal, match, and pred" " equal, match, and pred"
" predicate")); " predicates"));
pass = false;
}
} }
/* If all predicates passed, add captures to result list. */ /* If all predicates passed, add captures to result list. */
return pass; return pass;
@ -2831,11 +2869,12 @@ the query. */)
Lisp_Object result = Qnil; Lisp_Object result = Qnil;
Lisp_Object prev_result = result; Lisp_Object prev_result = result;
Lisp_Object predicates_table = make_vector (patterns_count, Qt); Lisp_Object predicates_table = make_vector (patterns_count, Qt);
Lisp_Object predicate_signal_data = Qnil;
while (ts_query_cursor_next_match (cursor, &match)) while (ts_query_cursor_next_match (cursor, &match))
{ {
/* Record the checkpoint that we may roll back to. */ /* Record the checkpoint that we may roll back to. */
prev_result = result; prev_result = result;
/* Get captured nodes. */ /* 1. Get captured nodes. */
const TSQueryCapture *captures = match.captures; const TSQueryCapture *captures = match.captures;
for (int idx = 0; idx < match.capture_count; idx++) for (int idx = 0; idx < match.capture_count; idx++)
{ {
@ -2858,7 +2897,8 @@ the query. */)
result = Fcons (cap, result); result = Fcons (cap, result);
} }
/* Get predicates. */ /* 2. Get predicates and check whether this match can be
included in the result list. */
Lisp_Object predicates = AREF (predicates_table, match.pattern_index); Lisp_Object predicates = AREF (predicates_table, match.pattern_index);
if (EQ (predicates, Qt)) if (EQ (predicates, Qt))
{ {
@ -2869,15 +2909,27 @@ the query. */)
/* captures_lisp = Fnreverse (captures_lisp); */ /* captures_lisp = Fnreverse (captures_lisp); */
struct capture_range captures_range = { result, prev_result }; struct capture_range captures_range = { result, prev_result };
if (!treesit_eval_predicates (captures_range, predicates)) bool match = treesit_eval_predicates (captures_range, predicates,
/* Predicates didn't pass, roll back. */ &predicate_signal_data);
if (!NILP (predicate_signal_data))
break;
/* Predicates didn't pass, roll back. */
if (!match)
result = prev_result; result = prev_result;
} }
/* Final clean up. */
if (needs_to_free_query_and_cursor) if (needs_to_free_query_and_cursor)
{ {
ts_query_delete (treesit_query); ts_query_delete (treesit_query);
ts_query_cursor_delete (cursor); ts_query_cursor_delete (cursor);
} }
/* Some capture predicate signaled an error. */
if (!NILP (predicate_signal_data))
xsignal (Qtreesit_query_error, predicate_signal_data);
return Fnreverse (result); return Fnreverse (result);
} }