chromium/tools/emacs/mojom-mode.el

;; Copyright 2023 The Chromium Authors
;; Use of this source code is governed by a BSD-style license that can be
;; found in the LICENSE file.

(require 'cc-mode)
(require 'imenu)

(defgroup mojom nil
  "Major mode for editing mojom files."
  :prefix "mojom-"
  :group 'languages)

;; Syntax highlighting directives are based on the Mojom IDL grammar:
;; https://chromium.googlesource.com/chromium/src/+/master/mojo/public/tools/bindings#grammar-reference

;; Based on <Identifier> in the grammar.
(defconst mojom-regexp-identifier "[a-zA-Z_][0-9a-zA-Z_]*")
;; Invented to match fully-qualified type names like `foo.bar.Baz`.
(defconst mojom-regexp-qualified-type
  (concat "\\(" mojom-regexp-identifier "\\.\\)*\\(?2:" mojom-regexp-identifier "\\)\\??"))
(defconst mojom-regexp-whitespace "[\s\n]+")

(defconst mojom-idl-builtins
  '(
    ;; <NumericType> in the grammar.
    "bool"
    "int8" "int16" "int32" "int64"
    "uint8" "uint16" "uint32" "uint64"
    "float" "double"
    ;; <Array> in the grammar.
    "array"
    ;; <Map> in the grammar.
    "map"
    ;; <HandleType> in the grammar.
    "handle"
    ;; <SpecificHandleType> in the grammar.
    "message_pipe"
    "shared_buffer"
    "data_pipe_consumer"
    "data_pipe_producer"
    "platform"
    ;; These are part of the language, but not documented in the grammar.
    "pending_remote"
    "pending_receiver"
    "pending_associated_remote"
    "pending_associated_receiver"
    "string"
    ))

(defconst mojom-idl-keywords
  '(
    ;; <ModuleStatement> in the grammar.
    "module"
    ;; <ImportStatement> in the grammar.
    "import"
    ;; <Interface> in the grammar.
    "interface"
    ;; <Struct> in the grammar.
    "struct"
    ;; <Union> in the grammar.
    "union"
    ;; <Enum> in the grammar.
    "enum"
    ;; <Const> in the grammar.
    "const"
    ;; <Feature> in the grammar.
    "feature"
    ))

(defconst mojom-idl-constants
  '(
    ;; <Literal> in the grammar.
    "true" "false"  "default"))

(defconst mojom-font-lock-keywords
  `(
    ;; <ModuleStatement> in the grammar.
    (,(concat "module\s+\\(" mojom-regexp-qualified-type "\\)")
     1 font-lock-string-face)

    ;; Function names.
    (,(concat "\\(" mojom-regexp-identifier "\\)\s*(")
      1 font-lock-function-name-face)

    ;; Punctuation.
    (,(regexp-opt '("=>" "?" "<" ">")) . font-lock-negation-char-face)

    (,(regexp-opt mojom-idl-builtins 'words) . font-lock-builtin-face)
    (,(regexp-opt mojom-idl-keywords 'words) . font-lock-keyword-face)
    (,(regexp-opt mojom-idl-constants 'words) . font-lock-constant-face)

    ;; Match types followed by identifiers, respectively highlighting them as
    ;; font-lock-type-face and font-lock-variable-name-face. For types that have
    ;; dotted module paths, only highlight the final element of the name. For
    ;; example, in `foo.bar.Baz baz`, we only want to highlight "Baz" as a type.
    (,(concat
       "\\(" mojom-regexp-qualified-type "\\|<" mojom-regexp-qualified-type ">\\)"
       mojom-regexp-whitespace
       mojom-regexp-identifier)
     2 font-lock-type-face)
    (,(concat
       "\\(" mojom-regexp-qualified-type "\\|<" mojom-regexp-qualified-type ">\\)"
       mojom-regexp-whitespace
       "\\(?3:" mojom-regexp-identifier "\\)"
       )
     3 font-lock-variable-name-face)))

(define-derived-mode mojom-mode c++-mode "Mojom"
  "Major mode for editing mojom files."
  :group 'mojom

  ;; Ensure that underscores are considered part of a word. This was necessary
  ;; to ensure that "handle" in "foo_handle_bar" isn't highlighted as a builtin.
  (modify-syntax-entry ?_ "w")

  ;; Completely replace c++-mode's font-lock configuration.
  (setq-local font-lock-defaults '(mojom-font-lock-keywords))

  ;; Configure indentation.
  (setq-local indent-tabs-mode nil)
  (setq-local tab-width 2)

  ;; Configure basic imenu functionality. This dumps everything at the same
  ;; level, but reuses the line's indentation as the imenu label for a visual.
  ;; We are not actually parsing the file, so this will inevitably produce
  ;; incomplete and incorrect results in some circumstances.
  ;;
  ;; TODO(dmcardle) Either add support for enum values or drop support for
  ;; fields. Maybe the latter would be more useful as an index anyway.
  (setq-local imenu-generic-expression
              `(
                ;; Definitions of interfaces, structs, enums, and unions.
                (nil
                 ,(concat "^\s*\\(interface\\|struct\\|enum\\|union\\)"
                          mojom-regexp-whitespace
                          mojom-regexp-identifier)
                 0)
                ;; Definitions of fields.
                (nil
                 ,(let ((maybe-attribute-section "\\(\\[[^]]*\\]\\)?")
                        (type-maybe-parameterized "[A-Za-z0-9_.,<>\s]+\\??")
                        (maybe-ordinal-value "\\(@[0-9]+\\)?")
                        (maybe-whitespace "[\s\n]*"))
                    (concat
                     "^\s*"
                     maybe-attribute-section maybe-whitespace
                     type-maybe-parameterized mojom-regexp-whitespace
                     mojom-regexp-identifier maybe-whitespace
                     maybe-ordinal-value maybe-whitespace
                     ";"))
                 0)
                ;; Function names. An identifier followed by an open parenthesis.
                (nil ,(concat "^\s*" mojom-regexp-identifier "[\s\n]*(") 0)))

  (imenu-add-menubar-index))

(add-to-list 'auto-mode-alist '("\\.mojom\\'" . mojom-mode))

(provide 'mojom-mode)