1 ;;; rnc-mode.el --- Emacs mode to edit Relax-NG Compact files -*- lexical-binding:t -*-
3 ;; Copyright (C) 1994-1998, 2001-2016 Free Software Foundation, Inc.
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6 ;; Keywords: xml relaxng
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
33 (add-to-list 'auto-mode-alist '("\\.rnc\\'" . rnc-mode))
35 (defconst rnc-mode-syntax-table
36 (let ((st (make-syntax-table)))
37 (modify-syntax-entry ?# "<" st)
38 (modify-syntax-entry ?\n ">" st)
39 (modify-syntax-entry ?\" "\"" st)
40 (modify-syntax-entry ?- "_" st)
41 (modify-syntax-entry ?. "_" st)
42 (modify-syntax-entry ?: "_" st)
43 (modify-syntax-entry ?_ "_" st)
46 (defconst rnc--keywords
47 ;; Taken from the grammar in http://relaxng.org/compact-20021121.html,
48 ;; by order of appearance.
49 '("namespace" "default" "datatypes" "element" "attribute"
50 "list" "mixed" "parent" "empty" "text" "notAllowed" "external"
51 "grammar" "div" "include" ;; "start"
52 "string" "token" "inherit"))
54 (defconst rnc--def-regexp "^[ \t]*\\([\\[:alpha:]][[:alnum:]-._]*\\)[ \t]*=")
56 (defconst rnc-font-lock-keywords
57 `((,rnc--def-regexp (1 font-lock-function-name-face))
58 (,(concat "\\_<" (regexp-opt rnc--keywords) "\\_>")
59 (0 font-lock-keyword-face))
60 ("attribute[ \t\n]+\\([^ ]+\\)" (1 'nxml-attribute-local-name))
61 ;; FIXME: We'd like to use nxml-element-local-name for element names,
62 ;; but by default this looks exactly like font-lock-function-name-face,
63 ;; which we want to use for local pattern definitions.
64 ;; ("element[ \t\n]+\\([^ ]+\\)" (1 'nxml-element-local-name))
67 (defconst rnc-imenu-generic-expression `((nil ,rnc--def-regexp 1)))
69 (defconst rnc-smie-grammar
70 ;; The body of an RNC file is a sequence of definitions.
71 ;; Problem is: these definitions are not separated by any special keyword.
72 ;; It's basically a repetition of (id "=" pattern), where
73 ;; patterns can end with:
74 ;; "}", ")" "*", "+", "?", id, stringliteral
75 ;; Since this is way beyond the power of SMIE, we resort to using a pseudo
76 ;; " ; " separator which is introduced by the tokenizer.
80 (header (header "include" atom))
81 (decls (id "=" pattern) (id "|=" pattern) (id "&=" pattern)
83 (pattern ("element" args) ("attribute" args)
84 ("list" args) ("mixed" args)
85 ("parent" id) ("external" id)
94 ;; The spec says "There is no notion of operator precedence".
96 '((assoc "," "&" "|") (nonassoc "?" "*" "+"))
99 (defun rnc-smie-forward-token ()
100 (let ((start (point)))
101 (forward-comment (point-max))
102 (if (and (> (point) start)
103 (looking-at "\\(?:\\s_\\|\\sw\\)+[ \t\n]*[|&]?=")
109 (smie-default-forward-token))))
111 (defun rnc-smie-backward-token ()
112 (let ((start (point)))
113 (forward-comment (- (point)))
114 (if (and (< (point) start)
118 (looking-at "\\(?:\\s_\\|\\sw\\)+[ \t\n]*[|&]?=")
121 (smie-default-backward-token))))
123 (defun rnc-smie-rules (kind token)
124 (pcase (cons kind token)
125 (`(:list-intro . "element") t)
126 (`(:before . ,(or "include" "default" "namespace" "datatypes")) 0)
129 (let ((offset (if (smie-rule-bolp) smie-indent-basic 0))
131 (while (or (null (car-safe x))
132 (integerp (car-safe x)))
133 (setq x (smie-backward-sexp 'halfsexp)))
134 (goto-char (nth 1 x))
135 `(column . ,(+ (smie-indent-virtual) offset)))))
136 (`(:after . ,(or "=" "|=" "&=")) smie-indent-basic)
137 (`(:before . ,(or "|" "&" ","))
138 (and (smie-rule-bolp) (smie-rule-parent-p "(" "{") (smie-rule-parent)))
139 (`(,_ . " ; ") (smie-rule-separator kind))
143 (define-derived-mode rnc-mode prog-mode "RNC"
144 "Major mode to edit Relax-NG Compact files."
145 (setq-local comment-start "#")
146 (setq-local font-lock-defaults '(rnc-font-lock-keywords))
147 (setq-local imenu-generic-expression rnc-imenu-generic-expression)
148 (smie-setup rnc-smie-grammar #'rnc-smie-rules
149 :forward-token #'rnc-smie-forward-token
150 :backward-token #'rnc-smie-backward-token))
153 ;;; rnc-mode.el ends here