diff options
| author | Yuan Fu | 2025-11-02 16:16:50 -0800 |
|---|---|---|
| committer | Yuan Fu | 2025-11-02 17:11:55 -0800 |
| commit | b01435306a36e4e75671fbe7bacea351f89947d5 (patch) | |
| tree | 702393061f1a049f87c892637365a0582170b136 | |
| parent | 68290e1a03ba4f264eab5d25960cf907b0c903fe (diff) | |
| download | emacs-b01435306a36e4e75671fbe7bacea351f89947d5.tar.gz emacs-b01435306a36e4e75671fbe7bacea351f89947d5.zip | |
Change tree-sitter query predicate names (bug#79687)
Latest tree-sitter library throws a syntax error if the
predicate names in a query don't end with question mark. So we
made the following change:
:equal changed to :eq?
:match changed to :match?
:pred changed to :pred?
Old names are transparently converted to new names when
expanding patterns.
:match predicate can now take the regexp and the node in any
order: it'll figure out which is which automatically. This way
it works with current Emacs convention (regexp first), as well
as tree-sitter's match convention (regexp second).
* doc/lispref/parsing.texi (Pattern Matching): Update manuel to
use new predicate names.
* src/treesit.c:
(Ftreesit_pattern_expand):
(Ftreesit_query_expand):
(treesit_predicate_match):
(treesit_eval_predicates):
(syms_of_treesit): Use new predicate names.
* test/src/treesit-tests.el (treesit-query-api): Update test.
| -rw-r--r-- | doc/lispref/parsing.texi | 34 | ||||
| -rw-r--r-- | etc/NEWS | 12 | ||||
| -rw-r--r-- | src/treesit.c | 92 | ||||
| -rw-r--r-- | test/src/treesit-tests.el | 6 |
4 files changed, 84 insertions, 60 deletions
diff --git a/doc/lispref/parsing.texi b/doc/lispref/parsing.texi index 5734fcf8094..08f5c310a24 100644 --- a/doc/lispref/parsing.texi +++ b/doc/lispref/parsing.texi | |||
| @@ -1473,7 +1473,7 @@ example, with the following pattern: | |||
| 1473 | @group | 1473 | @group |
| 1474 | ( | 1474 | ( |
| 1475 | (array :anchor (_) @@first (_) @@last :anchor) | 1475 | (array :anchor (_) @@first (_) @@last :anchor) |
| 1476 | (:equal @@first @@last) | 1476 | (:eq? @@first @@last) |
| 1477 | ) | 1477 | ) |
| 1478 | @end group | 1478 | @end group |
| 1479 | @end example | 1479 | @end example |
| @@ -1482,24 +1482,32 @@ example, with the following pattern: | |||
| 1482 | tree-sitter only matches arrays where the first element is equal to | 1482 | tree-sitter only matches arrays where the first element is equal to |
| 1483 | the last element. To attach a predicate to a pattern, we need to | 1483 | the last element. To attach a predicate to a pattern, we need to |
| 1484 | group them together. Currently there are three predicates: | 1484 | group them together. Currently there are three predicates: |
| 1485 | @code{:equal}, @code{:match}, and @code{:pred}. | 1485 | @code{:eq?}, @code{:match?}, and @code{:pred?}. |
| 1486 | 1486 | ||
| 1487 | @deffn Predicate :equal arg1 arg2 | 1487 | @deffn Predicate :eq? arg1 arg2 |
| 1488 | Matches if @var{arg1} is equal to @var{arg2}. Arguments can be either | 1488 | Matches if @var{arg1} is equal to @var{arg2}. Arguments can be either |
| 1489 | strings or capture names. Capture names represent the text that the | 1489 | strings or capture names. Capture names represent the text that the |
| 1490 | captured node spans in the buffer. | 1490 | captured node spans in the buffer. Note that this is more like |
| 1491 | @code{equal} in Elisp, but @code{eq?} is the convention used by | ||
| 1492 | tree-sitter. Previously we supported the @code{:equal} predicate but | ||
| 1493 | it's now considered deprecated. | ||
| 1491 | @end deffn | 1494 | @end deffn |
| 1492 | 1495 | ||
| 1493 | @deffn Predicate :match regexp capture-name | 1496 | @deffn Predicate :match? capture-name regexp |
| 1494 | Matches if the text that @var{capture-name}'s node spans in the buffer | 1497 | Matches if the text that @var{capture-name}'s node spans in the buffer |
| 1495 | matches regular expression @var{regexp}, given as a string literal. | 1498 | matches regular expression @var{regexp}, given as a string literal. |
| 1496 | Matching is case-sensitive. | 1499 | Matching is case-sensitive. The ordering of the arguments doesn't |
| 1500 | matter. Previously we supported the @code{:match} predicate but it's | ||
| 1501 | now considered deprecated. | ||
| 1497 | @end deffn | 1502 | @end deffn |
| 1498 | 1503 | ||
| 1499 | @deffn Predicate :pred fn &rest nodes | 1504 | @deffn Predicate :pred? fn &rest nodes |
| 1500 | Matches if function @var{fn} returns non-@code{nil} when passed each | 1505 | Matches if function @var{fn} returns non-@code{nil} when passed each |
| 1501 | node in @var{nodes} as arguments. The function runs with the current | 1506 | node in @var{nodes} as arguments. The function runs with the current |
| 1502 | buffer set to the buffer of node being queried. | 1507 | buffer set to the buffer of node being queried. Be very careful when |
| 1508 | using this predicate, since it can be expensive when used in a tight | ||
| 1509 | loop. Previously we supported the @code{:pred} predicate but it's now | ||
| 1510 | considered deprecated. | ||
| 1503 | @end deffn | 1511 | @end deffn |
| 1504 | 1512 | ||
| 1505 | Note that a predicate can only refer to capture names that appear in | 1513 | Note that a predicate can only refer to capture names that appear in |
| @@ -1554,9 +1562,9 @@ Anchor @code{:anchor} is written as @samp{.}. | |||
| 1554 | @item | 1562 | @item |
| 1555 | @samp{:+} is written as @samp{+}. | 1563 | @samp{:+} is written as @samp{+}. |
| 1556 | @item | 1564 | @item |
| 1557 | @code{:equal}, @code{:match} and @code{:pred} are written as | 1565 | @code{:eq?}, @code{:match?} and @code{:pred?} are written as |
| 1558 | @code{#equal}, @code{#match} and @code{#pred}, respectively. | 1566 | @code{#eq?}, @code{#match?} and @code{#pred?}, respectively. In |
| 1559 | In general, predicates change their @samp{:} to @samp{#}. | 1567 | general, predicates change the @samp{:} to @samp{#}. |
| 1560 | @end itemize | 1568 | @end itemize |
| 1561 | 1569 | ||
| 1562 | For example, | 1570 | For example, |
| @@ -1565,7 +1573,7 @@ For example, | |||
| 1565 | @group | 1573 | @group |
| 1566 | '(( | 1574 | '(( |
| 1567 | (compound_expression :anchor (_) @@first (_) :* @@rest) | 1575 | (compound_expression :anchor (_) @@first (_) :* @@rest) |
| 1568 | (:match "love" @@first) | 1576 | (:match? "love" @@first) |
| 1569 | )) | 1577 | )) |
| 1570 | @end group | 1578 | @end group |
| 1571 | @end example | 1579 | @end example |
| @@ -1577,7 +1585,7 @@ is written in string form as | |||
| 1577 | @group | 1585 | @group |
| 1578 | "( | 1586 | "( |
| 1579 | (compound_expression . (_) @@first (_)* @@rest) | 1587 | (compound_expression . (_) @@first (_)* @@rest) |
| 1580 | (#match \"love\" @@first) | 1588 | (#match? \"love\" @@first) |
| 1581 | )" | 1589 | )" |
| 1582 | @end group | 1590 | @end group |
| 1583 | @end example | 1591 | @end example |
| @@ -1049,6 +1049,18 @@ Now 'treesit-explore-mode' (or 'treesit-explore') prompts for a parser | |||
| 1049 | rather than a language, and it is now possible to select a local parser | 1049 | rather than a language, and it is now possible to select a local parser |
| 1050 | at point to explore. | 1050 | at point to explore. |
| 1051 | 1051 | ||
| 1052 | +++ | ||
| 1053 | *** Tree-sitter query predicate :equal, :match, and :pred are deprecated | ||
| 1054 | Use :eq?, :match?, :pred? instead. The change is because newer | ||
| 1055 | tree-sitter library mandates query predicates to end with question mark. | ||
| 1056 | Emacs will transparently converts :equal, :match and :pred to :eq?, | ||
| 1057 | :match? and :pred?, respectively, so existing queries still work fine | ||
| 1058 | with latest tree-sitter library. Predicate :equal is changed to :eq? to | ||
| 1059 | better follow tree-sitter’s convention. Also, the :match? predicates | ||
| 1060 | can now take the regexp as either the first or second argument, so it | ||
| 1061 | works with both tree-sitter convention (regexp arg second) and Emacs | ||
| 1062 | convention (regexp arg first). | ||
| 1063 | |||
| 1052 | ** Hideshow | 1064 | ** Hideshow |
| 1053 | 1065 | ||
| 1054 | +++ | 1066 | +++ |
diff --git a/src/treesit.c b/src/treesit.c index 69751b5ea10..3230d0a50a1 100644 --- a/src/treesit.c +++ b/src/treesit.c | |||
| @@ -490,17 +490,17 @@ static Lisp_Object Vtreesit_str_dot; | |||
| 490 | static Lisp_Object Vtreesit_str_question_mark; | 490 | static Lisp_Object Vtreesit_str_question_mark; |
| 491 | static Lisp_Object Vtreesit_str_star; | 491 | static Lisp_Object Vtreesit_str_star; |
| 492 | static Lisp_Object Vtreesit_str_plus; | 492 | static Lisp_Object Vtreesit_str_plus; |
| 493 | static Lisp_Object Vtreesit_str_pound_equal; | 493 | static Lisp_Object Vtreesit_str_pound_eq_question_mark; |
| 494 | static Lisp_Object Vtreesit_str_pound_match; | 494 | static Lisp_Object Vtreesit_str_pound_match_question_mark; |
| 495 | static Lisp_Object Vtreesit_str_pound_pred; | 495 | static Lisp_Object Vtreesit_str_pound_pred_question_mark; |
| 496 | static Lisp_Object Vtreesit_str_open_bracket; | 496 | static Lisp_Object Vtreesit_str_open_bracket; |
| 497 | static Lisp_Object Vtreesit_str_close_bracket; | 497 | static Lisp_Object Vtreesit_str_close_bracket; |
| 498 | static Lisp_Object Vtreesit_str_open_paren; | 498 | static Lisp_Object Vtreesit_str_open_paren; |
| 499 | static Lisp_Object Vtreesit_str_close_paren; | 499 | static Lisp_Object Vtreesit_str_close_paren; |
| 500 | static Lisp_Object Vtreesit_str_space; | 500 | static Lisp_Object Vtreesit_str_space; |
| 501 | static Lisp_Object Vtreesit_str_equal; | 501 | static Lisp_Object Vtreesit_str_eq_question_mark; |
| 502 | static Lisp_Object Vtreesit_str_match; | 502 | static Lisp_Object Vtreesit_str_match_question_mark; |
| 503 | static Lisp_Object Vtreesit_str_pred; | 503 | static Lisp_Object Vtreesit_str_pred_question_mark; |
| 504 | static Lisp_Object Vtreesit_str_empty; | 504 | static Lisp_Object Vtreesit_str_empty; |
| 505 | 505 | ||
| 506 | /* This is the limit on recursion levels for some tree-sitter | 506 | /* This is the limit on recursion levels for some tree-sitter |
| @@ -3471,12 +3471,12 @@ See Info node `(elisp)Pattern Matching' for detailed explanation. */) | |||
| 3471 | return Vtreesit_str_star; | 3471 | return Vtreesit_str_star; |
| 3472 | if (BASE_EQ (pattern, QCplus)) | 3472 | if (BASE_EQ (pattern, QCplus)) |
| 3473 | return Vtreesit_str_plus; | 3473 | return Vtreesit_str_plus; |
| 3474 | if (BASE_EQ (pattern, QCequal)) | 3474 | if (BASE_EQ (pattern, QCequal) || BASE_EQ (pattern, QCeq_q)) |
| 3475 | return Vtreesit_str_pound_equal; | 3475 | return Vtreesit_str_pound_eq_question_mark; |
| 3476 | if (BASE_EQ (pattern, QCmatch)) | 3476 | if (BASE_EQ (pattern, QCmatch) || BASE_EQ (pattern, QCmatch_q)) |
| 3477 | return Vtreesit_str_pound_match; | 3477 | return Vtreesit_str_pound_match_question_mark; |
| 3478 | if (BASE_EQ (pattern, QCpred)) | 3478 | if (BASE_EQ (pattern, QCpred) || BASE_EQ (pattern, QCpred_q)) |
| 3479 | return Vtreesit_str_pound_pred; | 3479 | return Vtreesit_str_pound_pred_question_mark; |
| 3480 | Lisp_Object opening_delimeter | 3480 | Lisp_Object opening_delimeter |
| 3481 | = VECTORP (pattern) | 3481 | = VECTORP (pattern) |
| 3482 | ? Vtreesit_str_open_bracket : Vtreesit_str_open_paren; | 3482 | ? Vtreesit_str_open_bracket : Vtreesit_str_open_paren; |
| @@ -3507,7 +3507,9 @@ A PATTERN in QUERY can be | |||
| 3507 | :* | 3507 | :* |
| 3508 | :+ | 3508 | :+ |
| 3509 | :equal | 3509 | :equal |
| 3510 | :eq? | ||
| 3510 | :match | 3511 | :match |
| 3512 | :match? | ||
| 3511 | (TYPE PATTERN...) | 3513 | (TYPE PATTERN...) |
| 3512 | [PATTERN...] | 3514 | [PATTERN...] |
| 3513 | FIELD-NAME: | 3515 | FIELD-NAME: |
| @@ -3670,7 +3672,7 @@ treesit_predicate_equal (Lisp_Object args, struct capture_range captures, | |||
| 3670 | return !NILP (Fstring_equal (text1, text2)); | 3672 | return !NILP (Fstring_equal (text1, text2)); |
| 3671 | } | 3673 | } |
| 3672 | 3674 | ||
| 3673 | /* Handles predicate (#match "regexp" @node). Return true if "regexp" | 3675 | /* Handles predicate (#match? "regexp" @node). Return true if "regexp" |
| 3674 | matches the text spanned by @node; return false otherwise. | 3676 | matches the text spanned by @node; return false otherwise. |
| 3675 | Matching is case-sensitive. If everything goes fine, don't touch | 3677 | Matching is case-sensitive. If everything goes fine, don't touch |
| 3676 | SIGNAL_DATA; if error occurs, set it to a suitable signal data. */ | 3678 | SIGNAL_DATA; if error occurs, set it to a suitable signal data. */ |
| @@ -3680,26 +3682,25 @@ treesit_predicate_match (Lisp_Object args, struct capture_range captures, | |||
| 3680 | { | 3682 | { |
| 3681 | if (list_length (args) != 2) | 3683 | if (list_length (args) != 2) |
| 3682 | { | 3684 | { |
| 3683 | *signal_data = list2 (build_string ("Predicate `match' requires two " | 3685 | *signal_data = list2 (build_string ("Predicate `match?' requires two " |
| 3684 | "arguments but got"), | 3686 | "arguments but got"), |
| 3685 | Flength (args)); | 3687 | Flength (args)); |
| 3686 | return false; | 3688 | return false; |
| 3687 | } | 3689 | } |
| 3688 | Lisp_Object regexp = XCAR (args); | 3690 | Lisp_Object arg1 = XCAR (args); |
| 3689 | Lisp_Object capture_name = XCAR (XCDR (args)); | 3691 | Lisp_Object arg2 = XCAR (XCDR (args)); |
| 3690 | 3692 | Lisp_Object regexp = SYMBOLP (arg2) ? arg1 : arg2; | |
| 3691 | /* It's probably common to get the argument order backwards. Catch | 3693 | Lisp_Object capture_name = SYMBOLP (arg2) ? arg2 : arg1; |
| 3692 | this mistake early and show helpful explanation, because Emacs | 3694 | |
| 3693 | loves you. (We put the regexp first because that's what | 3695 | if (!STRINGP (regexp) || !SYMBOLP (capture_name)) |
| 3694 | string-match does.) */ | 3696 | { |
| 3695 | if (!STRINGP (regexp)) | 3697 | *signal_data = list2 (build_string ("Predicate `match?' takes a regexp " |
| 3696 | xsignal1 (Qtreesit_query_error, | 3698 | "and a node capture (order doesn't " |
| 3697 | build_string ("The first argument to `match' should " | 3699 | "matter), but got"), |
| 3698 | "be a regexp string, not a capture name")); | 3700 | Flength (args)); |
| 3699 | if (!SYMBOLP (capture_name)) | 3701 | return false; |
| 3700 | xsignal1 (Qtreesit_query_error, | 3702 | } |
| 3701 | build_string ("The second argument to `match' should " | 3703 | |
| 3702 | "be a capture name, not a string")); | ||
| 3703 | 3704 | ||
| 3704 | Lisp_Object node = Qnil; | 3705 | Lisp_Object node = Qnil; |
| 3705 | if (!treesit_predicate_capture_name_to_node (capture_name, captures, &node, | 3706 | if (!treesit_predicate_capture_name_to_node (capture_name, captures, &node, |
| @@ -3783,11 +3784,11 @@ treesit_eval_predicates (struct capture_range captures, Lisp_Object predicates, | |||
| 3783 | Lisp_Object predicate = XCAR (tail); | 3784 | Lisp_Object predicate = XCAR (tail); |
| 3784 | Lisp_Object fn = XCAR (predicate); | 3785 | Lisp_Object fn = XCAR (predicate); |
| 3785 | Lisp_Object args = XCDR (predicate); | 3786 | Lisp_Object args = XCDR (predicate); |
| 3786 | if (!NILP (Fstring_equal (fn, Vtreesit_str_equal))) | 3787 | if (!NILP (Fstring_equal (fn, Vtreesit_str_eq_question_mark))) |
| 3787 | pass &= treesit_predicate_equal (args, captures, signal_data); | 3788 | pass &= treesit_predicate_equal (args, captures, signal_data); |
| 3788 | else if (!NILP (Fstring_equal (fn, Vtreesit_str_match))) | 3789 | else if (!NILP (Fstring_equal (fn, Vtreesit_str_match_question_mark))) |
| 3789 | pass &= treesit_predicate_match (args, captures, signal_data); | 3790 | pass &= treesit_predicate_match (args, captures, signal_data); |
| 3790 | else if (!NILP (Fstring_equal (fn, Vtreesit_str_pred))) | 3791 | else if (!NILP (Fstring_equal (fn, Vtreesit_str_pred_question_mark))) |
| 3791 | pass &= treesit_predicate_pred (args, captures, signal_data); | 3792 | pass &= treesit_predicate_pred (args, captures, signal_data); |
| 3792 | else | 3793 | else |
| 3793 | { | 3794 | { |
| @@ -5175,8 +5176,11 @@ syms_of_treesit (void) | |||
| 5175 | DEFSYM (QCstar, ":*"); | 5176 | DEFSYM (QCstar, ":*"); |
| 5176 | DEFSYM (QCplus, ":+"); | 5177 | DEFSYM (QCplus, ":+"); |
| 5177 | DEFSYM (QCequal, ":equal"); | 5178 | DEFSYM (QCequal, ":equal"); |
| 5179 | DEFSYM (QCeq_q, ":eq?"); | ||
| 5178 | DEFSYM (QCmatch, ":match"); | 5180 | DEFSYM (QCmatch, ":match"); |
| 5181 | DEFSYM (QCmatch_q, ":match?"); | ||
| 5179 | DEFSYM (QCpred, ":pred"); | 5182 | DEFSYM (QCpred, ":pred"); |
| 5183 | DEFSYM (QCpred_q, ":pred?"); | ||
| 5180 | DEFSYM (QCline, ":line"); | 5184 | DEFSYM (QCline, ":line"); |
| 5181 | DEFSYM (QCcol, ":col"); | 5185 | DEFSYM (QCcol, ":col"); |
| 5182 | DEFSYM (QCpos, ":pos"); | 5186 | DEFSYM (QCpos, ":pos"); |
| @@ -5357,12 +5361,12 @@ depending on customization of `treesit-enabled-modes'. */); | |||
| 5357 | Vtreesit_str_star = build_string ("*"); | 5361 | Vtreesit_str_star = build_string ("*"); |
| 5358 | staticpro (&Vtreesit_str_plus); | 5362 | staticpro (&Vtreesit_str_plus); |
| 5359 | Vtreesit_str_plus = build_string ("+"); | 5363 | Vtreesit_str_plus = build_string ("+"); |
| 5360 | staticpro (&Vtreesit_str_pound_equal); | 5364 | staticpro (&Vtreesit_str_pound_eq_question_mark); |
| 5361 | Vtreesit_str_pound_equal = build_string ("#equal"); | 5365 | Vtreesit_str_pound_eq_question_mark = build_string ("#eq?"); |
| 5362 | staticpro (&Vtreesit_str_pound_match); | 5366 | staticpro (&Vtreesit_str_pound_match_question_mark); |
| 5363 | Vtreesit_str_pound_match = build_string ("#match"); | 5367 | Vtreesit_str_pound_match_question_mark = build_string ("#match?"); |
| 5364 | staticpro (&Vtreesit_str_pound_pred); | 5368 | staticpro (&Vtreesit_str_pound_pred_question_mark); |
| 5365 | Vtreesit_str_pound_pred = build_string ("#pred"); | 5369 | Vtreesit_str_pound_pred_question_mark = build_string ("#pred?"); |
| 5366 | staticpro (&Vtreesit_str_open_bracket); | 5370 | staticpro (&Vtreesit_str_open_bracket); |
| 5367 | Vtreesit_str_open_bracket = build_string ("["); | 5371 | Vtreesit_str_open_bracket = build_string ("["); |
| 5368 | staticpro (&Vtreesit_str_close_bracket); | 5372 | staticpro (&Vtreesit_str_close_bracket); |
| @@ -5373,12 +5377,12 @@ depending on customization of `treesit-enabled-modes'. */); | |||
| 5373 | Vtreesit_str_close_paren = build_string (")"); | 5377 | Vtreesit_str_close_paren = build_string (")"); |
| 5374 | staticpro (&Vtreesit_str_space); | 5378 | staticpro (&Vtreesit_str_space); |
| 5375 | Vtreesit_str_space = build_string (" "); | 5379 | Vtreesit_str_space = build_string (" "); |
| 5376 | staticpro (&Vtreesit_str_equal); | 5380 | staticpro (&Vtreesit_str_eq_question_mark); |
| 5377 | Vtreesit_str_equal = build_string ("equal"); | 5381 | Vtreesit_str_eq_question_mark = build_string ("eq?"); |
| 5378 | staticpro (&Vtreesit_str_match); | 5382 | staticpro (&Vtreesit_str_match_question_mark); |
| 5379 | Vtreesit_str_match = build_string ("match"); | 5383 | Vtreesit_str_match_question_mark = build_string ("match?"); |
| 5380 | staticpro (&Vtreesit_str_pred); | 5384 | staticpro (&Vtreesit_str_pred_question_mark); |
| 5381 | Vtreesit_str_pred = build_string ("pred"); | 5385 | Vtreesit_str_pred_question_mark = build_string ("pred?"); |
| 5382 | staticpro (&Vtreesit_str_empty); | 5386 | staticpro (&Vtreesit_str_empty); |
| 5383 | Vtreesit_str_empty = build_string (""); | 5387 | Vtreesit_str_empty = build_string (""); |
| 5384 | 5388 | ||
diff --git a/test/src/treesit-tests.el b/test/src/treesit-tests.el index b5ea63a53f3..89303114735 100644 --- a/test/src/treesit-tests.el +++ b/test/src/treesit-tests.el | |||
| @@ -547,10 +547,10 @@ BODY is the test body." | |||
| 547 | ;; String query. | 547 | ;; String query. |
| 548 | '("(string) @string | 548 | '("(string) @string |
| 549 | (pair key: (_) @keyword) | 549 | (pair key: (_) @keyword) |
| 550 | ((_) @bob (#match \"\\\\`B.b\\\\'\" @bob)) | 550 | ((_) @bob (#match? \"\\\\`B.b\\\\'\" @bob)) |
| 551 | (number) @number | 551 | (number) @number |
| 552 | ((number) @n3 (#equal \"3\" @n3)) | 552 | ((number) @n3 (#eq? \"3\" @n3)) |
| 553 | ((number) @n3p (#pred treesit--ert-pred-last-sibling @n3p))" | 553 | ((number) @n3p (#pred? treesit--ert-pred-last-sibling @n3p))" |
| 554 | ;; Sexp query. | 554 | ;; Sexp query. |
| 555 | ((string) @string | 555 | ((string) @string |
| 556 | (pair key: (_) @keyword) | 556 | (pair key: (_) @keyword) |