emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: New tree-sitter mode: bison-ts-mode


From: Philip Kaludercic
Subject: Re: New tree-sitter mode: bison-ts-mode
Date: Fri, 22 Sep 2023 20:40:36 +0000

"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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]