]> code.delx.au - gnu-emacs-elpa/blob - packages/sm-c-mode/sm-c-mode.el
1ac263eab3912b2af12ef8fc47210d85c6504406
[gnu-emacs-elpa] / packages / sm-c-mode / sm-c-mode.el
1 ;;; sm-c-mode.el --- Experimental C major mode based on SMIE -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
4
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6 ;; Version: 0
7 ;; Keywords:
8
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
13
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 ;;; Commentary:
23
24 ;; ¡¡Don't use this!!
25 ;;
26 ;; This is an experiment to see concretely where&how SMIE falls down when
27 ;; trying to handle a language like C.
28 ;; So, strictly speaking, this does provide "SMIE-based indentation for C" and
29 ;; might even do it OK for simple cases, but it really doesn't benefit much
30 ;; from SMIE:
31 ;; - it does a lot of its own parsing by hand.
32 ;; - its smie-ruled-function also does a lot of indentation by hand.
33 ;; Hopefully at some point, someone will find a way to extend SMIE such that
34 ;; it can handle C without having to constantly work around SMIE, e.g.
35 ;; it'd be nice to hook the sm-c--while-to-do, sm-c--else-to-if, and sm-c--boi
36 ;; functions into SMIE at some level.
37
38 ;; Note that this mode makes no attempt to try and handle sanely K&R style
39 ;; function definitions.
40
41 ;;; Code:
42
43 (require 'cl-lib)
44 (require 'smie)
45
46 (defgroup sm-c-mode nil
47 "Major mode to edit C code, based on SMIE."
48 :group 'programming)
49
50 (defcustom sm-c-indent-basic 2
51 "Basic step of indentation.
52 Typically 2 for GNU style and `tab-width' for Linux style."
53 :type 'integer)
54
55 (defcustom sm-c-indent-braces t
56 "If non-nil, braces in if/while/... are indented."
57 :type 'boolean)
58
59 ;;; Handling CPP directives.
60
61 (defsubst sm-c--cpp-inside-p (ppss)
62 (eq 2 (nth 7 ppss)))
63
64 (eval-and-compile
65 (defconst sm-c--cpp-regexp "^[ \t]*\\(\\(#\\)[ \t]*\\([a-z]+\\)\\)"))
66
67 (defconst sm-c--cpp-syntax-table
68 (let ((st (make-syntax-table)))
69 (modify-syntax-entry ?/ ". 124" st)
70 (modify-syntax-entry ?* ". 23b" st)
71 (modify-syntax-entry ?\n ">" st)
72 st))
73
74 (defun sm-c--cpp-goto-end (ppss &optional limit)
75 (cl-assert (sm-c--cpp-inside-p ppss))
76 (let (found)
77 (while
78 (and (setq found (re-search-forward "\\(?:\\\\\\\\\\)*\n" limit 'move))
79 ;; We could also check (nth 5 ppss) to figure out if we're
80 ;; after a backslash, but this is a very common case, so it's good
81 ;; to avoid calling parse-partial-sexp for that.
82 (or (eq ?\\ (char-before (match-beginning 0)))
83 (with-syntax-table sm-c--cpp-syntax-table
84 (nth 4 (parse-partial-sexp (1+ (nth 8 ppss)) (point)))))))
85 found))
86
87 (defun sm-c--cpp-fontify-syntactically (ppss)
88 ;; FIXME: ¡¡BIG UGLY HACK!!
89 ;; Copied from font-lock.el's font-lock-fontify-syntactically-region.
90 (cl-assert (> (point) (nth 8 ppss)))
91 (save-excursion
92 (save-restriction
93 (sm-c--cpp-goto-end ppss)
94 (narrow-to-region (1+ (nth 8 ppss)) (point))
95 ;; FIXME: We should add some "with-local-syntax-ppss" macro to
96 ;; encapsulate this.
97 (let ((syntax-propertize-function nil)
98 (syntax-ppss-cache nil)
99 (syntax-ppss-last nil))
100 (font-lock-fontify-syntactically-region (point-min) (point-max))))))
101
102 (defun sm-c--cpp-syntax-propertize (end)
103 (let ((ppss (syntax-ppss))
104 found)
105 (when (sm-c--cpp-inside-p ppss)
106 (while
107 (and (setq found (re-search-forward "\\(\\\\\\\\\\)*\n" end 'move))
108 (or (eq ?\\ (char-before (match-beginning 0)))
109 (with-syntax-table sm-c--cpp-syntax-table
110 (nth 4 (parse-partial-sexp (1+ (nth 8 ppss)) (point)))))))
111 (when found
112 (put-text-property (1- (point)) (point)
113 'syntax-table (string-to-syntax "> c"))))))
114
115 ;;;; Indenting CPP directives.
116
117 (defcustom sm-c-indent-cpp-basic 1
118 "Indent step for CPP directives."
119 :type 'integer)
120
121 (defun sm-c--cpp-prev (tok)
122 (let ((offset nil))
123 (while
124 (when (re-search-backward sm-c--cpp-regexp nil t)
125 (pcase (cons tok (match-string 3))
126 (`(,_ . "endif") (sm-c--cpp-prev "endif"))
127 ((or `(,(or "endif" "else" "elif") . ,(or "if" "ifdef" "ifndef"))
128 `(,(or "else" "elif") . "elif"))
129 (setq offset 0))
130 (`(,(or "endif" "else" "elif") . ,_) nil)
131 (`(,_ . ,(or "if" "ifdef" "ifndef" "elif" "else"))
132 (setq offset sm-c-indent-cpp-basic))
133 (_ (setq offset 0)))
134 (not offset)))
135 (when offset
136 (goto-char (match-beginning 3))
137 (+ offset (current-column)))))
138
139
140 (defun sm-c--cpp-indent-line (&optional _arg)
141 ;; FIXME: Also align the terminating \, if any.
142 (when (> sm-c-indent-cpp-basic 0)
143 (let* ((pos (point-marker))
144 (beg)
145 (indent
146 (save-excursion
147 (forward-line 0)
148 (when (looking-at sm-c--cpp-regexp)
149 (setq beg (match-beginning 3))
150 (or (sm-c--cpp-prev (match-string 3)) 0)))))
151 (when indent
152 (let ((before (<= pos beg)))
153 (goto-char beg)
154 (unless (= (current-column) indent)
155 (skip-chars-backward " \t")
156 (delete-region (point)
157 (progn (skip-chars-forward " \t") (point)))
158 (indent-to indent))
159 (unless before (goto-char pos)))))))
160
161 ;;;; Indenting inside CPP #define.
162
163 (defconst sm-c--cpp-smie-indent-functions
164 ;; FIXME: Don't just align line after #define with the "d"!
165 (mapcar
166 (lambda (f)
167 (cond
168 ((eq f #'smie-indent-comment-inside) #'sm-c--cpp-indent-comment-inside)
169 ;; ((eq f #'smie-indent-exps) #'sm-c--cpp-indent-exps)
170 (t f)))
171 (default-value 'smie-indent-functions)))
172
173 (defun sm-c--cpp-indent-comment-inside ()
174 (let ((ppss (syntax-ppss)))
175 (when (nth 4 ppss)
176 ;; Indicate where's the comment start.
177 `(noindent . ,(nth 8 ppss)))))
178
179 (defun sm-c--cpp-smie-indent ()
180 (let ((ppss (syntax-ppss)))
181 (cond
182 ((sm-c--cpp-inside-p ppss)
183 (save-restriction
184 (narrow-to-region (nth 8 ppss) (point-max))
185 (let ((indent
186 (let ((smie-indent-functions sm-c--cpp-smie-indent-functions)
187 (syntax-ppss-cache nil)
188 (syntax-ppss-last nil)
189 (parse-sexp-lookup-properties nil))
190 (smie-indent-calculate))))
191 (if (not (eq 'noindent (car-safe indent)))
192 (if (integerp indent)
193 (max (funcall smie-rules-function :elem 'basic) indent)
194 indent)
195 ;; We can't just return `noindent' if we're inside a comment,
196 ;; because the indent.el code would then be similarly confused,
197 ;; thinking the `noindent' is because we're inside the cpp
198 ;; pseudo-comment, and would hence align the code with the content
199 ;; of the psuedo-comment rather than the nested real comment!
200 ;;
201 ;; FIXME: Copy&paste from indent--default-inside-comment.
202 ;; FIXME: This will always re-indent inside these comments, even
203 ;; during indent-region.
204 (save-excursion
205 (forward-line -1)
206 (skip-chars-forward " \t")
207 (when (< (1- (point)) (cdr indent) (line-end-position))
208 (goto-char (cdr indent))
209 (when (looking-at comment-start-skip)
210 (goto-char (match-end 0))))
211 (current-column))))))
212
213 ((equal (syntax-after (point)) (string-to-syntax "< c")) 0)
214 )))
215
216 ;;; Syntax table
217
218 (defvar sm-c-mode-syntax-table
219 (let ((st (make-syntax-table)))
220 (modify-syntax-entry ?/ ". 124" st)
221 (modify-syntax-entry ?* ". 23b" st)
222 (modify-syntax-entry ?\n ">" st)
223 (modify-syntax-entry ?\" "\"" st)
224 (modify-syntax-entry ?\' "\"" st)
225 (modify-syntax-entry ?= "." st)
226 (modify-syntax-entry ?< "." st)
227 (modify-syntax-entry ?> "." st)
228 st))
229
230 (defun sm-c-syntax-propertize (start end)
231 (goto-char start)
232 (sm-c--cpp-syntax-propertize end)
233 (funcall
234 (syntax-propertize-rules
235 (sm-c--cpp-regexp
236 (2 (prog1 "< c"
237 (when (and (equal (match-string 3) "include")
238 (looking-at "[ \t]*\\(<\\)[^>\n]*\\(>\\)"))
239 (put-text-property (match-beginning 1) (match-end 1)
240 'syntax-table (string-to-syntax "|"))
241 (put-text-property (match-beginning 2) (match-end 2)
242 'syntax-table (string-to-syntax "|")))
243 (sm-c--cpp-syntax-propertize end)))))
244 (point) end))
245
246 (defun sm-c-syntactic-face-function (ppss)
247 (if (sm-c--cpp-inside-p ppss)
248 (prog1 nil (sm-c--cpp-fontify-syntactically ppss))
249 (funcall (default-value 'font-lock-syntactic-face-function) ppss)))
250
251 ;;; SMIE support
252
253 (defconst sm-c-paren-block-keywords '("if" "while" "for" "switch"))
254
255 (defconst sm-c-smie-precedence-table
256 '((assoc ";")
257 ;; Compiled from https://en.wikipedia.org/wiki/Operators_in_C_and_C++.
258 (assoc ",") ;1
259 ;; (nonassoc "throw")
260 (nonassoc "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|=") ;2
261 ;; (nonassoc "?" ":") ;; Better handle it in the BNF.
262 (assoc "||") ;3
263 (assoc "&&") ;4
264 (assoc "|") ;5
265 (assoc "^") ;6
266 ;; (assoc "&") ;; Binary and. Confused with address-of.
267 (nonassoc "==" "!=") ;7
268 (nonassoc "<" "<=" ">" ">=") ;8
269 (nonassoc "<<" ">>") ;9
270 (assoc "+" "-") ;10
271 (assoc "/" "* mult" "%") ;11
272 ;; (nonassoc ".*" "->*") ;12 ;; Only C++
273 ;; (nonassoc "++" "--" "+" "-" "!" "~" "(type)" "*" "&"
274 ;; "sizeof" "new" "delete");13 ;; All prefix.
275 (left "." "->") ;; "++" "--" suffixes, "()", "[]", "typeid", "*_cast". ;14
276 ;; (noassoc "::") ;; Only C++
277 ))
278
279 (defconst sm-c-smie-grammar
280 ;; `((:smie-closer-alist ("{" . "}")) ("{" (39) 0) ("}" 0 (40)) ("else" 27 26) ("," 38 38) ("do" (41) 22) ("while" (42) 23) ("for" (43) 24) (";" 11 11) ("if" (44) 25))
281 (let ((grm
282 (smie-prec2->grammar
283 (smie-merge-prec2s
284 (smie-bnf->prec2
285 '((decls ("typedef" decl) ("extern" decl)
286 (decls ";" decls))
287 (decl)
288 (id)
289 (insts ("{" insts "}")
290 (insts ";" insts)
291 ("return" exp)
292 ("goto" exp)
293 (":label")
294 ("case" subexp ": case")
295 ("else" exp-if))
296 (exp-if ("if" exp) ("do" exp) ("while" exp) ("switch" exp) ("for" exp)
297 (exp))
298 (exp ("(" exp ")") (exp "," exp) (subexp "?" exp ":" exp))
299 (subexp (subexp "||" subexp))
300 ;; Some of the precedence table deals with pre/postfixes, which
301 ;; smie-precs->prec2 can't handle, so handle it here instead.
302 (exp11 (exp12) (exp11 "/" exp11))
303 (exp12 (exp13)) ;C++ only.
304 (exp13 (exp14) ("++ prefix" exp13) ("-- prefix" exp13)
305 ("!" exp13) ("~" exp13) ("&" exp13) ("* deref" exp13))
306 (exp14 (id) (exp14 "++ postfix") (exp14 "-- postfix")
307 (exp14 "->" id) (exp14 "." id)))
308 '((assoc ";") (assoc ",") (nonassoc "?" ":"))
309 sm-c-smie-precedence-table)
310 (smie-precs->prec2 sm-c-smie-precedence-table)
311 (smie-precs->prec2 '((nonassoc ";") (nonassoc ":")))))))
312 ;; SMIE gives (":label" 261 262), but really this could just as well be
313 ;; (":label" nil nil) because labels don't have any argument to their left
314 ;; or right. They're like both openers and closers at the same time.
315 (mapcar (lambda (x)
316 (if (equal (car-safe x) ":label")
317 ;; Rather than (":label" (n1) (n2)) we use
318 ;; (":label" (n1) n2) because SMIE otherwise complains:
319 ;; cl--assertion-failed((numberp (funcall op-forw toklevels)))
320 ;; in smie-next-sexp.
321 `(,(nth 0 x) (,(nth 1 x)) ,(nth 2 x)) x))
322 grm)))
323
324 ;; (defun sm-c--:-discriminate ()
325 ;; (save-excursion
326 ;; (and (null (smie-backward-sexp))
327 ;; (let ((prev (smie-indent-backward-token)))
328 ;; (cond
329 ;; ((equal prev "case" ) ": case")
330 ;; ((member prev '(";" "{" "}")) ":-label")
331 ;; (t ":"))))))
332
333 (defconst sm-c-smie-operator-regexp
334 (let ((ops '()))
335 (pcase-dolist (`(,token . ,_) sm-c-smie-grammar)
336 (when (and (stringp token) (string-match "\\`[^ [:alnum:](){}]+" token))
337 (push (match-string 0 token) ops)))
338 (regexp-opt ops)))
339
340 (defun sm-c-smie-forward-token ()
341 (forward-comment (point-max))
342 (let ((tok (if (looking-at sm-c-smie-operator-regexp)
343 (progn (goto-char (match-end 0)) (match-string 0))
344 (smie-default-forward-token))))
345 (cond
346 ((and (equal tok "") (looking-at "\\\\\n"))
347 (goto-char (match-end 0))
348 (sm-c-smie-forward-token))
349 ((member tok '(":" "*"))
350 (save-excursion (sm-c-smie-backward-token)))
351 ((looking-at "[ \t]*:")
352 (if (not (equal (save-excursion (sm-c-smie-forward-token)) ":label"))
353 tok
354 (looking-at "[ \t]*:")
355 (goto-char (match-end 0)) ":label"))
356 (t tok))))
357
358
359 (defun sm-c-smie-backward-token ()
360 (forward-comment (- (point)))
361 (let ((tok (if (looking-back sm-c-smie-operator-regexp (- (point) 3) t)
362 (progn (goto-char (match-beginning 0)) (match-string 0))
363 (smie-default-backward-token))))
364 (cond
365 ((and (equal tok "") (looking-at "\n"))
366 (let ((pos (point)))
367 (if (not (= 0 (mod (skip-chars-backward "\\\\") 2)))
368 (sm-c-smie-backward-token)
369 (goto-char pos)
370 tok)))
371 ((equal tok "*") (sm-c-smie--*-token))
372 ((equal tok ":")
373 (let ((pos1 (point))
374 (prev (sm-c-smie-backward-token)))
375 (if (zerop (length prev))
376 (progn (goto-char pos1) tok)
377 (let ((pos2 (point)))
378 (pcase (car (smie-indent-backward-token))
379 ("case" (goto-char pos1) ": case")
380 ((or ";" "{" "}") (goto-char pos2) ":label")
381 (_ (goto-char pos1) tok))))))
382 (t tok))))
383
384 (defun sm-c--prev-token ()
385 (car (smie-indent-backward-token)))
386
387 (defun sm-c--else-to-if ()
388 (let ((pos (point)))
389 (unless (equal (sm-c--prev-token) ";")
390 (goto-char pos))
391 (while
392 (pcase (smie-backward-sexp)
393 (`(,_ ,pos "if") (goto-char pos) nil) ;Found it!
394 (`(,_ ,_ ";") nil) ;Can't find it!
395 (`(,_ ,pos "else") (goto-char pos) (sm-c--else-to-if) t)
396 (`(,_ ,pos "while")
397 (goto-char pos) (unless (sm-c--while-to-do) (goto-char pos)) t)
398 (`(t . ,_) nil) ;Can't find it!
399 (`(,_ ,pos . ,_) (goto-char pos) t)
400 (`nil t)))))
401
402 (defun sm-c--while-to-do ()
403 "Jump to the matching `do' and return non-nil, if any. Return nil otherwise."
404 (pcase (sm-c--prev-token)
405 ("}"
406 ;; The easy case!
407 (forward-char 1) (backward-sexp 1)
408 (equal (sm-c--prev-token) "do"))
409 (";"
410 (let ((found-do nil))
411 (while
412 (pcase (smie-backward-sexp)
413 (`(,_ ,pos "do") (goto-char pos) (setq found-do t) nil)
414 (`(,_ ,_ ";") nil) ;Can't find it!
415 (`(,_ ,pos "else") (goto-char pos) (sm-c--else-to-if) t)
416 (`(,_ ,pos "while")
417 (goto-char pos) (unless (sm-c--while-to-do) (goto-char pos)) t)
418 (`(t . ,_) nil) ;Can't find it!
419 (`(,_ ,pos . ,_) (goto-char pos) t)
420 (`nil (or (not (looking-at "{"))
421 (smie-rule-prev-p "=")))))
422 found-do))))
423
424 (defun sm-c--skip-labels (max)
425 (while
426 (let ((start (point)))
427 (pcase (sm-c-smie-forward-token)
428 ("case"
429 (smie-forward-sexp "case")
430 (forward-comment (point-max))
431 (if (>= (point) max) (progn (goto-char start) nil)
432 t))
433 (":label"
434 (forward-comment (point-max))
435 (if (>= (point) max) (progn (goto-char start) nil)
436 t))
437 (_ (goto-char start) nil)))))
438
439 (defun sm-c--boi (&optional inner)
440 "Jump to the beginning-of-instruction.
441 By default for things like nested ifs, it jumps to the outer if, but
442 if INNER is non-nil, it stops at the innermost one."
443 (while
444 (let ((pos (point)))
445 (pcase (smie-backward-sexp)
446 (`(,_ ,_ ";") nil) ;Found it!
447 (`(,_ ,pos "else") (goto-char pos) (sm-c--else-to-if) t)
448 (`(,_ ,pos "while")
449 (goto-char pos) (unless (sm-c--while-to-do) (goto-char pos)) t)
450 (`(,(pred numberp) ,pos . ,_) (goto-char pos) t)
451 ((or `nil `(nil . ,_))
452 (if (and (or (not (looking-at "{"))
453 (smie-rule-prev-p "="))
454 (not (bobp)))
455 t
456 (goto-char pos) nil))
457 (`(,_ ,_ ,(or "(" "{" "[")) nil) ;Found it!
458 (`(,_ ,pos ,(and tok
459 (guard (when inner
460 (or (member tok sm-c-paren-block-keywords)
461 (equal tok "do"))))))
462 (goto-char pos) nil) ;Found it!
463 (`(t ,(pred (eq (point-min))) . ,_) nil)
464 (`(,_ ,pos . ,_) (goto-char pos) t)))))
465
466 ;; (defun sm-c--if-tail-to-head ()
467 ;; (pcase (sm-c--prev-token)
468 ;; (")"
469 ;; (forward-char 1) (backward-sexp 1)
470 ;; (pcase (sm-c--prev-token)
471 ;; ("if" nil)
472 ;; ((or "while" "for") (sm-c--if-tail-to-head))))
473 ;; ("do" (sm-c--if-tail-to-head))))
474
475 (defun sm-c--boe (tok)
476 (let ((start (point))
477 (res (smie-backward-sexp tok)))
478 (when (member (nth 2 res) '("if" "while" "do" "for" "else"))
479 (when (member (nth 2 res) '("if" "for"))
480 (let ((forward-sexp-function nil))
481 (forward-sexp 1))
482 (forward-comment (point-max)))
483 (when (looking-at "{")
484 (let ((forward-sexp-function nil))
485 (forward-sexp 1))
486 (forward-comment (point-max)))
487 (if (> (point) start) (goto-char start)))))
488
489 (defun sm-c-smie--*-token ()
490 (save-excursion
491 (let ((pos (point)))
492 (pcase (car (smie-indent-backward-token))
493 (")"
494 ;; Can be a multiplication (as in "(a+b)*c"), or a deref
495 ;; (as in "if (stop) *a = 0;")
496 (if (and (goto-char (nth 1 (syntax-ppss)))
497 (eq ?\( (char-after))
498 (member (smie-default-backward-token) '("if" "for")))
499 "* deref"
500 "* mult"))
501 ("]" "* mult") ;Multiplication.
502 ((or "(" "[" "{" "}") "* deref")
503 (`nil
504 (goto-char pos)
505 (let ((res nil))
506 (while (not res)
507 (pcase (smie-backward-sexp)
508 (`(,_ ,_ ,(or ";" "{")) (setq res "* deref"))
509 ((and `nil (guard (looking-at "{"))) (setq res "* deref"))
510 (`(,left ,_ ,op)
511 (if (and (numberp left)
512 (numberp (nth 2 (assoc op smie-grammar)))
513 (< (nth 2 (assoc op smie-grammar))
514 (nth 1 (assoc "* mult" smie-grammar))))
515 (smie-backward-sexp 'halfsexp)
516 (setq res "* mult")))))
517 res))
518 (_ "* mult")))))
519
520 (defun sm-c-smie-hanging-eolp ()
521 (let ((start (point))
522 (prev (smie-indent-backward-token)))
523 (if (and (not (numberp (nth 1 prev)))
524 (save-excursion (equal (sm-c-smie-backward-token) ";")))
525 ;; Treat instructions that start after ";" as always "hanging".
526 (end-of-line)
527 (goto-char start)))
528 (skip-chars-forward " \t")
529 (or (eolp)
530 (forward-comment (point-max))
531 (and (looking-at "\\\\\n")
532 (goto-char (match-end 0)))))
533
534 (defvar sm-c-smie--inhibit-case/label-rule nil)
535
536 (defun sm-c--smie-virtual ()
537 (if (and (smie-indent--bolp)
538 (not (save-excursion
539 (member (sm-c-smie-forward-token)
540 '("case" ":label")))))
541 (current-column)
542 (let ((sm-c-smie--inhibit-case/label-rule t))
543 (smie-indent-calculate))))
544
545 (defun sm-c-smie-rules (kind token)
546 (pcase (cons kind token)
547 (`(:elem . basic) sm-c-indent-basic)
548 (`(:list-intro . ";")
549 (save-excursion
550 (forward-char 1)
551 (if (and (null (smie-forward-sexp))
552 ;; FIXME: Handle \\\n as well!
553 (progn (forward-comment (point-max))
554 (looking-at "(")))
555 nil
556 t)))
557 (`(:before . "else")
558 (save-excursion
559 (sm-c--else-to-if)
560 `(column . ,(smie-indent-virtual))))
561 (`(:before . "while")
562 (save-excursion
563 (when (sm-c--while-to-do)
564 `(column . ,(smie-indent-virtual)))))
565 (`(:before . ,(or "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|="))
566 (save-excursion
567 (sm-c--boe token)
568 `(column . ,(+ (funcall smie-rules-function :elem 'basic)
569 (smie-indent-virtual)))))
570 (`(:before . "if")
571 (when (and (not (smie-rule-bolp)) (smie-rule-prev-p "else"))
572 (save-excursion
573 (smie-indent-backward-token)
574 `(column . ,(sm-c--smie-virtual)))))
575 ;; (`(:after . ,(or "=" "+=" "-=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|="))
576 ;; (funcall smie-rules-function :elem 'basic))
577 (`(:before . "{")
578 (cond
579 ((smie-rule-prev-p "=") nil) ;Not a block of instructions!
580 ((save-excursion
581 (let ((pos (point)))
582 (sm-c--boi 'inner) (sm-c--skip-labels (point-max))
583 (let ((tok (save-excursion (sm-c-smie-forward-token))))
584 (cond
585 ((or (equal tok "typedef")
586 (and (member tok '("enum" "struct"))
587 ;; Make sure that the {...} is about this struct/enum,
588 ;; as opposed to "struct foo *get_foo () {...}"!
589 (save-excursion
590 (smie-indent-forward-token)
591 (smie-indent-forward-token)
592 (forward-comment (point-max))
593 (>= (point) pos))))
594 `(column . ,(+ (if (save-excursion
595 (goto-char pos)
596 (smie-rule-hanging-p))
597 0
598 (funcall smie-rules-function :elem 'basic))
599 (smie-indent-virtual))))
600 ((or (member tok sm-c-paren-block-keywords)
601 (equal tok "do"))
602 nil)
603 ((save-excursion
604 (goto-char pos)
605 (when (and (> (car (syntax-ppss)) 0)
606 (equal ")" (car (smie-indent-backward-token))))
607 (up-list -1)
608 `(column . ,(sm-c--smie-virtual)))))
609 (t `(column . ,(smie-indent-virtual))))))))
610 ((smie-rule-hanging-p)
611 (cond
612 ((smie-rule-prev-p "do" "else")
613 (smie-indent-backward-token))
614 ((smie-rule-prev-p ")")
615 (smie-backward-sexp)
616 (smie-indent-backward-token))
617 (t (sm-c--boi 'inner)))
618 `(column . ,(sm-c--smie-virtual)))
619 (t
620 (let ((pos (point)))
621 (pcase (sm-c--prev-token)
622 ((or "do" "else")
623 (cond
624 (sm-c-indent-braces
625 `(column . ,(+ (funcall smie-rules-function :elem 'basic)
626 (smie-indent-virtual))))))
627 (")" nil)
628 (_ (goto-char pos) (sm-c--boi)
629 (if (< (point) pos)
630 `(column . ,(sm-c--smie-virtual)))))))))
631 (`(:before . "(")
632 (save-excursion
633 (let ((res (smie-backward-sexp)))
634 (pcase res
635 (`nil
636 (if (looking-at "(")
637 ;; (unless (save-excursion
638 ;; (member (sm-c-smie-backward-token)
639 ;; sm-c-paren-block-keywords))
640 ;; `(column . ,(sm-c--smie-virtual)))
641 nil
642 `(column . ,(+ (funcall smie-rules-function :elem 'basic)
643 (sm-c--smie-virtual)))))))))
644 (`(:after . "else")
645 (save-excursion
646 (funcall smie-rules-function :elem 'basic)))
647 (`(:after . ")")
648 (save-excursion
649 (let ((_ (progn (forward-char 1) (backward-sexp 1)))
650 (pos (point))
651 (prev (sm-c-smie-backward-token)))
652 (cond
653 ((member prev sm-c-paren-block-keywords)
654 `(column . ,(+ (funcall smie-rules-function :elem 'basic)
655 (smie-indent-virtual))))
656 ((and (looking-at "[[:alnum:]_]+(")
657 (save-excursion
658 (forward-line 0)
659 (and (bobp) (looking-at sm-c--cpp-regexp))))
660 ;; Will be bumped up presumably by the "max" in
661 ;; sm-c--cpp-smie-indent.
662 `(column . 0))
663 (t (goto-char pos) `(column . ,(sm-c--smie-virtual)))))))
664 (`(:after . "}")
665 (save-excursion
666 (forward-char 1) (backward-sexp 1)
667 (sm-c--boi)
668 `(column . ,(sm-c--smie-virtual))))
669 (`(:after . ";")
670 (save-excursion
671 (sm-c--boi)
672 `(column . ,(sm-c--smie-virtual))))
673 (`(:after . ":label")
674 ;; Yuck!
675 `(column . ,(sm-c--smie-virtual)))
676 (`(:after . ": case")
677 ;; Yuck!
678 (save-excursion
679 (smie-backward-sexp ": case")
680 `(column . ,(sm-c--smie-virtual))))
681 (`(:after . "* deref") `(column . ,(sm-c--smie-virtual)))
682 ((and `(:before . ":label") (guard (not sm-c-smie--inhibit-case/label-rule)))
683 (let ((ppss (syntax-ppss)))
684 (when (nth 1 ppss)
685 (save-excursion
686 (goto-char (nth 1 ppss))
687 `(column . ,(smie-indent-virtual))))))
688 ((and `(:before . "case") (guard (not sm-c-smie--inhibit-case/label-rule)))
689 (catch 'found
690 (dolist (pos (reverse (nth 9 (syntax-ppss))))
691 (save-excursion
692 (goto-char pos)
693 (and (looking-at "{")
694 (null (car-safe (smie-backward-sexp)))
695 (equal "switch" (sm-c-smie-backward-token))
696 (goto-char pos)
697 (throw 'found `(column . ,(smie-indent-virtual))))))))))
698
699 ;;; Backslash alignment
700
701 (defvar-local sm-c--bs-changed nil)
702
703 (defun sm-c--bs-after-change (beg end _len)
704 (unless undo-in-progress
705 (if (null sm-c--bs-changed)
706 (setq sm-c--bs-changed (cons beg end))
707 (cl-callf (lambda (x) (min x beg)) (car sm-c--bs-changed))
708 (cl-callf (lambda (x) (max x end)) (cdr sm-c--bs-changed)))))
709
710 (defun sm-c--bs-realign ()
711 (when sm-c--bs-changed
712 (sm-c--bs-realign-1 (car sm-c--bs-changed) (cdr sm-c--bs-changed))
713 (setq sm-c--bs-changed nil)))
714
715 (defun sm-c--bs-realign-1 (from to)
716 (save-excursion
717 (goto-char from)
718 (end-of-line)
719 (unless (zerop (mod (skip-chars-backward "\\\\") 2))
720 (skip-chars-backward " \t")
721 (setq from (point))
722 (let ((col (current-column))
723 start end)
724 (while
725 (progn (setq start (point))
726 (end-of-line 0)
727 (and (< (point) start)
728 (not (zerop (mod (skip-chars-backward "\\\\") 2)))))
729 (skip-chars-backward " \t")
730 (setq col (max (current-column) col)))
731 (goto-char from)
732 (while
733 (progn (setq end (point))
734 (end-of-line 2)
735 (and (> (line-beginning-position) end)
736 (not (zerop (mod (skip-chars-backward "\\\\") 2)))))
737 (skip-chars-backward " \t")
738 (setq col (max (current-column) col)))
739 (goto-char to)
740 (beginning-of-line)
741 (unless (or (> (point) end) ;Don't realign if we changed outside!
742 (<= end start)) ;A lone \
743
744 (setq col (1+ col)) ;Add a space before the backslashes.
745 (goto-char end)
746 (end-of-line)
747 (while (>= (point) start)
748 (cl-assert (eq (char-before) ?\\))
749 (forward-char -1)
750 (let ((curcol (current-column)))
751 (cond
752 ((> col curcol) (indent-to col))
753 ((< col curcol)
754 (move-to-column col t)
755 (delete-region (point)
756 (progn (skip-chars-forward " \t") (point))))))
757 (end-of-line 0)))))))
758
759 ;;; Font-lock support
760
761 (defconst sm-c-font-lock-keywords
762 `((,sm-c--cpp-regexp (1 font-lock-preprocessor-face))
763 ("\\_<\\(?:true\\|false\\)\\_>" (0 font-lock-constant-face))
764 ("\\_<\\(case\\)\\_>[ \t]*\\([^: \t]+\\)"
765 (1 font-lock-keyword-face)
766 (2 font-lock-constant-face))
767 ("\\(?:[{};]\\(\\)\\|^\\)[ \t]*\\([[:alpha:]_][[:alnum:]_]*\\)[ \t]*:"
768 (2 (if (or (match-beginning 1)
769 (save-excursion (equal ":label" (sm-c-smie-backward-token))))
770 font-lock-constant-face)))
771 (,(let ((kws (delq nil (mapcar (lambda (x)
772 (setq x (car x))
773 (and (stringp x)
774 (string-match "\\`[a-z]" x)
775 x))
776 sm-c-smie-grammar))))
777 (concat "\\_<" (regexp-opt
778 (append
779 ;; Elements not in SMIE's grammar. Either because
780 ;; they're uninteresting from a parsing point of view,
781 ;; or because SMIE's parsing engine can't handle them
782 ;; even poorly.
783 '("break" "continue" "struct" "enum" "union" "static")
784 ;; "case" already handled above.
785 (delete "case" kws)))
786 "\\_>"))
787 (0 font-lock-keyword-face))
788 (,(let* ((spc0 "\\(?:\n?[ \t]\\|/\\*.*?\\*/\\)*")
789 (spc1 (concat "\n?[ \t]" spc0))
790 (id "\\(?:\\sw\\|\\s_\\)+"))
791 (cl-flet ((repeat (repetition &rest res)
792 (concat "\\(?:" (apply #'concat res) "\\)"
793 (pcase repetition
794 ((pred symbolp) (symbol-name repetition))
795 (1 "")))))
796 (concat
797 "^"
798 (repeat '* "\\*" spc0)
799 (repeat '* id (repeat 1 spc1 "\\|" spc0 "\\*" spc0))
800 "\\(" id "\\)[ \t\n]*(")))
801 (1
802 (prog1 font-lock-function-name-face
803 (if (< (match-beginning 0) (line-beginning-position))
804 (put-text-property (match-beginning 0) (match-end 0)
805 'font-lock-multiline t)))))))
806
807
808 ;;;###autoload
809 (define-derived-mode sm-c-mode prog-mode "smC"
810 "C editing mode based on SMIE."
811 ;; (setq-local font-lock-support-mode nil) ;; To help debugging.
812 (setq-local comment-start "/* ")
813 (setq-local comment-end " */")
814 (setq-local parse-sexp-lookup-properties t)
815 (setq-local open-paren-in-column-0-is-defun-start nil)
816 (setq-local syntax-propertize-function #'sm-c-syntax-propertize)
817 (setq-local font-lock-defaults '(sm-c-font-lock-keywords))
818 (setq-local font-lock-syntactic-face-function #'sm-c-syntactic-face-function)
819 (smie-setup sm-c-smie-grammar #'sm-c-smie-rules
820 :backward-token #'sm-c-smie-backward-token
821 :forward-token #'sm-c-smie-forward-token)
822 ;; FIXME: The stock SMIE forward-sexp-function is not good enough here, since
823 ;; our grammar is much too poor. We should setup another function instead
824 ;; (or ideally teach SMIE to use it).
825 (kill-local-variable 'forward-sexp-function)
826 (add-hook 'smie-indent-functions #'sm-c--cpp-smie-indent nil t)
827 (add-function :after (local 'indent-line-function)
828 #'sm-c--cpp-indent-line)
829 (setq-local smie--hanging-eolp-function #'sm-c-smie-hanging-eolp)
830 ;; Backslash auto-realign.
831 (add-hook 'after-change-functions #'sm-c--bs-after-change nil t)
832 (add-hook 'post-command-hook #'sm-c--bs-realign nil t))
833
834 (defun sm-c--cpp-is-not-really-a-comment (&rest args)
835 ;; Without this, placing the region around a CPP directive and hitting
836 ;; M-; would just strip the leading "#" instead of commenting things out.
837 (if (not (derived-mode-p 'sm-c-mode))
838 (apply args)
839 (let ((parse-sexp-lookup-properties nil))
840 (apply args))))
841
842 ;; FIXME: Clearly, we should change newcomment.el instead.
843 (advice-add 'comment-only-p :around #'sm-c--cpp-is-not-really-a-comment)
844
845 (provide 'sm-c-mode)
846 ;;; sm-c-mode.el ends here