diff --git a/grammar.py b/grammar.py index e46c94d..51c2766 100644 --- a/grammar.py +++ b/grammar.py @@ -439,4 +439,4 @@ if __name__ == "__main__": ts_path = Path(__file__).parent / "tree-sitter-fine" emit_tree_sitter_grammar(grammar, ts_path) emit_tree_sitter_queries(grammar, ts_path) - emit_emacs_major_mode(grammar, ts_path) + emit_emacs_major_mode(grammar, ts_path / "fine.el") diff --git a/parser/emacs.py b/parser/emacs.py index 2f51c0b..1a73d88 100644 --- a/parser/emacs.py +++ b/parser/emacs.py @@ -5,6 +5,13 @@ import pathlib import textwrap from parser.tree_sitter import terminal_name +from parser.generated_source import ( + begin_manual_section, + end_manual_section, + merge_existing, + sign_generated_source, + signature_token, +) from . import parser @@ -116,5 +123,116 @@ def gather_faces(grammar: parser.Grammar): return feature_string -def emit_emacs_major_mode(grammar: parser.Grammar, path: pathlib.Path | str): +def emit_emacs_major_mode(grammar: parser.Grammar, file_path: pathlib.Path | str): + if isinstance(file_path, str): + file_path = pathlib.Path(file_path) + face_var = gather_faces(grammar) + + contents = f""" +;;; {file_path.name} --- Major mode for editing {grammar.name} --- -*- lexical-binding: t -*- + +;; NOTE: This file is partially generated. +;; Only modify marked sections, or your modifications will be lost! +;; {signature_token()} + +;; {begin_manual_section('commentary')} + +;; This is free and unencumbered software released into the public domain. +;; Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +;; software, either in source code form or as a compiled binary, for any purpose, +;; commercial or non-commercial, and by any means. +;; +;; In jurisdictions that recognize copyright laws, the author or authors of this +;; software dedicate any and all copyright interest in the software to the public +;; domain. We make this dedication for the benefit of the public at large and to +;; the detriment of our heirs and successors. We intend this dedication to be an +;; overt act of relinquishment in perpetuity of all present and future rights to +;; this software under copyright law. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +;; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +;;; Commentary: +;; (Nobody has written anything about the major mode yet.) + +;; {end_manual_section()} + +;;; Code: +(require 'treesit) + +;; {begin_manual_section('prologue')} + +;; {end_manual_section()} + +{face_var} + +(defun {grammar.name}-ts-setup () + "Setup for {grammar.name}-mode." + + ;; {begin_manual_section('setup_prologue')} + ;; {end_manual_section()} + + ;; Set up the font-lock rules. + (setq-local treesit-font-lock-settings + (apply #'treesit-font-lock-rules + {grammar.name}-font-lock-rules)) + + ;; {begin_manual_section('feature_list')} + ;; NOTE: This list is just to get you started; these are some of the standard + ;; features and somewhat standard positions in the feature list. You can + ;; edit this to more closely match your grammar's output. (The info page + ;; for treesit-font-lock-feature-list describes what it does nicely.) + (setq-local treesit-font-lock-feature-list + '((comment definition) + (keyword string) + (assignment attribute builtin constant escape-sequence number type) + (bracket delimiter error function operator property variable))) + ;; {end_manual_section()} + + ;; {begin_manual_section('setup_epilogue')} + ;; If you want to set up more do it here. + ;; {end_manual_section()} + + (treesit-major-mode-setup)) + +;;;###autoload +(define-derived-mode {grammar.name}-mode prog-mode "{grammar.name}" + "Major mode for editing {grammar.name} files." + + (setq-local font-lock-defaults nil) + (when (treesit-ready-p '{grammar.name}) + (treesit-parser-create '{grammar.name}) + ({grammar.name}-ts-setup))) + + +;; {begin_manual_section('eplogue')} + +;; {end_manual_section()} +;;; {file_path.name} ends here +""".lstrip() + + # Sign the contents to give folks a way to check that they haven't been + # messed with. + contents = sign_generated_source(contents) + + # Try to pull existing file contents out and merge them with the + # generated code. This preserves hand-editing in approved areas. + try: + with open(file_path, "r", encoding="utf-8") as file: + existing_contents = file.read() + contents = merge_existing(existing_contents, contents) + except Exception: + pass + + # Ensure that parent directories are created as necessary for the output. + if not file_path.parent.exists(): + file_path.parent.mkdir(parents=True, exist_ok=True) + + # And write the file! + with open(file_path, "w", encoding="utf-8") as file: + file.write(contents)