aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYuan Fu2022-12-26 17:16:59 -0800
committerYuan Fu2022-12-26 17:50:14 -0800
commita6d961ae2fd0eb93938f2afd932f4d3cb63a0412 (patch)
treebcdb59869539bdf0d1cee4c81a1e864171350551 /src
parent835a80dcc48c9c9d90709dcadbedb9afd6ded48c (diff)
downloademacs-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.c57
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. */
2274static Lisp_Object 2276static Lisp_Object
2275treesit_predicate_capture_name_to_text (Lisp_Object name, 2277treesit_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. */
2302static Lisp_Object
2303treesit_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 2383static bool
2372 predicates besides equal and match. If we later found out that 2384treesit_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");