"Augustin Chéneau (BTuin)" <btuin@mailo.com> writes:
Le 22/09/2023 à 09:38, Philip Kaludercic a écrit :
"Augustin Chéneau (BTuin)" <btuin@mailo.com> writes:
A few comments on the proposed file:
;;; bison-ts-mode --- Tree-sitter mode for Bison -*- lexical-binding: t; -*-
Could you add the usual header information here?
;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
--8<---------------cut here---------------start------------->8---
;; Author: Augustin Chéneau <btuin@mailo.com>
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
--8<---------------cut here---------------end--------------->8---
;;; Commentary:
;; This is a mode based on tree-sitter for Bison and Yacc files, tools to
generate parsers.
Shouldn't you mention what tree sitter grammar is being being used
here?
;;; Code:
(require 'treesit)
(require 'c-ts-common)
(declare-function treesit-parser-create "treesit.c")
(declare-function treesit-induce-sparse-tree "treesit.c")
(declare-function treesit-node-child-by-field-name "treesit.c")
(declare-function treesit-search-subtree "treesit.c")
(declare-function treesit-node-parent "treesit.c")
(declare-function treesit-node-next-sibling "treesit.c")
(declare-function treesit-node-type "treesit.c")
(declare-function treesit-node-child "treesit.c")
(declare-function treesit-node-end "treesit.c")
(declare-function treesit-node-start "treesit.c")
(declare-function treesit-node-string "treesit.c")
(declare-function treesit-query-compile "treesit.c")
(declare-function treesit-query-capture "treesit.c")
(declare-function treesit-parser-add-notifier "treesit.c")
(declare-function treesit-parser-buffer "treesit.c")
(declare-function treesit-parser-list "treesit.c")
(defgroup bison nil
bison or bison-ts?
As far as I know, no group is explicitly reserved for
tree-sitter. rust-ts-mode uses the group "rust", java-ts-mode uses
"java", ruby-ts-mode uses "ruby"...
OK, that is fine then.
"Support for Bison and Yacc."
Shouldn't tree-sitter be mentioned here?
Same as above.
Sure? Above I was talking about using the group name "bison" or
"bison-ts", this is about mentioning that tree sitter is required for
the mode to work.
:group 'languages)
(defcustom bison-ts-mode-indent-offset 2
"Number of spaces for each indentation step in `bison-ts-mode'.
It has no effect in the epilogue part of the file."
:version "30.1"
:type 'integer
:safe 'integerp
:group 'bison)
(defcustom bison-ts-mode-autodetect-language t
"Search for a %language directive in the file at initialization.
Changing the value of this directive in the file requires to reload the mode to
be effective. If `bison-ts-mode-buffer-language' is set by a file-local
variable, the auto-detection is not run."
:version "30.1"
:type 'boolean
:safe 'boolean
:group 'bison)
(defvar-local bison-ts-mode-embedded-language nil
"Embedded language in Bison buffer.")
(defun bison-ts-mode--merge-feature-lists (l1 l2)
"Merge the lists of lists L1 and L2.
The first sublist of L1 is merged with the first sublist of L2 and so on.
L1 and L2 don't need to have the same size."
(let ((res ()))
(while (or l1 l2)
(setq res (push (append (car l1) (car l2)) res))
(setq l1 (cdr l1) l2 (cdr l2)))
(nreverse res)))
Is this a generic function that should be extracted into some common
file?
It probably should, it could be useful for other *-ts-mode that want
to mix multiple languages, unless there already is such a function in
Elisp.
Then again, if you use `cl-loop', you can reproduce this specific
behaviour using
(cl-loop for i1 in '(1 2 3) collect i1
for i2 in '(a b c) collect i2)
(defun bison-ts-mode--find-language-in-buffer (&optional buffer)
"Find and return the language set by the Bison directive %language.
If BUFFER is set, search in this buffer, otherwise search in the current
buffer."
(save-excursion
(when buffer
(switch-to-buffer buffer))
Or rather?
(with-current-buffer (or buffer (current-buffer))
(save-excursion
...))
(goto-char (point-min))
(let ((pos-end
(re-search-forward
(rx
bol (0+ blank) "%language" (0+ blank) "\"" (1+ (in alpha "+"))
"\"")
nil
t))
(pos-beg nil))
(when pos-end
Using when-let might be nice here.
(goto-char (1- pos-end))
(setq pos-beg (1+ (search-backward "\"" nil t)))
(buffer-substring-no-properties pos-beg (1- pos-end))))))
I'd use a single regular expression here with a group, then extract
the
right information using `match-string'.
Nice, I didn't know it was possible.
(defun bison-ts-mode--detect-language (&optional buffer)
"Dectect the embedded language in a Bison buffer.
Known languages are C, C++, D, and Java, but D is not supported as there is
no support for tree-sitter D in Emacs yet.
If BUFFER is set, search in this buffer, otherwise search in the current
buffer."
(if-let ((str (bison-ts-mode--find-language-in-buffer buffer)))
(pcase (downcase str)
("c" 'c)
("c++" 'cpp)
("d" (progn (message "D language not yet supported") nil))
Each pcase case has an implicit progn.
("java" 'java))
(progn
Use when-let to avoid this progn.
(message
"bison-ts-mode: %%language specification not found or invalid, defaulting
to C.")
Is it necessary to prefix the message with the major mode name?
If feared it would be a bit cryptic without. Anyway I modified this
function to only display a message if "%language" is present but the
language is invalid, so it probably isn't necessary anymore.
OK.
'c)))
(defun bison-ts-mode--language-at-point-function (position)
"Return the language at POSITION."
(let* ((node (treesit-node-at position 'bison)))
^
let is enough
(if (equal (treesit-node-type node)
"embedded_code")
There is no need to break the line here.
bison-ts-mode-embedded-language
'bison)))
(defun bison-ts-mode--font-lock-settings (language)
"Return the font-lock settings for Bison.
LANGUAGE should be set to \\='bison."
(treesit-font-lock-rules
:language language
:feature 'bison-comment
'((comment) @font-lock-comment-face)
:language language
:feature 'bison-declaration
'((declaration_name) @font-lock-keyword-face)
:language language
:feature 'bison-type
'((type) @font-lock-type-face)
:language language
:feature 'bison-grammar-rule-usage
'((grammar_rule_identifier) @font-lock-variable-use-face)
:language language
:feature 'bison-grammar-rule-declaration
'((grammar_rule (grammar_rule_declaration)
@font-lock-variable-use-face))
:language language
:feature 'bison-string
:override t
'((string) @font-lock-string-face)
:language language
:feature 'bison-literal
:override t
'((char_literal) @font-lock-keyword-face
(number_literal) @font-lock-number-face)
:language language
:feature 'bison-directive-grammar-rule
:override t
'((grammar_rule (directive) @font-lock-keyword-face))
:language language
:feature 'bison-operator
:override t
'(["|"] @font-lock-operator-face)
:language language
:feature 'bison-delimiter
:override t
'([";"] @font-lock-delimiter-face)))
(defvar bison-ts-mode--font-lock-feature-list
'(( bison-comment bison-declaration bison-type
bison-grammar-rule-usage bison-grammar-rule-declaration
bison-string bison-literal bison-directive-grammar-rule
bison-operator bison-delimiter)))
(defun bison-ts-mode--bison-matcher-action (root-name)
"Treesit matcher to check if NODE at BOL is not located in the epilogue.
ROOT-NAME is the highest-level node of the embedded language."
(lambda (node _parent bol &rest _)
(if (equal (treesit-node-type (treesit-node-parent node)) root-name)
(let* ((bison-node (treesit-node-at bol 'bison)))
^
here again, let is enough
(if (equal
(treesit-node-type
(treesit-node-parent(treesit-node-parent bison-node)))
"action")
Though you could bind the (treesit-node-type ...) expression under
the
above let.
t
nil)))))
Why (if foo t nil) when foo would do the same job (equal only
returns
nil and t, so normalising the value isn't even necessary).
Because I was stupid.
OK, I am glad I didn't miss something obvious ^^
(defun bison-ts-mode--bison-matcher-not-epilogue (root-name)
"Treesit matcher to check if NODE at BOL is not located in the epilogue.
ROOT-NAME is the highest-level node of the embedded language."
(lambda (node _parent bol &rest _)
(if (equal (treesit-node-type (treesit-node-parent node)) root-name)
(let* ((bison-node (treesit-node-at bol 'bison)))
(if (equal (treesit-node-type (treesit-node-parent bison-node))
"epilogue")
nil
t)))))
Am I missing something, or couldn't these two functions be merged if
you
give them a third argument NODE-TYPE and pass it "action" or "epilogue".
No, bison-ts-mode--bison-matcher-action checks if the _grandparent_ is
an "action" node, while bison-ts-mode--bison-matcher-not-epilogue
checks if the _parent_ is an "epilogue" node.
In that case, would adding another parameter and then binding the
returned lambda expression via defalias be worth the effort?
(defun bison-ts-mode--bison-parent (_node _parent bol &rest _)
"Get the parent of the bison node at BOL."
(treesit-node-start (treesit-node-parent (treesit-node-at bol 'bison))))
(defun bison-ts-mode--indent-rules ()
"Indent rules supported by `bison-ts-mode'."
(let*
((common
`(((node-is "^declaration$")
column-0 0)
((and (parent-is "^declaration$")
(not (node-is "^code_block$")))
column-0 2)
((and (parent-is "comment") c-ts-common-looking-at-star)
c-ts-common-comment-start-after-first-star -1)
(c-ts-common-comment-2nd-line-matcher
c-ts-common-comment-2nd-line-anchor
1)
((parent-is "comment") prev-adaptive-prefix 0)
;; Opening and closing brackets "{}" of declarations
((and (parent-is "^declaration$")
(node-is "^code_block$"))
column-0 0)
((and (n-p-gp "}" "" "^declaration$"))
column-0 0)
((parent-is "^declaration$") parent 2)
((node-is "^grammar_rule$") column-0 0)
((and
(parent-is "^grammar_rule$")
(node-is ";"))
column-0 bison-ts-mode-indent-offset)
((and (parent-is "^grammar_rule$")
(node-is "|"))
column-0 bison-ts-mode-indent-offset)
((and (parent-is "^grammar_rule$")
(not (node-is "^grammar_rule_declaration$"))
(not (node-is "^action$")))
column-0 ,(+ bison-ts-mode-indent-offset 2))
((or
(node-is "^action$")
(node-is "^}$"))
column-0 12)
;; Set '%%' at the beginning of the line
((or
(and (parent-is "^grammar_rules_section$")
(node-is "%%"))
(node-is "^grammar_rules_section$"))
column-0 0)
(no-node parent-bol 0))))
`((bison . ,common)
;; Import and override embedded languages rules to add an offset
,(pcase bison-ts-mode-embedded-language
('c `(c
((bison-ts-mode--bison-matcher-action "translation_unit")
bison-ts-mode--bison-parent ,bison-ts-mode-indent-offset)
((bison-ts-mode--bison-matcher-not-epilogue "translation_unit")
column-0 ,bison-ts-mode-indent-offset)
,@(alist-get 'c (c-ts-mode--get-indent-style 'c))))
('cpp `(cpp
((bison-ts-mode--bison-matcher-action "translation_unit")
bison-ts-mode--bison-parent ,bison-ts-mode-indent-offset)
((bison-ts-mode--bison-matcher-not-epilogue
"translation_unit")
parent-0 ,bison-ts-mode-indent-offset)
,@(alist-get 'cpp (c-ts-mode--get-indent-style 'cpp))))
('java `(java
((bison-ts-mode--bison-matcher-action "program")
bison-ts-mode--bison-parent ,bison-ts-mode-indent-offset)
((bison-ts-mode--bison-matcher-not-epilogue "program")
column-0 ,bison-ts-mode-indent-offset)
,@java-ts-mode--indent-rules))))))
(define-derived-mode bison-ts-mode prog-mode "Bison"
"A mode for Bison."
^
major-mode
Also, mentioning tree-sitter seems like something worth doing.
(when (treesit-ready-p 'bison)
(when (not bison-ts-mode-embedded-language)
(setq bison-ts-mode-embedded-language (bison-ts-mode--detect-language)))
;; Require only if needed, to avoid warnings if a grammar is not
;; installed but not used.
(pcase bison-ts-mode-embedded-language
Would a `pcase-exhaustive' be appropriate here?
No, the language D is recognized but not supported yet in Emacs, so if
this language is detected it will not configure anything.
There is a d-mode in NonGNU ELPA, but I guess that isn't enough since
you need a d-ts-mode, right?
('c (require 'c-ts-mode))
('cpp (require 'c-ts-mode))
('java (require 'java-ts-mode)))
(setq-local treesit-font-lock-settings
(append (bison-ts-mode--font-lock-settings 'bison)
(pcase bison-ts-mode-embedded-language
('c (c-ts-mode--font-lock-settings 'c))
('cpp (c-ts-mode--font-lock-settings 'cpp))
('java java-ts-mode--font-lock-settings))))
(setq-local treesit-font-lock-feature-list
(if bison-ts-mode-embedded-language
(bison-ts-mode--merge-feature-lists
bison-ts-mode--font-lock-feature-list
(pcase bison-ts-mode-embedded-language
('c c-ts-mode--feature-list)
('cpp c-ts-mode--feature-list)
('java java-ts-mode--feature-list)))
bison-ts-mode--font-lock-feature-list))
(setq-local treesit-simple-imenu-settings
`(("Grammar"
"\\`grammar_rule_declaration\\'"
nil
(lambda (node) (substring-no-properties (treesit-node-text
node))))))
(c-ts-common-comment-setup)
(setq-local treesit-simple-indent-rules
(bison-ts-mode--indent-rules))
(setq-local treesit-language-at-point-function
'bison-ts-mode--language-at-point-function)
(when bison-ts-mode-embedded-language
(setq-local treesit-range-settings
(treesit-range-rules
:embed bison-ts-mode-embedded-language
:host 'bison
:local t
'((embedded_code) @capture))))
(treesit-major-mode-setup)))
(provide 'bison-ts-mode)
;;; bison-ts-mode.el ends here
Sorry for the number of comments, but there has been a discussion on
the
code-quality of tree-sitter major modes that has been less than optimal,
so I hope that your contribution could help raise the bar.
No problem, thank you for your review!
[...]
(defgroup bison-ts nil
"Support for Bison and Yacc."
:group 'languages)
(defcustom bison-ts-mode-indent-offset 2
"Number of spaces for each indentation step in `bison-ts-mode'.
It has no effect in the epilogue part of the file."
:version "30.1"
:type 'integer
:safe 'integerp
:group 'bison)
The ":group" annotations here are not necessary in general, defcustoms
can automatically detect the previous defgroup.
(defcustom bison-ts-mode-autodetect-language t
"Search for a %language directive in the file at initialization.
Changing the value of this directive in the file requires to reload the mode to
be effective. If `bison-ts-mode-buffer-language' is set by a file-local
variable, the auto-detection is not run."
:version "30.1"
:type 'boolean
:safe 'boolean
:group 'bison)
(defvar-local bison-ts-mode-embedded-language nil
"Embedded language in Bison buffer.
Supported values are `c', `cpp', and `java'.")
;;;###autoload
(put 'bison-ts-mode-embedded-language 'safe-local-variable 'symbolp)
(defun bison-ts-mode--merge-feature-lists (l1 l2)
"Merge the lists of lists L1 and L2.
The first sublist of L1 is merged with the first sublist of L2 and so on.
L1 and L2 don't need to have the same size."
(let ((res ()))
(while (or l1 l2)
(setq res (push (seq-uniq (append (car l1) (car l2)) 'eq) res))
(setq l1 (cdr l1) l2 (cdr l2)))
(nreverse res)))
(defun bison-ts-mode--find-language-in-buffer (&optional buffer)
"Find and return the language set by the Bison directive %language.
If BUFFER is set, search in this buffer, otherwise search in the current
buffer."
(save-excursion
(with-current-buffer (or buffer (current-buffer))
(goto-char (point-min))
(when
(re-search-forward
(rx
bol (0+ blank) "%language" (0+ blank) "\"" (group (1+ (in alpha "+")))
"\"")
I'd say this regular expression is complex enough to be split into
multiple lines. And you can use the fact that `rx' takes s-expressions
to add comments inbetween.
nil
t)))
(substring-no-properties (match-string 1))))
Or `match-string-no-properties'
(defun bison-ts-mode--detect-language (&optional buffer)
"Dectect the embedded language in a Bison buffer.
Known languages are C, C++, D, and Java, but D is not supported as there is
no support for tree-sitter D in Emacs yet.
If BUFFER is set, search in this buffer, otherwise search in the current
buffer."
(if-let ((str (bison-ts-mode--find-language-in-buffer buffer)))
(pcase-exhaustive (downcase str)
("c" 'c)
("c++" 'cpp)
("d" (message "D language not yet supported") nil)
("java" 'java)
(_ (message "%%language specification \"%s\" is invalid, defaulting to
C" str) 'c))))
No point in using `pcase-exhaustive' if you end with _ anyway?
(defun bison-ts-mode--language-at-point-function (position)
"Return the language at POSITION."
(let ((node (treesit-node-at position 'bison)))
(if (equal (treesit-node-type node) "embedded_code")
bison-ts-mode-embedded-language
'bison)))
(defun bison-ts-mode--font-lock-settings (language)
"Return the font-lock settings for Bison.
LANGUAGE should be set to \\='bison."
(treesit-font-lock-rules
:language language
:feature 'comment
'((comment) @font-lock-comment-face)
:language language
:feature 'declaration
'((declaration_name) @font-lock-keyword-face)
:language language
:feature 'type
'((type) @font-lock-type-face)
:language language
:feature 'variable
'((grammar_rule_identifier) @font-lock-variable-use-face)
:language language
:feature 'grammar-declaration
'((grammar_rule (grammar_rule_declaration)
@font-lock-variable-use-face))
:language language
:feature 'string
:override t
'((string) @font-lock-string-face)
:language language
:feature 'literal
:override t
'((char_literal) @font-lock-keyword-face
(number_literal) @font-lock-number-face)
:language language
:feature 'directive-grammar-rule
:override t
'((grammar_rule (directive) @font-lock-keyword-face))
:language language
:feature 'operator
:override t
'(["|"] @font-lock-operator-face)
:language language
:feature 'delimiter
:override t
'([";"] @font-lock-delimiter-face)))
(defvar bison-ts-mode--font-lock-feature-list
I am not that familiar with the tree-sitter stuff, but would it be
possible to use `defconst' here?
'(( comment declaration grammar-declaration)
( type string directive-grammar-rule)
( literal)
( variable operator delimiter)))
(defun bison-ts-mode--bison-matcher-action (root-name)
"Treesit matcher to check if NODE at BOL is located in an action node.
ROOT-NAME is the highest-level node of the embedded language."
(lambda (node _parent bol &rest _)
(if (equal (treesit-node-type (treesit-node-parent node)) root-name)
(let ((bison-node (treesit-node-at bol 'bison)))
(equal
(treesit-node-type
(treesit-node-parent (treesit-node-parent bison-node)))
"action")))))
(defun bison-ts-mode--bison-matcher-not-epilogue (root-name)
"Treesit matcher to check if NODE at BOL is not located in the epilogue.
ROOT-NAME is the highest-level node of the embedded language."
(lambda (node _parent bol &rest _)
(if (equal (treesit-node-type (treesit-node-parent node)) root-name)
(let ((bison-node (treesit-node-at bol 'bison)))
(not (equal (treesit-node-type (treesit-node-parent bison-node))
"epilogue"))))))
(defun bison-ts-mode--bison-parent (_node _parent bol &rest _)
"Get the parent of the bison node at BOL."
(treesit-node-start (treesit-node-parent (treesit-node-at bol 'bison))))
(defun bison-ts-mode--indent-rules ()
"Indent rules supported by `bison-ts-mode'."
(let*
((common
`(((node-is "^declaration$")
column-0 0)
((and (parent-is "^declaration$")
(not (node-is "^code_block$")))
column-0 2)
((and (parent-is "comment") c-ts-common-looking-at-star)
c-ts-common-comment-start-after-first-star -1)
(c-ts-common-comment-2nd-line-matcher
c-ts-common-comment-2nd-line-anchor
1)
((parent-is "comment") prev-adaptive-prefix 0)
;; Opening and closing brackets "{}" of declarations
((and (parent-is "^declaration$")
(node-is "^code_block$"))
column-0 0)
((and (n-p-gp "}" "" "^declaration$"))
column-0 0)
((parent-is "^declaration$") parent 2)
((node-is "^grammar_rule$") column-0 0)
((and
(parent-is "^grammar_rule$")
(node-is ";"))
column-0 bison-ts-mode-indent-offset)
((and (parent-is "^grammar_rule$")
(node-is "|"))
column-0 bison-ts-mode-indent-offset)
((and (parent-is "^grammar_rule$")
(not (node-is "^grammar_rule_declaration$"))
(not (node-is "^action$")))
column-0 ,(+ bison-ts-mode-indent-offset 2))
((or
(node-is "^action$")
(node-is "^}$"))
column-0 12)
;; Set '%%' at the beginning of the line
((or
(and (parent-is "^grammar_rules_section$")
(node-is "%%"))
(node-is "^grammar_rules_section$"))
column-0 0)
(no-node parent-bol 0))))
`((bison . ,common)
;; Import and override embedded languages rules to add an offset
,(pcase bison-ts-mode-embedded-language
('c `(c
((bison-ts-mode--bison-matcher-action "translation_unit")
bison-ts-mode--bison-parent ,bison-ts-mode-indent-offset)
((bison-ts-mode--bison-matcher-not-epilogue "translation_unit")
column-0 ,bison-ts-mode-indent-offset)
,@(alist-get 'c (c-ts-mode--get-indent-style 'c))))
('cpp `(cpp
((bison-ts-mode--bison-matcher-action "translation_unit")
bison-ts-mode--bison-parent ,bison-ts-mode-indent-offset)
((bison-ts-mode--bison-matcher-not-epilogue
"translation_unit")
parent-0 ,bison-ts-mode-indent-offset)
,@(alist-get 'cpp (c-ts-mode--get-indent-style 'cpp))))
('java `(java
((bison-ts-mode--bison-matcher-action "program")
bison-ts-mode--bison-parent ,bison-ts-mode-indent-offset)
((bison-ts-mode--bison-matcher-not-epilogue "program")
column-0 ,bison-ts-mode-indent-offset)
,@java-ts-mode--indent-rules))))))
(define-derived-mode bison-ts-mode prog-mode "Bison"
"A major-mode for Bison based on tree-sitter."
(when (treesit-ready-p 'bison)
(when (not bison-ts-mode-embedded-language)
Or `unless'
(setq bison-ts-mode-embedded-language (bison-ts-mode--detect-language)))
;; Require only if needed, to avoid warnings if a grammar is not
;; installed but not used.
(pcase bison-ts-mode-embedded-language
('c (require 'c-ts-mode))
('cpp (require 'c-ts-mode))
('java (require 'java-ts-mode)))
(setq-local treesit-font-lock-settings
(append (bison-ts-mode--font-lock-settings 'bison)
(pcase bison-ts-mode-embedded-language
('c (c-ts-mode--font-lock-settings 'c))
('cpp (c-ts-mode--font-lock-settings 'cpp))
('java java-ts-mode--font-lock-settings))))
(setq-local treesit-font-lock-feature-list
(if bison-ts-mode-embedded-language
(bison-ts-mode--merge-feature-lists
bison-ts-mode--font-lock-feature-list
(pcase bison-ts-mode-embedded-language
('c c-ts-mode--feature-list)
('cpp c-ts-mode--feature-list)
('java java-ts-mode--feature-list)))
bison-ts-mode--font-lock-feature-list))
(setq-local treesit-simple-imenu-settings
`(("Grammar"
"\\`grammar_rule_declaration\\'"
nil
(lambda (node) (substring-no-properties (treesit-node-text
node))))))
The function `treesit-node-text' appears to take an optional NO-PROPERTY
argument.
(c-ts-common-comment-setup)
(setq-local treesit-simple-indent-rules
(bison-ts-mode--indent-rules))
(setq-local treesit-language-at-point-function
'bison-ts-mode--language-at-point-function)
(when bison-ts-mode-embedded-language
(setq-local treesit-range-settings
(treesit-range-rules
:embed bison-ts-mode-embedded-language
:host 'bison
:local t
'((embedded_code) @capture))))
(treesit-major-mode-setup)))
(provide 'bison-ts-mode)
;;; bison-ts-mode.el ends here