diff options
| author | Yuan Fu | 2022-12-26 17:16:59 -0800 |
|---|---|---|
| committer | Yuan Fu | 2022-12-26 17:50:14 -0800 |
| commit | a6d961ae2fd0eb93938f2afd932f4d3cb63a0412 (patch) | |
| tree | bcdb59869539bdf0d1cee4c81a1e864171350551 /src | |
| parent | 835a80dcc48c9c9d90709dcadbedb9afd6ded48c (diff) | |
| download | emacs-a6d961ae2fd0eb93938f2afd932f4d3cb63a0412.tar.gz emacs-a6d961ae2fd0eb93938f2afd932f4d3cb63a0412.zip | |
Add a new tree-sitter query predicate 'pred'
I realized that using an arbitrary function as the predicate in
queries is very helpful for some queries I'm writing for python and
javascript, and presumably most other languages[1].
Granted, we can already filter out unwanted nodes by using a function
instead of a face for the capture name, and (1) determine whether the
captured node is valid and (2) fontify that node if it's valid.
However, such approach is a bit more cumbersome and more importantly
gets in the way of another potential use of the fontification queries:
context extraction.
For example, I could use the query for the 'variable' feature to get
all the variables in a certain region. In this use-case, we want the
filtering happen before returning the captured nodes.
Besides, the change is relatively small and straightforward: most code
are already there, I just need to add some boilerplate.
[1] For a code like aa.bb(cc), we want bb to be in function face,
because obviously its a function. But for aa.bb, we want bb to be in
property face, because it's a property. In the AST, bb is always a
property, the difference between the two cases is the enclosing node:
in the first case, aa.bb is in a "call_expression" node, indicating
that bb is used as a function (a method). So we want a predicate
function that checks whether bb is used as a function or a property,
and determine whether it should be in function or property face.
* doc/lispref/parsing.texi (Pattern Matching): Update manual.
* src/treesit.c (Ftreesit_pattern_expand): Handle :pred.
(treesit_predicate_capture_name_to_node): A new function extracted
from treesit_predicate_capture_name_to_text.
(treesit_predicate_capture_name_to_text): Use the newly extracted
function.
(treesit_predicate_pred): New predicate function.
(treesit_eval_predicates): Add new predicate. Also fix a bug: we want
to AND the results of each predicate.
* test/src/treesit-tests.el (treesit--ert-pred-last-sibling): New
helper function.
(treesit-query-api): Test #pred predicate.
Diffstat (limited to 'src')
| -rw-r--r-- | src/treesit.c | 57 |
1 files changed, 45 insertions, 12 deletions
diff --git a/src/treesit.c b/src/treesit.c index ecc977745a6..813d4222f98 100644 --- a/src/treesit.c +++ b/src/treesit.c | |||
| @@ -2170,6 +2170,8 @@ See Info node `(elisp)Pattern Matching' for detailed explanation. */) | |||
| 2170 | return build_pure_c_string ("#equal"); | 2170 | return build_pure_c_string ("#equal"); |
| 2171 | if (EQ (pattern, QCmatch)) | 2171 | if (EQ (pattern, QCmatch)) |
| 2172 | return build_pure_c_string ("#match"); | 2172 | return build_pure_c_string ("#match"); |
| 2173 | if (EQ (pattern, QCpred)) | ||
| 2174 | return build_pure_c_string ("#pred"); | ||
| 2173 | Lisp_Object opening_delimeter | 2175 | Lisp_Object opening_delimeter |
| 2174 | = build_pure_c_string (VECTORP (pattern) ? "[" : "("); | 2176 | = build_pure_c_string (VECTORP (pattern) ? "[" : "("); |
| 2175 | Lisp_Object closing_delimiter | 2177 | Lisp_Object closing_delimiter |
| @@ -2269,10 +2271,10 @@ treesit_predicates_for_pattern (TSQuery *query, uint32_t pattern_index) | |||
| 2269 | return Fnreverse (result); | 2271 | return Fnreverse (result); |
| 2270 | } | 2272 | } |
| 2271 | 2273 | ||
| 2272 | /* Translate a capture NAME (symbol) to the text of the captured node. | 2274 | /* Translate a capture NAME (symbol) to a node. |
| 2273 | Signals treesit-query-error if such node is not captured. */ | 2275 | Signals treesit-query-error if such node is not captured. */ |
| 2274 | static Lisp_Object | 2276 | static Lisp_Object |
| 2275 | treesit_predicate_capture_name_to_text (Lisp_Object name, | 2277 | treesit_predicate_capture_name_to_node (Lisp_Object name, |
| 2276 | struct capture_range captures) | 2278 | struct capture_range captures) |
| 2277 | { | 2279 | { |
| 2278 | Lisp_Object node = Qnil; | 2280 | Lisp_Object node = Qnil; |
| @@ -2292,6 +2294,16 @@ treesit_predicate_capture_name_to_text (Lisp_Object name, | |||
| 2292 | name, build_pure_c_string ("A predicate can only refer" | 2294 | name, build_pure_c_string ("A predicate can only refer" |
| 2293 | " to captured nodes in the " | 2295 | " to captured nodes in the " |
| 2294 | "same pattern")); | 2296 | "same pattern")); |
| 2297 | return node; | ||
| 2298 | } | ||
| 2299 | |||
| 2300 | /* Translate a capture NAME (symbol) to the text of the captured node. | ||
| 2301 | Signals treesit-query-error if such node is not captured. */ | ||
| 2302 | static Lisp_Object | ||
| 2303 | treesit_predicate_capture_name_to_text (Lisp_Object name, | ||
| 2304 | struct capture_range captures) | ||
| 2305 | { | ||
| 2306 | Lisp_Object node = treesit_predicate_capture_name_to_node (name, captures); | ||
| 2295 | 2307 | ||
| 2296 | struct buffer *old_buffer = current_buffer; | 2308 | struct buffer *old_buffer = current_buffer; |
| 2297 | set_buffer_internal (XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer)); | 2309 | set_buffer_internal (XBUFFER (XTS_PARSER (XTS_NODE (node)->parser)->buffer)); |
| @@ -2365,13 +2377,30 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures) | |||
| 2365 | return false; | 2377 | return false; |
| 2366 | } | 2378 | } |
| 2367 | 2379 | ||
| 2368 | /* About predicates: I decide to hard-code predicates in C instead of | 2380 | /* Handles predicate (#pred FN ARG...). Return true if FN returns |
| 2369 | implementing an extensible system where predicates are translated | 2381 | non-nil; return false otherwise. The arity of FN must match the |
| 2370 | to Lisp functions, and new predicates can be added by extending a | 2382 | number of ARGs */ |
| 2371 | list of functions, because I really couldn't imagine any useful | 2383 | static bool |
| 2372 | predicates besides equal and match. If we later found out that | 2384 | treesit_predicate_pred (Lisp_Object args, struct capture_range captures) |
| 2373 | such system is indeed useful and necessary, it can be easily | 2385 | { |
| 2374 | added. */ | 2386 | if (XFIXNUM (Flength (args)) < 2) |
| 2387 | xsignal2 (Qtreesit_query_error, | ||
| 2388 | build_pure_c_string ("Predicate `pred' requires " | ||
| 2389 | "at least two arguments, " | ||
| 2390 | "but was only given"), | ||
| 2391 | Flength (args)); | ||
| 2392 | |||
| 2393 | Lisp_Object fn = Fintern (XCAR (args), Qnil); | ||
| 2394 | Lisp_Object nodes = Qnil; | ||
| 2395 | Lisp_Object tail = XCDR (args); | ||
| 2396 | FOR_EACH_TAIL (tail) | ||
| 2397 | nodes = Fcons (treesit_predicate_capture_name_to_node (XCAR (tail), | ||
| 2398 | captures), | ||
| 2399 | nodes); | ||
| 2400 | nodes = Fnreverse (nodes); | ||
| 2401 | |||
| 2402 | return !NILP (CALLN (Fapply, fn, nodes)); | ||
| 2403 | } | ||
| 2375 | 2404 | ||
| 2376 | /* If all predicates in PREDICATES passes, return true; otherwise | 2405 | /* If all predicates in PREDICATES passes, return true; otherwise |
| 2377 | return false. */ | 2406 | return false. */ |
| @@ -2387,14 +2416,17 @@ treesit_eval_predicates (struct capture_range captures, Lisp_Object predicates) | |||
| 2387 | Lisp_Object fn = XCAR (predicate); | 2416 | Lisp_Object fn = XCAR (predicate); |
| 2388 | Lisp_Object args = XCDR (predicate); | 2417 | Lisp_Object args = XCDR (predicate); |
| 2389 | if (!NILP (Fstring_equal (fn, build_pure_c_string ("equal")))) | 2418 | if (!NILP (Fstring_equal (fn, build_pure_c_string ("equal")))) |
| 2390 | pass = treesit_predicate_equal (args, captures); | 2419 | pass &= treesit_predicate_equal (args, captures); |
| 2391 | else if (!NILP (Fstring_equal (fn, build_pure_c_string ("match")))) | 2420 | else if (!NILP (Fstring_equal (fn, build_pure_c_string ("match")))) |
| 2392 | pass = treesit_predicate_match (args, captures); | 2421 | pass &= treesit_predicate_match (args, captures); |
| 2422 | else if (!NILP (Fstring_equal (fn, build_pure_c_string ("pred")))) | ||
| 2423 | pass &= treesit_predicate_pred (args, captures); | ||
| 2393 | else | 2424 | else |
| 2394 | xsignal3 (Qtreesit_query_error, | 2425 | xsignal3 (Qtreesit_query_error, |
| 2395 | build_pure_c_string ("Invalid predicate"), | 2426 | build_pure_c_string ("Invalid predicate"), |
| 2396 | fn, build_pure_c_string ("Currently Emacs only supports" | 2427 | fn, build_pure_c_string ("Currently Emacs only supports" |
| 2397 | " equal and match predicate")); | 2428 | " equal, match, and pred" |
| 2429 | " predicate")); | ||
| 2398 | } | 2430 | } |
| 2399 | /* If all predicates passed, add captures to result list. */ | 2431 | /* If all predicates passed, add captures to result list. */ |
| 2400 | return pass; | 2432 | return pass; |
| @@ -3217,6 +3249,7 @@ syms_of_treesit (void) | |||
| 3217 | DEFSYM (QCanchor, ":anchor"); | 3249 | DEFSYM (QCanchor, ":anchor"); |
| 3218 | DEFSYM (QCequal, ":equal"); | 3250 | DEFSYM (QCequal, ":equal"); |
| 3219 | DEFSYM (QCmatch, ":match"); | 3251 | DEFSYM (QCmatch, ":match"); |
| 3252 | DEFSYM (QCpred, ":pred"); | ||
| 3220 | 3253 | ||
| 3221 | DEFSYM (Qnot_found, "not-found"); | 3254 | DEFSYM (Qnot_found, "not-found"); |
| 3222 | DEFSYM (Qsymbol_error, "symbol-error"); | 3255 | DEFSYM (Qsymbol_error, "symbol-error"); |