]> code.delx.au - gnu-emacs-elpa/blob - packages/rnc-mode/rnc-mode.el
Merge commit '0cda39255827f283e7578cd469ae42daad9556a2' from js2-mode
[gnu-emacs-elpa] / packages / rnc-mode / rnc-mode.el
1 ;;; rnc-mode.el --- Emacs mode to edit Relax-NG Compact files -*- lexical-binding:t -*-
2
3 ;; Copyright (C) 1994-1998, 2001-2016 Free Software Foundation, Inc.
4
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6 ;; Keywords: xml relaxng
7 ;; Version: 0.1
8
9 ;; This file is part of GNU Emacs.
10
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.
15
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.
20
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/>.
23
24
25 ;;; Commentary:
26
27 (require 'smie)
28 (require 'nxml-mode)
29
30 ;;; Code:
31
32 ;;;###autoload
33 (add-to-list 'auto-mode-alist '("\\.rnc\\'" . rnc-mode))
34
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)
44 st))
45
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"))
53
54 (defconst rnc--def-regexp "^[ \t]*\\([\\[:alpha:]][[:alnum:]-._]*\\)[ \t]*=")
55
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))
65 ))
66
67 (defconst rnc-imenu-generic-expression `((nil ,rnc--def-regexp 1)))
68
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.
77 (smie-prec2->grammar
78 (smie-bnf->prec2
79 '((id) (atom) (args)
80 (header (header "include" atom))
81 (decls (id "=" pattern) (id "|=" pattern) (id "&=" pattern)
82 (decls " ; " decls))
83 (pattern ("element" args) ("attribute" args)
84 ("list" args) ("mixed" args)
85 ("parent" id) ("external" id)
86 ("grammar" atom)
87 ("{" pattern "}")
88 (pattern "," pattern)
89 (pattern "&" pattern)
90 (pattern "|" pattern)
91 (pattern "?")
92 (pattern "*")
93 (pattern "+")))
94 ;; The spec says "There is no notion of operator precedence".
95 '((assoc " ; "))
96 '((assoc "," "&" "|") (nonassoc "?" "*" "+"))
97 )))
98
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]*[|&]?=")
104 (save-excursion
105 (goto-char start)
106 (forward-comment -1)
107 (= (point) start)))
108 " ; "
109 (smie-default-forward-token))))
110
111 (defun rnc-smie-backward-token ()
112 (let ((start (point)))
113 (forward-comment (- (point)))
114 (if (and (< (point) start)
115 (let ((pos (point)))
116 (goto-char start)
117 (prog1
118 (looking-at "\\(?:\\s_\\|\\sw\\)+[ \t\n]*[|&]?=")
119 (goto-char pos))))
120 " ; "
121 (smie-default-backward-token))))
122
123 (defun rnc-smie-rules (kind token)
124 (pcase (cons kind token)
125 (`(:list-intro . "element") t)
126 (`(:elem . empty-line-token) " ; ")
127 (`(:before . ,(or "include" "default" "namespace" "datatypes")) 0)
128 (`(:before . "{")
129 (save-excursion
130 (let ((offset (if (smie-rule-bolp) smie-indent-basic 0))
131 x)
132 (while (or (null (car-safe x))
133 (integerp (car-safe x)))
134 (setq x (smie-backward-sexp 'halfsexp)))
135 (goto-char (nth 1 x))
136 `(column . ,(+ (smie-indent-virtual) offset)))))
137 (`(:after . ,(or "=" "|=" "&=")) smie-indent-basic)
138 (`(:before . ,(or "|" "&" ","))
139 (and (smie-rule-bolp) (smie-rule-parent-p "(" "{") (smie-rule-parent)))
140 (`(,_ . " ; ") (smie-rule-separator kind))
141 ))
142
143 ;;;###autoload
144 (define-derived-mode rnc-mode prog-mode "RNC"
145 "Major mode to edit Relax-NG Compact files."
146 (setq-local comment-start "#")
147 (setq-local font-lock-defaults '(rnc-font-lock-keywords))
148 (setq-local imenu-generic-expression rnc-imenu-generic-expression)
149 (smie-setup rnc-smie-grammar #'rnc-smie-rules
150 :forward-token #'rnc-smie-forward-token
151 :backward-token #'rnc-smie-backward-token))
152
153 (provide 'rnc-mode)
154 ;;; rnc-mode.el ends here