;; 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)