Commit | Line | Data |
---|---|---|
5ad4bef5 SM |
1 | ;;; smie.el --- Simple Minded Indentation Engine |
2 | ||
3 | ;; Copyright (C) 2010 Free Software Foundation, Inc. | |
4 | ||
5 | ;; Author: Stefan Monnier <monnier@iro.umontreal.ca> | |
6 | ;; Keywords: languages, lisp, internal, parsing, indentation | |
7 | ||
8 | ;; This file is part of GNU Emacs. | |
9 | ||
10 | ;; GNU Emacs is free software; you can redistribute it and/or modify | |
11 | ;; it under the terms of the GNU General Public License as published by | |
12 | ;; the Free Software Foundation, either version 3 of the License, or | |
13 | ;; (at your option) any later version. | |
14 | ||
15 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | ;; GNU General Public License for more details. | |
19 | ||
20 | ;; You should have received a copy of the GNU General Public License | |
21 | ;; along with this program. If not, see <http://www.gnu.org/licenses/>. | |
22 | ||
23 | ;;; Commentary: | |
24 | ||
25 | ;; While working on the SML indentation code, the idea grew that maybe | |
26 | ;; I could write something generic to do the same thing, and at the | |
27 | ;; end of working on the SML code, I had a pretty good idea of what it | |
28 | ;; could look like. That idea grew stronger after working on | |
29 | ;; LaTeX indentation. | |
30 | ;; | |
31 | ;; So at some point I decided to try it out, by writing a new | |
32 | ;; indentation code for Coq while trying to keep most of the code | |
33 | ;; "table driven", where only the tables are Coq-specific. The result | |
34 | ;; (which was used for Beluga-mode as well) turned out to be based on | |
35 | ;; something pretty close to an operator precedence parser. | |
36 | ||
37 | ;; So here is another rewrite, this time following the actual principles of | |
38 | ;; operator precedence grammars. Why OPG? Even though they're among the | |
39 | ;; weakest kinds of parsers, these parsers have some very desirable properties | |
40 | ;; for Emacs: | |
41 | ;; - most importantly for indentation, they work equally well in either | |
42 | ;; direction, so you can use them to parse backward from the indentation | |
43 | ;; point to learn the syntactic context; | |
44 | ;; - they work locally, so there's no need to keep a cache of | |
45 | ;; the parser's state; | |
46 | ;; - because of that locality, indentation also works just fine when earlier | |
47 | ;; parts of the buffer are syntactically incorrect since the indentation | |
e7d67e73 | 48 | ;; looks at "as little as possible" of the buffer to make an indentation |
5ad4bef5 SM |
49 | ;; decision. |
50 | ;; - they typically have no error handling and can't even detect a parsing | |
51 | ;; error, so we don't have to worry about what to do in case of a syntax | |
52 | ;; error because the parser just automatically does something. Better yet, | |
53 | ;; we can afford to use a sloppy grammar. | |
54 | ||
55 | ;; The development (especially the parts building the 2D precedence | |
56 | ;; tables and then computing the precedence levels from it) is largely | |
57 | ;; inspired from page 187-194 of "Parsing techniques" by Dick Grune | |
58 | ;; and Ceriel Jacobs (BookBody.pdf available at | |
59 | ;; http://www.cs.vu.nl/~dick/PTAPG.html). | |
60 | ;; | |
e7d67e73 SM |
61 | ;; OTOH we had to kill many chickens, read many coffee grounds, and practice |
62 | ;; untold numbers of black magic spells, to come up with the indentation code. | |
63 | ;; Since then, some of that code has been beaten into submission, but the | |
64 | ;; smie-indent-keyword is still pretty obscure. | |
5ad4bef5 SM |
65 | |
66 | ;;; Code: | |
67 | ||
8723cfa4 SM |
68 | ;; FIXME: I think the behavior on empty lines is wrong. It shouldn't |
69 | ;; look at the next token on subsequent lines. | |
70 | ||
5ad4bef5 SM |
71 | (eval-when-compile (require 'cl)) |
72 | ||
e2d2a205 | 73 | (defvar comment-continue) |
ee8359ba | 74 | (declare-function comment-string-strip "newcomment" (str beforep afterp)) |
e2d2a205 | 75 | |
5ad4bef5 SM |
76 | ;;; Building precedence level tables from BNF specs. |
77 | ||
a49e651e SM |
78 | ;; We have 4 different representations of a "grammar": |
79 | ;; - a BNF table, which is a list of BNF rules of the form | |
80 | ;; (NONTERM RHS1 ... RHSn) where each RHS is a list of terminals (tokens) | |
81 | ;; or nonterminals. Any element in these lists which does not appear as | |
82 | ;; the `car' of a BNF rule is taken to be a terminal. | |
83 | ;; - A list of precedences (key word "precs"), is a list, sorted | |
84 | ;; from lowest to highest precedence, of precedence classes that | |
85 | ;; have the form (ASSOCIATIVITY TERMINAL1 .. TERMINALn), where | |
86 | ;; ASSOCIATIVITY can be `assoc', `left', `right' or `nonassoc'. | |
87 | ;; - a 2 dimensional precedence table (key word "prec2"), is a 2D | |
88 | ;; table recording the precedence relation (can be `<', `=', `>', or | |
89 | ;; nil) between each pair of tokens. | |
90 | ;; - a precedence-level table (key word "levels"), while is a alist | |
91 | ;; giving for each token its left and right precedence level (a | |
92 | ;; number or nil). This is used in `smie-op-levels'. | |
93 | ;; The prec2 tables are only intermediate data structures: the source | |
94 | ;; code normally provides a mix of BNF and precs tables, and then | |
95 | ;; turns them into a levels table, which is what's used by the rest of | |
96 | ;; the SMIE code. | |
97 | ||
5ad4bef5 SM |
98 | (defun smie-set-prec2tab (table x y val &optional override) |
99 | (assert (and x y)) | |
100 | (let* ((key (cons x y)) | |
101 | (old (gethash key table))) | |
102 | (if (and old (not (eq old val))) | |
f5228f84 | 103 | (if (and override (gethash key override)) |
5ad4bef5 SM |
104 | ;; FIXME: The override is meant to resolve ambiguities, |
105 | ;; but it also hides real conflicts. It would be great to | |
106 | ;; be able to distinguish the two cases so that overrides | |
107 | ;; don't hide real conflicts. | |
108 | (puthash key (gethash key override) table) | |
109 | (display-warning 'smie (format "Conflict: %s %s/%s %s" x old val y))) | |
110 | (puthash key val table)))) | |
111 | ||
112 | (defun smie-precs-precedence-table (precs) | |
113 | "Compute a 2D precedence table from a list of precedences. | |
114 | PRECS should be a list, sorted by precedence (e.g. \"+\" will | |
115 | come before \"*\"), of elements of the form \(left OP ...) | |
ee8359ba | 116 | or (right OP ...) or (nonassoc OP ...) or (assoc OP ...). All operators in |
35e53abd | 117 | one of those elements share the same precedence level and associativity." |
5ad4bef5 SM |
118 | (let ((prec2-table (make-hash-table :test 'equal))) |
119 | (dolist (prec precs) | |
120 | (dolist (op (cdr prec)) | |
121 | (let ((selfrule (cdr (assq (car prec) | |
122 | '((left . >) (right . <) (assoc . =)))))) | |
123 | (when selfrule | |
124 | (dolist (other-op (cdr prec)) | |
125 | (smie-set-prec2tab prec2-table op other-op selfrule)))) | |
126 | (let ((op1 '<) (op2 '>)) | |
127 | (dolist (other-prec precs) | |
128 | (if (eq prec other-prec) | |
129 | (setq op1 '> op2 '<) | |
130 | (dolist (other-op (cdr other-prec)) | |
131 | (smie-set-prec2tab prec2-table op other-op op2) | |
132 | (smie-set-prec2tab prec2-table other-op op op1))))))) | |
133 | prec2-table)) | |
134 | ||
f5228f84 | 135 | (defun smie-merge-prec2s (&rest tables) |
5ad4bef5 SM |
136 | (if (null (cdr tables)) |
137 | (car tables) | |
138 | (let ((prec2 (make-hash-table :test 'equal))) | |
139 | (dolist (table tables) | |
140 | (maphash (lambda (k v) | |
141 | (smie-set-prec2tab prec2 (car k) (cdr k) v)) | |
142 | table)) | |
143 | prec2))) | |
144 | ||
145 | (defun smie-bnf-precedence-table (bnf &rest precs) | |
146 | (let ((nts (mapcar 'car bnf)) ;Non-terminals | |
147 | (first-ops-table ()) | |
148 | (last-ops-table ()) | |
149 | (first-nts-table ()) | |
150 | (last-nts-table ()) | |
151 | (prec2 (make-hash-table :test 'equal)) | |
f5228f84 SM |
152 | (override (apply 'smie-merge-prec2s |
153 | (mapcar 'smie-precs-precedence-table precs))) | |
5ad4bef5 SM |
154 | again) |
155 | (dolist (rules bnf) | |
156 | (let ((nt (car rules)) | |
157 | (last-ops ()) | |
158 | (first-ops ()) | |
159 | (last-nts ()) | |
160 | (first-nts ())) | |
161 | (dolist (rhs (cdr rules)) | |
2bc01104 SM |
162 | (unless (consp rhs) |
163 | (signal 'wrong-type-argument `(consp ,rhs))) | |
5ad4bef5 SM |
164 | (if (not (member (car rhs) nts)) |
165 | (pushnew (car rhs) first-ops) | |
166 | (pushnew (car rhs) first-nts) | |
167 | (when (consp (cdr rhs)) | |
168 | ;; If the first is not an OP we add the second (which | |
169 | ;; should be an OP if BNF is an "operator grammar"). | |
170 | ;; Strictly speaking, this should only be done if the | |
171 | ;; first is a non-terminal which can expand to a phrase | |
172 | ;; without any OP in it, but checking doesn't seem worth | |
173 | ;; the trouble, and it lets the writer of the BNF | |
174 | ;; be a bit more sloppy by skipping uninteresting base | |
175 | ;; cases which are terminals but not OPs. | |
176 | (assert (not (member (cadr rhs) nts))) | |
177 | (pushnew (cadr rhs) first-ops))) | |
178 | (let ((shr (reverse rhs))) | |
179 | (if (not (member (car shr) nts)) | |
180 | (pushnew (car shr) last-ops) | |
181 | (pushnew (car shr) last-nts) | |
710a7f46 SM |
182 | (when (consp (cdr shr)) |
183 | (assert (not (member (cadr shr) nts))) | |
184 | (pushnew (cadr shr) last-ops))))) | |
5ad4bef5 SM |
185 | (push (cons nt first-ops) first-ops-table) |
186 | (push (cons nt last-ops) last-ops-table) | |
187 | (push (cons nt first-nts) first-nts-table) | |
188 | (push (cons nt last-nts) last-nts-table))) | |
189 | ;; Compute all first-ops by propagating the initial ones we have | |
190 | ;; now, according to first-nts. | |
191 | (setq again t) | |
192 | (while (prog1 again (setq again nil)) | |
193 | (dolist (first-nts first-nts-table) | |
194 | (let* ((nt (pop first-nts)) | |
195 | (first-ops (assoc nt first-ops-table))) | |
196 | (dolist (first-nt first-nts) | |
197 | (dolist (op (cdr (assoc first-nt first-ops-table))) | |
198 | (unless (member op first-ops) | |
199 | (setq again t) | |
200 | (push op (cdr first-ops)))))))) | |
201 | ;; Same thing for last-ops. | |
202 | (setq again t) | |
203 | (while (prog1 again (setq again nil)) | |
204 | (dolist (last-nts last-nts-table) | |
205 | (let* ((nt (pop last-nts)) | |
206 | (last-ops (assoc nt last-ops-table))) | |
207 | (dolist (last-nt last-nts) | |
208 | (dolist (op (cdr (assoc last-nt last-ops-table))) | |
209 | (unless (member op last-ops) | |
210 | (setq again t) | |
211 | (push op (cdr last-ops)))))))) | |
212 | ;; Now generate the 2D precedence table. | |
213 | (dolist (rules bnf) | |
214 | (dolist (rhs (cdr rules)) | |
215 | (while (cdr rhs) | |
216 | (cond | |
217 | ((member (car rhs) nts) | |
218 | (dolist (last (cdr (assoc (car rhs) last-ops-table))) | |
219 | (smie-set-prec2tab prec2 last (cadr rhs) '> override))) | |
220 | ((member (cadr rhs) nts) | |
221 | (dolist (first (cdr (assoc (cadr rhs) first-ops-table))) | |
222 | (smie-set-prec2tab prec2 (car rhs) first '< override)) | |
223 | (if (and (cddr rhs) (not (member (car (cddr rhs)) nts))) | |
224 | (smie-set-prec2tab prec2 (car rhs) (car (cddr rhs)) | |
225 | '= override))) | |
226 | (t (smie-set-prec2tab prec2 (car rhs) (cadr rhs) '= override))) | |
227 | (setq rhs (cdr rhs))))) | |
228 | prec2)) | |
229 | ||
a49e651e SM |
230 | ;; (defun smie-prec2-closer-alist (prec2 include-inners) |
231 | ;; "Build a closer-alist from a PREC2 table. | |
232 | ;; The return value is in the same form as `smie-closer-alist'. | |
233 | ;; INCLUDE-INNERS if non-nil means that inner keywords will be included | |
234 | ;; in the table, e.g. the table will include things like (\"if\" . \"else\")." | |
235 | ;; (let* ((non-openers '()) | |
236 | ;; (non-closers '()) | |
237 | ;; ;; For each keyword, this gives the matching openers, if any. | |
238 | ;; (openers (make-hash-table :test 'equal)) | |
239 | ;; (closers '()) | |
240 | ;; (done nil)) | |
241 | ;; ;; First, find the non-openers and non-closers. | |
242 | ;; (maphash (lambda (k v) | |
243 | ;; (unless (or (eq v '<) (member (cdr k) non-openers)) | |
244 | ;; (push (cdr k) non-openers)) | |
245 | ;; (unless (or (eq v '>) (member (car k) non-closers)) | |
246 | ;; (push (car k) non-closers))) | |
247 | ;; prec2) | |
248 | ;; ;; Then find the openers and closers. | |
249 | ;; (maphash (lambda (k _) | |
250 | ;; (unless (member (car k) non-openers) | |
251 | ;; (puthash (car k) (list (car k)) openers)) | |
252 | ;; (unless (or (member (cdr k) non-closers) | |
253 | ;; (member (cdr k) closers)) | |
254 | ;; (push (cdr k) closers))) | |
255 | ;; prec2) | |
256 | ;; ;; Then collect the matching elements. | |
257 | ;; (while (not done) | |
258 | ;; (setq done t) | |
259 | ;; (maphash (lambda (k v) | |
260 | ;; (when (eq v '=) | |
261 | ;; (let ((aopeners (gethash (car k) openers)) | |
262 | ;; (dopeners (gethash (cdr k) openers)) | |
263 | ;; (new nil)) | |
264 | ;; (dolist (o aopeners) | |
265 | ;; (unless (member o dopeners) | |
266 | ;; (setq new t) | |
267 | ;; (push o dopeners))) | |
268 | ;; (when new | |
269 | ;; (setq done nil) | |
270 | ;; (puthash (cdr k) dopeners openers))))) | |
271 | ;; prec2)) | |
272 | ;; ;; Finally, dump the resulting table. | |
273 | ;; (let ((alist '())) | |
274 | ;; (maphash (lambda (k v) | |
275 | ;; (when (or include-inners (member k closers)) | |
276 | ;; (dolist (opener v) | |
277 | ;; (unless (equal opener k) | |
278 | ;; (push (cons opener k) alist))))) | |
279 | ;; openers) | |
280 | ;; alist))) | |
281 | ||
282 | (defun smie-bnf-closer-alist (bnf &optional no-inners) | |
283 | ;; We can also build this closer-alist table from a prec2 table, | |
284 | ;; but it takes more work, and the order is unpredictable, which | |
285 | ;; is a problem for smie-close-block. | |
286 | ;; More convenient would be to build it from a levels table since we | |
287 | ;; always have this table (contrary to the BNF), but it has all the | |
288 | ;; disadvantages of the prec2 case plus the disadvantage that the levels | |
289 | ;; table has lost some info which would result in extra invalid pairs. | |
290 | "Build a closer-alist from a BNF table. | |
291 | The return value is in the same form as `smie-closer-alist'. | |
292 | NO-INNERS if non-nil means that inner keywords will be excluded | |
293 | from the table, e.g. the table will not include things like (\"if\" . \"else\")." | |
294 | (let ((nts (mapcar #'car bnf)) ;non terminals. | |
295 | (alist '())) | |
296 | (dolist (nt bnf) | |
297 | (dolist (rhs (cdr nt)) | |
298 | (unless (or (< (length rhs) 2) (member (car rhs) nts)) | |
299 | (if no-inners | |
300 | (let ((last (car (last rhs)))) | |
301 | (unless (member last nts) | |
302 | (pushnew (cons (car rhs) last) alist :test #'equal))) | |
303 | ;; Reverse so that the "real" closer gets there first, | |
304 | ;; which is important for smie-close-block. | |
305 | (dolist (term (reverse (cdr rhs))) | |
306 | (unless (member term nts) | |
307 | (pushnew (cons (car rhs) term) alist :test #'equal))))))) | |
308 | (nreverse alist))) | |
309 | ||
310 | ||
2bc01104 SM |
311 | (defun smie-debug--prec2-cycle (csts) |
312 | "Return a cycle in CSTS, assuming there's one. | |
313 | CSTS is a list of pairs representing arcs in a graph." | |
314 | ;; A PATH is of the form (START . REST) where REST is a reverse | |
315 | ;; list of nodes through which the path goes. | |
316 | (let ((paths (mapcar (lambda (pair) (list (car pair) (cdr pair))) csts)) | |
317 | (cycle nil)) | |
318 | (while (null cycle) | |
319 | (dolist (path (prog1 paths (setq paths nil))) | |
320 | (dolist (cst csts) | |
321 | (when (eq (car cst) (nth 1 path)) | |
322 | (if (eq (cdr cst) (car path)) | |
323 | (setq cycle path) | |
324 | (push (cons (car path) (cons (cdr cst) (cdr path))) | |
325 | paths)))))) | |
326 | (cons (car cycle) (nreverse (cdr cycle))))) | |
327 | ||
328 | (defun smie-debug--describe-cycle (table cycle) | |
329 | (let ((names | |
330 | (mapcar (lambda (val) | |
331 | (let ((res nil)) | |
332 | (dolist (elem table) | |
333 | (if (eq (cdr elem) val) | |
334 | (push (concat "." (car elem)) res)) | |
335 | (if (eq (cddr elem) val) | |
336 | (push (concat (car elem) ".") res))) | |
337 | (assert res) | |
338 | res)) | |
339 | cycle))) | |
340 | (mapconcat | |
4ddea91b | 341 | (lambda (elems) (mapconcat 'identity elems "=")) |
2bc01104 SM |
342 | (append names (list (car names))) |
343 | " < "))) | |
344 | ||
5ad4bef5 | 345 | (defun smie-prec2-levels (prec2) |
8723cfa4 SM |
346 | ;; FIXME: Rather than only return an alist of precedence levels, we should |
347 | ;; also extract other useful data from it: | |
348 | ;; - matching sets of block openers&closers (which can otherwise become | |
349 | ;; collapsed into a single equivalence class in smie-op-levels) for | |
350 | ;; smie-close-block as well as to detect mismatches in smie-next-sexp | |
351 | ;; or in blink-paren (as well as to do the blink-paren for inner | |
352 | ;; keywords like the "in" of "let..in..end"). | |
353 | ;; - better default indentation rules (i.e. non-zero indentation after inner | |
354 | ;; keywords like the "in" of "let..in..end") for smie-indent-after-keyword. | |
355 | ;; Of course, maybe those things would be even better handled in the | |
356 | ;; bnf->prec function. | |
5ad4bef5 SM |
357 | "Take a 2D precedence table and turn it into an alist of precedence levels. |
358 | PREC2 is a table as returned by `smie-precs-precedence-table' or | |
359 | `smie-bnf-precedence-table'." | |
360 | ;; For each operator, we create two "variables" (corresponding to | |
361 | ;; the left and right precedence level), which are represented by | |
a49e651e | 362 | ;; cons cells. Those are the very cons cells that appear in the |
5ad4bef5 SM |
363 | ;; final `table'. The value of each "variable" is kept in the `car'. |
364 | (let ((table ()) | |
365 | (csts ()) | |
366 | (eqs ()) | |
367 | tmp x y) | |
368 | ;; From `prec2' we construct a list of constraints between | |
369 | ;; variables (aka "precedence levels"). These can be either | |
370 | ;; equality constraints (in `eqs') or `<' constraints (in `csts'). | |
371 | (maphash (lambda (k v) | |
372 | (if (setq tmp (assoc (car k) table)) | |
373 | (setq x (cddr tmp)) | |
374 | (setq x (cons nil nil)) | |
375 | (push (cons (car k) (cons nil x)) table)) | |
376 | (if (setq tmp (assoc (cdr k) table)) | |
377 | (setq y (cdr tmp)) | |
378 | (setq y (cons nil (cons nil nil))) | |
379 | (push (cons (cdr k) y) table)) | |
380 | (ecase v | |
381 | (= (push (cons x y) eqs)) | |
382 | (< (push (cons x y) csts)) | |
383 | (> (push (cons y x) csts)))) | |
384 | prec2) | |
385 | ;; First process the equality constraints. | |
386 | (let ((eqs eqs)) | |
387 | (while eqs | |
388 | (let ((from (caar eqs)) | |
389 | (to (cdar eqs))) | |
390 | (setq eqs (cdr eqs)) | |
391 | (if (eq to from) | |
f5228f84 | 392 | nil ;Nothing to do. |
5ad4bef5 SM |
393 | (dolist (other-eq eqs) |
394 | (if (eq from (cdr other-eq)) (setcdr other-eq to)) | |
395 | (when (eq from (car other-eq)) | |
396 | ;; This can happen because of `assoc' settings in precs | |
397 | ;; or because of a rhs like ("op" foo "op"). | |
398 | (setcar other-eq to))) | |
399 | (dolist (cst csts) | |
400 | (if (eq from (cdr cst)) (setcdr cst to)) | |
401 | (if (eq from (car cst)) (setcar cst to))))))) | |
402 | ;; Then eliminate trivial constraints iteratively. | |
403 | (let ((i 0)) | |
404 | (while csts | |
405 | (let ((rhvs (mapcar 'cdr csts)) | |
406 | (progress nil)) | |
407 | (dolist (cst csts) | |
408 | (unless (memq (car cst) rhvs) | |
409 | (setq progress t) | |
472e7ec1 SM |
410 | ;; We could give each var in a given iteration the same value, |
411 | ;; but we can also give them arbitrarily different values. | |
412 | ;; Basically, these are vars between which there is no | |
413 | ;; constraint (neither equality nor inequality), so | |
414 | ;; anything will do. | |
415 | ;; We give them arbitrary values, which means that we | |
416 | ;; replace the "no constraint" case with either > or < | |
417 | ;; but not =. The reason we do that is so as to try and | |
418 | ;; distinguish associative operators (which will have | |
419 | ;; left = right). | |
420 | (unless (caar cst) | |
710a7f46 | 421 | (setcar (car cst) i) |
472e7ec1 | 422 | (incf i)) |
5ad4bef5 SM |
423 | (setq csts (delq cst csts)))) |
424 | (unless progress | |
2bc01104 SM |
425 | (error "Can't resolve the precedence cycle: %s" |
426 | (smie-debug--describe-cycle | |
427 | table (smie-debug--prec2-cycle csts))))) | |
472e7ec1 | 428 | (incf i 10)) |
5ad4bef5 SM |
429 | ;; Propagate equalities back to their source. |
430 | (dolist (eq (nreverse eqs)) | |
f5228f84 | 431 | (assert (or (null (caar eq)) (eq (car eq) (cdr eq)))) |
5ad4bef5 SM |
432 | (setcar (car eq) (cadr eq))) |
433 | ;; Finally, fill in the remaining vars (which only appeared on the | |
434 | ;; right side of the < constraints). | |
f5228f84 SM |
435 | (dolist (x table) |
436 | ;; When both sides are nil, it means this operator binds very | |
437 | ;; very tight, but it's still just an operator, so we give it | |
438 | ;; the highest precedence. | |
439 | ;; OTOH if only one side is nil, it usually means it's like an | |
440 | ;; open-paren, which is very important for indentation purposes, | |
441 | ;; so we keep it nil, to make it easier to recognize. | |
442 | (unless (or (nth 1 x) (nth 2 x)) | |
443 | (setf (nth 1 x) i) | |
444 | (setf (nth 2 x) i)))) | |
5ad4bef5 SM |
445 | table)) |
446 | ||
447 | ;;; Parsing using a precedence level table. | |
448 | ||
449 | (defvar smie-op-levels 'unset | |
450 | "List of token parsing info. | |
451 | Each element is of the form (TOKEN LEFT-LEVEL RIGHT-LEVEL). | |
c2ea5810 SM |
452 | Parsing is done using an operator precedence parser. |
453 | LEFT-LEVEL and RIGHT-LEVEL can be either numbers or nil, where nil | |
454 | means that this operator does not bind on the corresponding side, | |
455 | i.e. a LEFT-LEVEL of nil means this is a token that behaves somewhat like | |
456 | an open-paren, whereas a RIGHT-LEVEL of nil would correspond to something | |
457 | like a close-paren.") | |
5ad4bef5 | 458 | |
11e4d8c0 SM |
459 | (defvar smie-forward-token-function 'smie-default-forward-token |
460 | "Function to scan forward for the next token. | |
461 | Called with no argument should return a token and move to its end. | |
462 | If no token is found, return nil or the empty string. | |
463 | It can return nil when bumping into a parenthesis, which lets SMIE | |
464 | use syntax-tables to handle them in efficient C code.") | |
465 | ||
466 | (defvar smie-backward-token-function 'smie-default-backward-token | |
467 | "Function to scan backward the previous token. | |
468 | Same calling convention as `smie-forward-token-function' except | |
469 | it should move backward to the beginning of the previous token.") | |
470 | ||
472e7ec1 SM |
471 | (defalias 'smie-op-left 'car) |
472 | (defalias 'smie-op-right 'cadr) | |
473 | ||
11e4d8c0 SM |
474 | (defun smie-default-backward-token () |
475 | (forward-comment (- (point))) | |
8723cfa4 SM |
476 | (buffer-substring-no-properties |
477 | (point) | |
478 | (progn (if (zerop (skip-syntax-backward ".")) | |
479 | (skip-syntax-backward "w_'")) | |
480 | (point)))) | |
5ad4bef5 | 481 | |
11e4d8c0 SM |
482 | (defun smie-default-forward-token () |
483 | (forward-comment (point-max)) | |
8723cfa4 SM |
484 | (buffer-substring-no-properties |
485 | (point) | |
486 | (progn (if (zerop (skip-syntax-forward ".")) | |
487 | (skip-syntax-forward "w_'")) | |
488 | (point)))) | |
5ad4bef5 | 489 | |
2bc01104 | 490 | (defun smie--associative-p (toklevels) |
472e7ec1 | 491 | ;; in "a + b + c" we want to stop at each +, but in |
8723cfa4 | 492 | ;; "if a then b elsif c then d else c" we don't want to stop at each keyword. |
472e7ec1 SM |
493 | ;; To distinguish the two cases, we made smie-prec2-levels choose |
494 | ;; different levels for each part of "if a then b else c", so that | |
495 | ;; by checking if the left-level is equal to the right level, we can | |
496 | ;; figure out that it's an associative operator. | |
8723cfa4 SM |
497 | ;; This is not 100% foolproof, tho, since the "elsif" will have to have |
498 | ;; equal left and right levels (since it's optional), so smie-next-sexp | |
499 | ;; has to be careful to distinguish those different cases. | |
472e7ec1 SM |
500 | (eq (smie-op-left toklevels) (smie-op-right toklevels))) |
501 | ||
502 | (defun smie-next-sexp (next-token next-sexp op-forw op-back halfsexp) | |
5ad4bef5 | 503 | "Skip over one sexp. |
472e7ec1 SM |
504 | NEXT-TOKEN is a function of no argument that moves forward by one |
505 | token (after skipping comments if needed) and returns it. | |
506 | NEXT-SEXP is a lower-level function to skip one sexp. | |
507 | OP-FORW is the accessor to the forward level of the level data. | |
508 | OP-BACK is the accessor to the backward level of the level data. | |
5ad4bef5 SM |
509 | HALFSEXP if non-nil, means skip over a partial sexp if needed. I.e. if the |
510 | first token we see is an operator, skip over its left-hand-side argument. | |
511 | Possible return values: | |
472e7ec1 SM |
512 | (FORW-LEVEL POS TOKEN): we couldn't skip TOKEN because its back-level |
513 | is too high. FORW-LEVEL is the forw-level of TOKEN, | |
5ad4bef5 | 514 | POS is its start position in the buffer. |
472e7ec1 | 515 | (t POS TOKEN): same thing when we bump on the wrong side of a paren. |
5ad4bef5 SM |
516 | (nil POS TOKEN): we skipped over a paren-like pair. |
517 | nil: we skipped over an identifier, matched parentheses, ..." | |
472e7ec1 SM |
518 | (catch 'return |
519 | (let ((levels ())) | |
520 | (while | |
521 | (let* ((pos (point)) | |
522 | (token (funcall next-token)) | |
523 | (toklevels (cdr (assoc token smie-op-levels)))) | |
472e7ec1 SM |
524 | (cond |
525 | ((null toklevels) | |
11e4d8c0 | 526 | (when (zerop (length token)) |
710a7f46 SM |
527 | (condition-case err |
528 | (progn (goto-char pos) (funcall next-sexp 1) nil) | |
8723cfa4 SM |
529 | (scan-error (throw 'return |
530 | (list t (caddr err) | |
531 | (buffer-substring-no-properties | |
532 | (caddr err) | |
533 | (+ (caddr err) | |
534 | (if (< (point) (caddr err)) | |
535 | -1 1))))))) | |
224b70cb SM |
536 | (if (eq pos (point)) |
537 | ;; We did not move, so let's abort the loop. | |
538 | (throw 'return (list t (point)))))) | |
472e7ec1 SM |
539 | ((null (funcall op-back toklevels)) |
540 | ;; A token like a paren-close. | |
541 | (assert (funcall op-forw toklevels)) ;Otherwise, why mention it? | |
8723cfa4 | 542 | (push toklevels levels)) |
472e7ec1 | 543 | (t |
8723cfa4 SM |
544 | (while (and levels (< (funcall op-back toklevels) |
545 | (funcall op-forw (car levels)))) | |
472e7ec1 | 546 | (setq levels (cdr levels))) |
5ad4bef5 | 547 | (cond |
472e7ec1 SM |
548 | ((null levels) |
549 | (if (and halfsexp (funcall op-forw toklevels)) | |
8723cfa4 | 550 | (push toklevels levels) |
472e7ec1 SM |
551 | (throw 'return |
552 | (prog1 (list (or (car toklevels) t) (point) token) | |
553 | (goto-char pos))))) | |
5ad4bef5 | 554 | (t |
8723cfa4 SM |
555 | (let ((lastlevels levels)) |
556 | (if (and levels (= (funcall op-back toklevels) | |
557 | (funcall op-forw (car levels)))) | |
710a7f46 | 558 | (setq levels (cdr levels))) |
8723cfa4 SM |
559 | ;; We may have found a match for the previously pending |
560 | ;; operator. Is this the end? | |
710a7f46 | 561 | (cond |
8723cfa4 SM |
562 | ;; Keep looking as long as we haven't matched the |
563 | ;; topmost operator. | |
564 | (levels | |
565 | (if (funcall op-forw toklevels) | |
566 | (push toklevels levels))) | |
567 | ;; We matched the topmost operator. If the new operator | |
568 | ;; is the last in the corresponding BNF rule, we're done. | |
472e7ec1 | 569 | ((null (funcall op-forw toklevels)) |
8723cfa4 | 570 | ;; It is the last element, let's stop here. |
472e7ec1 | 571 | (throw 'return (list nil (point) token))) |
8723cfa4 SM |
572 | ;; If the new operator is not the last in the BNF rule, |
573 | ;; ans is not associative, it's one of the inner operators | |
574 | ;; (like the "in" in "let .. in .. end"), so keep looking. | |
2bc01104 | 575 | ((not (smie--associative-p toklevels)) |
8723cfa4 SM |
576 | (push toklevels levels)) |
577 | ;; The new operator is associative. Two cases: | |
578 | ;; - it's really just an associative operator (like + or ;) | |
579 | ;; in which case we should have stopped right before. | |
580 | ((and lastlevels | |
2bc01104 | 581 | (smie--associative-p (car lastlevels))) |
5ad4bef5 SM |
582 | (throw 'return |
583 | (prog1 (list (or (car toklevels) t) (point) token) | |
472e7ec1 | 584 | (goto-char pos)))) |
8723cfa4 SM |
585 | ;; - it's an associative operator within a larger construct |
586 | ;; (e.g. an "elsif"), so we should just ignore it and keep | |
587 | ;; looking for the closing element. | |
588 | (t (setq levels lastlevels)))))))) | |
472e7ec1 SM |
589 | levels) |
590 | (setq halfsexp nil))))) | |
591 | ||
592 | (defun smie-backward-sexp (&optional halfsexp) | |
593 | "Skip over one sexp. | |
594 | HALFSEXP if non-nil, means skip over a partial sexp if needed. I.e. if the | |
595 | first token we see is an operator, skip over its left-hand-side argument. | |
596 | Possible return values: | |
597 | (LEFT-LEVEL POS TOKEN): we couldn't skip TOKEN because its right-level | |
598 | is too high. LEFT-LEVEL is the left-level of TOKEN, | |
599 | POS is its start position in the buffer. | |
600 | (t POS TOKEN): same thing but for an open-paren or the beginning of buffer. | |
601 | (nil POS TOKEN): we skipped over a paren-like pair. | |
602 | nil: we skipped over an identifier, matched parentheses, ..." | |
710a7f46 SM |
603 | (smie-next-sexp |
604 | (indirect-function smie-backward-token-function) | |
605 | (indirect-function 'backward-sexp) | |
606 | (indirect-function 'smie-op-left) | |
607 | (indirect-function 'smie-op-right) | |
224b70cb | 608 | halfsexp)) |
5ad4bef5 | 609 | |
5ad4bef5 SM |
610 | (defun smie-forward-sexp (&optional halfsexp) |
611 | "Skip over one sexp. | |
612 | HALFSEXP if non-nil, means skip over a partial sexp if needed. I.e. if the | |
613 | first token we see is an operator, skip over its left-hand-side argument. | |
614 | Possible return values: | |
615 | (RIGHT-LEVEL POS TOKEN): we couldn't skip TOKEN because its left-level | |
616 | is too high. RIGHT-LEVEL is the right-level of TOKEN, | |
617 | POS is its end position in the buffer. | |
35e53abd | 618 | (t POS TOKEN): same thing but for an open-paren or the beginning of buffer. |
5ad4bef5 SM |
619 | (nil POS TOKEN): we skipped over a paren-like pair. |
620 | nil: we skipped over an identifier, matched parentheses, ..." | |
710a7f46 SM |
621 | (smie-next-sexp |
622 | (indirect-function smie-forward-token-function) | |
623 | (indirect-function 'forward-sexp) | |
624 | (indirect-function 'smie-op-right) | |
625 | (indirect-function 'smie-op-left) | |
224b70cb | 626 | halfsexp)) |
5ad4bef5 | 627 | |
8723cfa4 SM |
628 | ;;; Miscellanous commands using the precedence parser. |
629 | ||
5ad4bef5 SM |
630 | (defun smie-backward-sexp-command (&optional n) |
631 | "Move backward through N logical elements." | |
8723cfa4 SM |
632 | (interactive "^p") |
633 | (smie-forward-sexp-command (- n))) | |
5ad4bef5 SM |
634 | |
635 | (defun smie-forward-sexp-command (&optional n) | |
636 | "Move forward through N logical elements." | |
8723cfa4 SM |
637 | (interactive "^p") |
638 | (let ((forw (> n 0)) | |
639 | (forward-sexp-function nil)) | |
640 | (while (/= n 0) | |
641 | (setq n (- n (if forw 1 -1))) | |
710a7f46 | 642 | (let ((pos (point)) |
8723cfa4 SM |
643 | (res (if forw |
644 | (smie-forward-sexp 'halfsexp) | |
645 | (smie-backward-sexp 'halfsexp)))) | |
b3a8fe90 | 646 | (if (and (car res) (= pos (point)) (not (if forw (eobp) (bobp)))) |
710a7f46 SM |
647 | (signal 'scan-error |
648 | (list "Containing expression ends prematurely" | |
649 | (cadr res) (cadr res))) | |
8723cfa4 SM |
650 | nil))))) |
651 | ||
652 | (defvar smie-closer-alist nil | |
653 | "Alist giving the closer corresponding to an opener.") | |
654 | ||
655 | (defun smie-close-block () | |
656 | "Close the closest surrounding block." | |
657 | (interactive) | |
658 | (let ((closer | |
659 | (save-excursion | |
660 | (backward-up-list 1) | |
661 | (if (looking-at "\\s(") | |
662 | (string (cdr (syntax-after (point)))) | |
663 | (let* ((open (funcall smie-forward-token-function)) | |
664 | (closer (cdr (assoc open smie-closer-alist))) | |
665 | (levels (list (assoc open smie-op-levels))) | |
666 | (seen '()) | |
667 | (found '())) | |
668 | (cond | |
669 | ;; Even if we improve the auto-computation of closers, | |
670 | ;; there are still cases where we need manual | |
671 | ;; intervention, e.g. for Octave's use of `until' | |
672 | ;; as a pseudo-closer of `do'. | |
673 | (closer) | |
674 | ((or (equal levels '(nil)) (nth 1 (car levels))) | |
675 | (error "Doesn't look like a block")) | |
676 | (t | |
677 | ;; FIXME: With grammars like Octave's, every closer ("end", | |
678 | ;; "endif", "endwhile", ...) has the same level, so we'd need | |
679 | ;; to look at the BNF or at least at the 2D prec-table, in | |
680 | ;; order to find the right closer for a given opener. | |
681 | (while levels | |
682 | (let ((level (pop levels))) | |
683 | (dolist (other smie-op-levels) | |
684 | (when (and (eq (nth 2 level) (nth 1 other)) | |
685 | (not (memq other seen))) | |
686 | (push other seen) | |
687 | (if (nth 2 other) | |
688 | (push other levels) | |
689 | (push (car other) found)))))) | |
690 | (cond | |
691 | ((null found) (error "No known closer for opener %s" open)) | |
692 | ;; FIXME: what should we do if there are various closers? | |
693 | (t (car found)))))))))) | |
694 | (unless (save-excursion (skip-chars-backward " \t") (bolp)) | |
695 | (newline)) | |
696 | (insert closer) | |
697 | (if (save-excursion (skip-chars-forward " \t") (eolp)) | |
698 | (indent-according-to-mode) | |
699 | (reindent-then-newline-and-indent)))) | |
5ad4bef5 | 700 | |
c8977b2e SM |
701 | (defun smie-down-list (&optional arg) |
702 | "Move forward down one level paren-like blocks. Like `down-list'. | |
703 | With argument ARG, do this that many times. | |
704 | A negative argument means move backward but still go down a level. | |
705 | This command assumes point is not in a string or comment." | |
706 | (interactive "p") | |
707 | (let ((start (point)) | |
708 | (inc (if (< arg 0) -1 1)) | |
709 | (offset (if (< arg 0) 1 0)) | |
710 | (next-token (if (< arg 0) | |
711 | smie-backward-token-function | |
712 | smie-forward-token-function))) | |
713 | (while (/= arg 0) | |
714 | (setq arg (- arg inc)) | |
715 | (while | |
716 | (let* ((pos (point)) | |
717 | (token (funcall next-token)) | |
718 | (levels (assoc token smie-op-levels))) | |
719 | (cond | |
720 | ((zerop (length token)) | |
721 | (if (if (< inc 0) (looking-back "\\s(\\|\\s)" (1- (point))) | |
722 | (looking-at "\\s(\\|\\s)")) | |
723 | ;; Go back to `start' in case of an error. This presumes | |
724 | ;; none of the token we've found until now include a ( or ). | |
725 | (progn (goto-char start) (down-list inc) nil) | |
726 | (forward-sexp inc) | |
727 | (/= (point) pos))) | |
728 | ((and levels (null (nth (+ 1 offset) levels))) nil) | |
729 | ((and levels (null (nth (- 2 offset) levels))) | |
730 | (let ((end (point))) | |
731 | (goto-char start) | |
732 | (signal 'scan-error | |
733 | (list "Containing expression ends prematurely" | |
734 | pos end)))) | |
735 | (t))))))) | |
736 | ||
a49e651e SM |
737 | (defvar smie-blink-matching-triggers '(?\s ?\n) |
738 | "Chars which might trigger `blink-matching-open'. | |
739 | These can include the final chars of end-tokens, or chars that are | |
740 | typically inserted right after an end token. | |
741 | I.e. a good choice can be: | |
742 | (delete-dups | |
743 | (mapcar (lambda (kw) (aref (cdr kw) (1- (length (cdr kw))))) | |
744 | smie-closer-alist))") | |
745 | ||
746 | (defcustom smie-blink-matching-inners t | |
747 | "Whether SMIE should blink to matching opener for inner keywords. | |
748 | If non-nil, it will blink not only for \"begin..end\" but also for \"if...else\"." | |
749 | :type 'boolean) | |
750 | ||
751 | (defun smie-blink-matching-check (start end) | |
752 | (save-excursion | |
753 | (goto-char end) | |
754 | (let ((ender (funcall smie-backward-token-function))) | |
755 | (cond | |
756 | ((not (and ender (rassoc ender smie-closer-alist))) | |
757 | ;; This not is one of the begin..end we know how to check. | |
758 | (blink-matching-check-mismatch start end)) | |
759 | ((not start) t) | |
2bc01104 | 760 | ((eq t (car (rassoc ender smie-closer-alist))) nil) |
a49e651e SM |
761 | (t |
762 | (goto-char start) | |
763 | (let ((starter (funcall smie-forward-token-function))) | |
764 | (not (member (cons starter ender) smie-closer-alist)))))))) | |
765 | ||
766 | (defun smie-blink-matching-open () | |
767 | "Blink the matching opener when applicable. | |
768 | This uses SMIE's tables and is expected to be placed on `post-self-insert-hook'." | |
769 | (when (and blink-matching-paren | |
770 | smie-closer-alist ; Optimization. | |
771 | (eq (char-before) last-command-event) ; Sanity check. | |
772 | (memq last-command-event smie-blink-matching-triggers) | |
2bc01104 | 773 | (not (nth 8 (syntax-ppss)))) |
a49e651e SM |
774 | (save-excursion |
775 | (let ((pos (point)) | |
776 | (token (funcall smie-backward-token-function))) | |
2bc01104 SM |
777 | (when (and (eq (point) (1- pos)) |
778 | (= 1 (length token)) | |
779 | (not (rassoc token smie-closer-alist))) | |
780 | ;; The trigger char is itself a token but is not one of the | |
781 | ;; closers (e.g. ?\; in Octave mode), so go back to the | |
782 | ;; previous token. | |
783 | (setq pos (point)) | |
784 | (setq token (save-excursion | |
785 | (funcall smie-backward-token-function)))) | |
786 | (when (rassoc token smie-closer-alist) | |
787 | ;; We're after a close token. Let's still make sure we | |
788 | ;; didn't skip a comment to find that token. | |
789 | (funcall smie-forward-token-function) | |
790 | (when (and (save-excursion | |
791 | ;; Trigger can be SPC, or reindent. | |
792 | (skip-chars-forward " \n\t") | |
793 | (>= (point) pos)) | |
794 | ;; If token ends with a trigger char, so don't blink for | |
795 | ;; anything else than this trigger char, lest we'd blink | |
796 | ;; both when inserting the trigger char and when | |
797 | ;; inserting a subsequent trigger char like SPC. | |
798 | (or (eq (point) pos) | |
799 | (not (memq (char-before) | |
800 | smie-blink-matching-triggers))) | |
801 | (or smie-blink-matching-inners | |
802 | (null (nth 2 (assoc token smie-op-levels))))) | |
803 | ;; The major mode might set blink-matching-check-function | |
804 | ;; buffer-locally so that interactive calls to | |
805 | ;; blink-matching-open work right, but let's not presume | |
806 | ;; that's the case. | |
807 | (let ((blink-matching-check-function #'smie-blink-matching-check)) | |
808 | (blink-matching-open)))))))) | |
a49e651e | 809 | |
5ad4bef5 SM |
810 | ;;; The indentation engine. |
811 | ||
812 | (defcustom smie-indent-basic 4 | |
813 | "Basic amount of indentation." | |
814 | :type 'integer) | |
815 | ||
816 | (defvar smie-indent-rules 'unset | |
927c346b SM |
817 | ;; TODO: For SML, we need more rule formats, so as to handle |
818 | ;; structure Foo = | |
819 | ;; Bar (toto) | |
820 | ;; and | |
821 | ;; structure Foo = | |
822 | ;; struct ... end | |
823 | ;; I.e. the indentation after "=" depends on the parent ("structure") | |
824 | ;; as well as on the following token ("struct"). | |
5ad4bef5 | 825 | "Rules of the following form. |
83156c18 | 826 | \((:before . TOK) . OFFSET-RULES) how to indent TOK itself. |
c2ea5810 | 827 | \(TOK . OFFSET-RULES) how to indent right after TOK. |
5ad4bef5 SM |
828 | \(list-intro . TOKENS) declare TOKENS as being followed by what may look like |
829 | a funcall but is just a sequence of expressions. | |
830 | \(t . OFFSET) basic indentation step. | |
831 | \(args . OFFSET) indentation of arguments. | |
8723cfa4 | 832 | \((T1 . T2) OFFSET) like ((:before . T2) (:parent T1 OFFSET)). |
c2ea5810 | 833 | |
83156c18 SM |
834 | OFFSET-RULES is a list of elements which can each either be: |
835 | ||
c2ea5810 SM |
836 | \(:hanging . OFFSET-RULES) if TOK is hanging, use OFFSET-RULES. |
837 | \(:parent PARENT . OFFSET-RULES) if TOK's parent is PARENT, use OFFSET-RULES. | |
838 | \(:next TOKEN . OFFSET-RULES) if TOK is followed by TOKEN, use OFFSET-RULES. | |
8723cfa4 SM |
839 | \(:prev TOKEN . OFFSET-RULES) if TOK is preceded by TOKEN, use |
840 | \(:bolp . OFFSET-RULES) If TOK is first on a line, use OFFSET-RULES. | |
841 | OFFSET the offset to use. | |
842 | ||
ee992a8c | 843 | PARENT can be either the name of the parent or a list of such names. |
8723cfa4 SM |
844 | |
845 | OFFSET can be of the form: | |
83156c18 SM |
846 | `point' align with the token. |
847 | `parent' align with the parent. | |
8723cfa4 SM |
848 | NUMBER offset by NUMBER. |
849 | \(+ OFFSETS...) use the sum of OFFSETS. | |
ee992a8c | 850 | VARIABLE use the value of VARIABLE as offset. |
8723cfa4 SM |
851 | |
852 | The precise meaning of `point' depends on various details: it can | |
853 | either mean the position of the token we're indenting, or the | |
854 | position of its parent, or the position right after its parent. | |
83156c18 | 855 | |
ee992a8c SM |
856 | A nil offset for indentation after an opening token defaults |
857 | to `smie-indent-basic'.") | |
5ad4bef5 | 858 | |
2bc01104 | 859 | (defun smie-indent--hanging-p () |
11e4d8c0 | 860 | ;; A hanging keyword is one that's at the end of a line except it's not at |
5ad4bef5 | 861 | ;; the beginning of a line. |
11e4d8c0 SM |
862 | (and (save-excursion |
863 | (when (zerop (length (funcall smie-forward-token-function))) | |
864 | ;; Could be an open-paren. | |
865 | (forward-char 1)) | |
866 | (skip-chars-forward " \t") | |
867 | (eolp)) | |
596880ea | 868 | (not (smie-indent--bolp)))) |
5ad4bef5 | 869 | |
2bc01104 | 870 | (defun smie-indent--bolp () |
5ad4bef5 SM |
871 | (save-excursion (skip-chars-backward " \t") (bolp))) |
872 | ||
2bc01104 | 873 | (defun smie-indent--offset (elem) |
5ad4bef5 SM |
874 | (or (cdr (assq elem smie-indent-rules)) |
875 | (cdr (assq t smie-indent-rules)) | |
876 | smie-indent-basic)) | |
877 | ||
8723cfa4 SM |
878 | (defvar smie-indent-debug-log) |
879 | ||
2bc01104 | 880 | (defun smie-indent--offset-rule (tokinfo &optional after parent) |
83156c18 SM |
881 | "Apply the OFFSET-RULES in TOKINFO. |
882 | Point is expected to be right in front of the token corresponding to TOKINFO. | |
883 | If computing the indentation after the token, then AFTER is the position | |
8723cfa4 SM |
884 | after the token, otherwise it should be nil. |
885 | PARENT if non-nil should be the parent info returned by `smie-backward-sexp'." | |
c2ea5810 | 886 | (let ((rules (cdr tokinfo)) |
8723cfa4 | 887 | next prev |
83156c18 | 888 | offset) |
c2ea5810 SM |
889 | (while (consp rules) |
890 | (let ((rule (pop rules))) | |
891 | (cond | |
892 | ((not (consp rule)) (setq offset rule)) | |
8723cfa4 | 893 | ((eq (car rule) '+) (setq offset rule)) |
c2ea5810 | 894 | ((eq (car rule) :hanging) |
2bc01104 | 895 | (when (smie-indent--hanging-p) |
c2ea5810 | 896 | (setq rules (cdr rule)))) |
8723cfa4 | 897 | ((eq (car rule) :bolp) |
2bc01104 | 898 | (when (smie-indent--bolp) |
8723cfa4 SM |
899 | (setq rules (cdr rule)))) |
900 | ((eq (car rule) :eolp) | |
901 | (unless after | |
902 | (error "Can't use :eolp in :before indentation rules")) | |
903 | (when (> after (line-end-position)) | |
904 | (setq rules (cdr rule)))) | |
83156c18 SM |
905 | ((eq (car rule) :prev) |
906 | (unless prev | |
907 | (save-excursion | |
908 | (setq prev (smie-indent-backward-token)))) | |
909 | (when (equal (car prev) (cadr rule)) | |
910 | (setq rules (cddr rule)))) | |
c2ea5810 SM |
911 | ((eq (car rule) :next) |
912 | (unless next | |
83156c18 SM |
913 | (unless after |
914 | (error "Can't use :next in :before indentation rules")) | |
c2ea5810 SM |
915 | (save-excursion |
916 | (goto-char after) | |
83156c18 SM |
917 | (setq next (smie-indent-forward-token)))) |
918 | (when (equal (car next) (cadr rule)) | |
c2ea5810 SM |
919 | (setq rules (cddr rule)))) |
920 | ((eq (car rule) :parent) | |
921 | (unless parent | |
922 | (save-excursion | |
83156c18 | 923 | (if after (goto-char after)) |
c2ea5810 | 924 | (setq parent (smie-backward-sexp 'halfsexp)))) |
ee992a8c SM |
925 | (when (if (listp (cadr rule)) |
926 | (member (nth 2 parent) (cadr rule)) | |
927 | (equal (nth 2 parent) (cadr rule))) | |
c2ea5810 | 928 | (setq rules (cddr rule)))) |
83156c18 | 929 | (t (error "Unknown rule %s for indentation of %s" |
c2ea5810 | 930 | rule (car tokinfo)))))) |
8723cfa4 SM |
931 | ;; If `offset' is not set yet, use `rules' to handle the case where |
932 | ;; the tokinfo uses the old-style ((PARENT . TOK). OFFSET). | |
933 | (unless offset (setq offset rules)) | |
934 | (when (boundp 'smie-indent-debug-log) | |
935 | (push (list (point) offset tokinfo) smie-indent-debug-log)) | |
83156c18 | 936 | offset)) |
c2ea5810 | 937 | |
2bc01104 | 938 | (defun smie-indent--column (offset &optional base parent virtual-point) |
8723cfa4 SM |
939 | "Compute the actual column to use for a given OFFSET. |
940 | BASE is the base position to use, and PARENT is the parent info, if any. | |
941 | If VIRTUAL-POINT is non-nil, then `point' is virtual." | |
942 | (cond | |
943 | ((eq (car-safe offset) '+) | |
2bc01104 | 944 | (apply '+ (mapcar (lambda (offset) (smie-indent--column offset nil parent)) |
8723cfa4 SM |
945 | (cdr offset)))) |
946 | ((integerp offset) | |
947 | (+ offset | |
948 | (case base | |
949 | ((nil) 0) | |
950 | (parent (goto-char (cadr parent)) | |
951 | (smie-indent-virtual)) | |
952 | (t | |
953 | (goto-char base) | |
954 | ;; For indentation after "(let" in SML-mode, we end up accumulating | |
955 | ;; the offset of "(" and the offset of "let", so we use `min' to try | |
956 | ;; and get it right either way. | |
957 | (min (smie-indent-virtual) (current-column)))))) | |
958 | ((eq offset 'point) | |
959 | ;; In indent-keyword, if we're indenting `then' wrt `if', we want to use | |
960 | ;; indent-virtual rather than use just current-column, so that we can | |
961 | ;; apply the (:before . "if") rule which does the "else if" dance in SML. | |
962 | ;; But in other cases, we do not want to use indent-virtual | |
963 | ;; (e.g. indentation of "*" w.r.t "+", or ";" wrt "("). We could just | |
964 | ;; always use indent-virtual and then have indent-rules say explicitly | |
965 | ;; to use `point' after things like "(" or "+" when they're not at EOL, | |
966 | ;; but you'd end up with lots of those rules. | |
967 | ;; So we use a heuristic here, which is that we only use virtual if | |
968 | ;; the parent is tightly linked to the child token (they're part of | |
969 | ;; the same BNF rule). | |
970 | (if (and virtual-point (null (car parent))) ;Black magic :-( | |
971 | (smie-indent-virtual) (current-column))) | |
972 | ((eq offset 'parent) | |
973 | (unless parent | |
974 | (setq parent (or (smie-backward-sexp 'halfsexp) :notfound))) | |
975 | (if (consp parent) (goto-char (cadr parent))) | |
976 | (smie-indent-virtual)) | |
977 | ((eq offset nil) nil) | |
ee992a8c | 978 | ((and (symbolp offset) (boundp 'offset)) |
2bc01104 | 979 | (smie-indent--column (symbol-value offset) base parent virtual-point)) |
8723cfa4 SM |
980 | (t (error "Unknown indentation offset %s" offset)))) |
981 | ||
c2ea5810 SM |
982 | (defun smie-indent-forward-token () |
983 | "Skip token forward and return it, along with its levels." | |
984 | (let ((tok (funcall smie-forward-token-function))) | |
985 | (cond | |
986 | ((< 0 (length tok)) (assoc tok smie-op-levels)) | |
987 | ((looking-at "\\s(") | |
988 | (forward-char 1) | |
989 | (list (buffer-substring (1- (point)) (point)) nil 0))))) | |
990 | ||
991 | (defun smie-indent-backward-token () | |
992 | "Skip token backward and return it, along with its levels." | |
993 | (let ((tok (funcall smie-backward-token-function))) | |
994 | (cond | |
995 | ((< 0 (length tok)) (assoc tok smie-op-levels)) | |
996 | ;; 4 == Open paren syntax. | |
997 | ((eq 4 (syntax-class (syntax-after (1- (point))))) | |
998 | (forward-char -1) | |
999 | (list (buffer-substring (point) (1+ (point))) nil 0))))) | |
1000 | ||
83156c18 SM |
1001 | (defun smie-indent-virtual () |
1002 | ;; We used to take an optional arg (with value :not-hanging) to specify that | |
1003 | ;; we should only use (smie-indent-calculate) if we're looking at a hanging | |
1004 | ;; keyword. This was a bad idea, because the virtual indent of a position | |
1005 | ;; should not depend on the caller, since it leads to situations where two | |
1006 | ;; dependent indentations get indented differently. | |
dd2c3c92 SM |
1007 | "Compute the virtual indentation to use for point. |
1008 | This is used when we're not trying to indent point but just | |
5ad4bef5 | 1009 | need to compute the column at which point should be indented |
83156c18 | 1010 | in order to figure out the indentation of some other (further down) point." |
dd2c3c92 | 1011 | ;; Trust pre-existing indentation on other lines. |
2bc01104 | 1012 | (if (smie-indent--bolp) (current-column) (smie-indent-calculate))) |
dd2c3c92 SM |
1013 | |
1014 | (defun smie-indent-fixindent () | |
1015 | ;; Obey the `fixindent' special comment. | |
2bc01104 | 1016 | (and (smie-indent--bolp) |
c2ea5810 | 1017 | (save-excursion |
710a7f46 SM |
1018 | (comment-normalize-vars) |
1019 | (re-search-forward (concat comment-start-skip | |
1020 | "fixindent" | |
1021 | comment-end-skip) | |
1022 | ;; 1+ to account for the \n comment termination. | |
1023 | (1+ (line-end-position)) t)) | |
1024 | (current-column))) | |
dd2c3c92 SM |
1025 | |
1026 | (defun smie-indent-bob () | |
1027 | ;; Start the file at column 0. | |
1028 | (save-excursion | |
1029 | (forward-comment (- (point))) | |
1030 | (if (bobp) 0))) | |
1031 | ||
1032 | (defun smie-indent-close () | |
1033 | ;; Align close paren with opening paren. | |
1034 | (save-excursion | |
1035 | ;; (forward-comment (point-max)) | |
1036 | (when (looking-at "\\s)") | |
1037 | (while (not (zerop (skip-syntax-forward ")"))) | |
1038 | (skip-chars-forward " \t")) | |
1039 | (condition-case nil | |
1040 | (progn | |
1041 | (backward-sexp 1) | |
83156c18 | 1042 | (smie-indent-virtual)) ;:not-hanging |
dd2c3c92 SM |
1043 | (scan-error nil))))) |
1044 | ||
1045 | (defun smie-indent-keyword () | |
1046 | ;; Align closing token with the corresponding opening one. | |
1047 | ;; (e.g. "of" with "case", or "in" with "let"). | |
1048 | (save-excursion | |
1049 | (let* ((pos (point)) | |
c2ea5810 SM |
1050 | (toklevels (smie-indent-forward-token)) |
1051 | (token (pop toklevels))) | |
83156c18 | 1052 | (if (null (car toklevels)) |
8723cfa4 SM |
1053 | (save-excursion |
1054 | (goto-char pos) | |
1055 | ;; Different cases: | |
2bc01104 | 1056 | ;; - smie-indent--bolp: "indent according to others". |
710a7f46 SM |
1057 | ;; - common hanging: "indent according to others". |
1058 | ;; - SML-let hanging: "indent like parent". | |
1059 | ;; - if-after-else: "indent-like parent". | |
1060 | ;; - middle-of-line: "trust current position". | |
1061 | (cond | |
1062 | ((null (cdr toklevels)) nil) ;Not a keyword. | |
2bc01104 | 1063 | ((smie-indent--bolp) |
710a7f46 SM |
1064 | ;; For an open-paren-like thingy at BOL, always indent only |
1065 | ;; based on other rules (typically smie-indent-after-keyword). | |
1066 | nil) | |
1067 | (t | |
8723cfa4 SM |
1068 | ;; We're only ever here for virtual-indent, which is why |
1069 | ;; we can use (current-column) as answer for `point'. | |
1070 | (let* ((tokinfo (or (assoc (cons :before token) | |
1071 | smie-indent-rules) | |
710a7f46 | 1072 | ;; By default use point unless we're hanging. |
8723cfa4 SM |
1073 | `((:before . ,token) (:hanging nil) point))) |
1074 | ;; (after (prog1 (point) (goto-char pos))) | |
2bc01104 SM |
1075 | (offset (smie-indent--offset-rule tokinfo))) |
1076 | (smie-indent--column offset))))) | |
83156c18 SM |
1077 | |
1078 | ;; FIXME: This still looks too much like black magic!! | |
1079 | ;; FIXME: Rather than a bunch of rules like (PARENT . TOKEN), we | |
1080 | ;; want a single rule for TOKEN with different cases for each PARENT. | |
8723cfa4 SM |
1081 | (let* ((parent (smie-backward-sexp 'halfsexp)) |
1082 | (tokinfo | |
1083 | (or (assoc (cons (caddr parent) token) | |
1084 | smie-indent-rules) | |
1085 | (assoc (cons :before token) smie-indent-rules) | |
1086 | ;; Default rule. | |
1087 | `((:before . ,token) | |
1088 | ;; (:parent open 0) | |
1089 | point))) | |
1090 | (offset (save-excursion | |
1091 | (goto-char pos) | |
2bc01104 | 1092 | (smie-indent--offset-rule tokinfo nil parent)))) |
8723cfa4 SM |
1093 | ;; Different behaviors: |
1094 | ;; - align with parent. | |
1095 | ;; - parent + offset. | |
1096 | ;; - after parent's column + offset (actually, after or before | |
1097 | ;; depending on where backward-sexp stopped). | |
1098 | ;; ? let it drop to some other indentation function (almost never). | |
1099 | ;; ? parent + offset + parent's own offset. | |
1100 | ;; Different cases: | |
1101 | ;; - bump into a same-level operator. | |
1102 | ;; - bump into a specific known parent. | |
1103 | ;; - find a matching open-paren thingy. | |
1104 | ;; - bump into some random parent. | |
1105 | ;; ? borderline case (almost never). | |
1106 | ;; ? bump immediately into a parent. | |
c2ea5810 SM |
1107 | (cond |
1108 | ((not (or (< (point) pos) | |
8723cfa4 | 1109 | (and (cadr parent) (< (cadr parent) pos)))) |
c2ea5810 | 1110 | ;; If we didn't move at all, that means we didn't really skip |
8723cfa4 SM |
1111 | ;; what we wanted. Should almost never happen, other than |
1112 | ;; maybe when an infix or close-paren is at the beginning | |
1113 | ;; of a buffer. | |
c2ea5810 | 1114 | nil) |
8723cfa4 | 1115 | ((eq (car parent) (car toklevels)) |
c2ea5810 | 1116 | ;; We bumped into a same-level operator. align with it. |
2bc01104 | 1117 | (if (and (smie-indent--bolp) (/= (point) pos) |
8723cfa4 SM |
1118 | (save-excursion |
1119 | (goto-char (goto-char (cadr parent))) | |
2bc01104 | 1120 | (not (smie-indent--bolp))) |
8723cfa4 SM |
1121 | ;; Check the offset of `token' rather then its parent |
1122 | ;; because its parent may have used a special rule. E.g. | |
1123 | ;; function foo; | |
1124 | ;; line2; | |
1125 | ;; line3; | |
1126 | ;; The ; on the first line had a special rule, but when | |
1127 | ;; indenting line3, we don't care about it and want to | |
1128 | ;; align with line2. | |
1129 | (memq offset '(point nil))) | |
1130 | ;; If the parent is at EOL and its children are indented like | |
1131 | ;; itself, then we can just obey the indentation chosen for the | |
1132 | ;; child. | |
1133 | ;; This is important for operators like ";" which | |
1134 | ;; are usually at EOL (and have an offset of 0): otherwise we'd | |
1135 | ;; always go back over all the statements, which is | |
1136 | ;; a performance problem and would also mean that fixindents | |
1137 | ;; in the middle of such a sequence would be ignored. | |
1138 | ;; | |
1139 | ;; This is a delicate point! | |
1140 | ;; Even if the offset is not 0, we could follow the same logic | |
1141 | ;; and subtract the offset from the child's indentation. | |
1142 | ;; But that would more often be a bad idea: OT1H we generally | |
1143 | ;; want to reuse the closest similar indentation point, so that | |
1144 | ;; the user's choice (or the fixindents) are obeyed. But OTOH | |
1145 | ;; we don't want this to affect "unrelated" parts of the code. | |
1146 | ;; E.g. a fixindent in the body of a "begin..end" should not | |
1147 | ;; affect the indentation of the "end". | |
1148 | (current-column) | |
1149 | (goto-char (cadr parent)) | |
710a7f46 SM |
1150 | ;; Don't use (smie-indent-virtual :not-hanging) here, because we |
1151 | ;; want to jump back over a sequence of same-level ops such as | |
1152 | ;; a -> b -> c | |
1153 | ;; -> d | |
1154 | ;; So as to align with the earliest appropriate place. | |
8723cfa4 SM |
1155 | (smie-indent-virtual))) |
1156 | (tokinfo | |
2bc01104 | 1157 | (if (and (= (point) pos) (smie-indent--bolp) |
8723cfa4 SM |
1158 | (or (eq offset 'point) |
1159 | (and (consp offset) (memq 'point offset)))) | |
710a7f46 | 1160 | ;; Since we started at BOL, we're not computing a virtual |
8723cfa4 SM |
1161 | ;; indentation, and we're still at the starting point, so |
1162 | ;; we can't use `current-column' which would cause | |
1163 | ;; indentation to depend on itself. | |
1164 | nil | |
2bc01104 | 1165 | (smie-indent--column offset 'parent parent |
8723cfa4 SM |
1166 | ;; If we're still at pos, indent-virtual |
1167 | ;; will inf-loop. | |
1168 | (unless (= (point) pos) 'virtual)))))))))) | |
dd2c3c92 SM |
1169 | |
1170 | (defun smie-indent-comment () | |
8723cfa4 SM |
1171 | "Compute indentation of a comment." |
1172 | ;; Don't do it for virtual indentations. We should normally never be "in | |
1173 | ;; front of a comment" when doing virtual-indentation anyway. And if we are | |
1174 | ;; (as can happen in octave-mode), moving forward can lead to inf-loops. | |
2bc01104 | 1175 | (and (smie-indent--bolp) |
4ddea91b SM |
1176 | (let ((pos (point))) |
1177 | (save-excursion | |
1178 | (beginning-of-line) | |
1179 | (and (re-search-forward comment-start-skip (line-end-position) t) | |
1180 | (eq pos (or (match-end 1) (match-beginning 0)))))) | |
5ad4bef5 | 1181 | (save-excursion |
dd2c3c92 SM |
1182 | (forward-comment (point-max)) |
1183 | (skip-chars-forward " \t\r\n") | |
1184 | (smie-indent-calculate)))) | |
1185 | ||
1186 | (defun smie-indent-comment-continue () | |
1187 | ;; indentation of comment-continue lines. | |
83156c18 SM |
1188 | (let ((continue (and comment-continue |
1189 | (comment-string-strip comment-continue t t)))) | |
c2ea5810 SM |
1190 | (and (< 0 (length continue)) |
1191 | (looking-at (regexp-quote continue)) (nth 4 (syntax-ppss)) | |
710a7f46 SM |
1192 | (let ((ppss (syntax-ppss))) |
1193 | (save-excursion | |
1194 | (forward-line -1) | |
1195 | (if (<= (point) (nth 8 ppss)) | |
1196 | (progn (goto-char (1+ (nth 8 ppss))) (current-column)) | |
1197 | (skip-chars-forward " \t") | |
c2ea5810 SM |
1198 | (if (looking-at (regexp-quote continue)) |
1199 | (current-column)))))))) | |
dd2c3c92 | 1200 | |
4ddea91b SM |
1201 | (defun smie-indent-comment-close () |
1202 | (and (boundp 'comment-end-skip) | |
1203 | comment-end-skip | |
1204 | (not (looking-at " \t*$")) ;Not just a \n comment-closer. | |
1205 | (looking-at comment-end-skip) | |
1206 | (nth 4 (syntax-ppss)) | |
1207 | (save-excursion | |
1208 | (goto-char (nth 8 (syntax-ppss))) | |
1209 | (current-column)))) | |
1210 | ||
1211 | (defun smie-indent-comment-inside () | |
1212 | (and (nth 4 (syntax-ppss)) | |
1213 | 'noindent)) | |
1214 | ||
dd2c3c92 SM |
1215 | (defun smie-indent-after-keyword () |
1216 | ;; Indentation right after a special keyword. | |
1217 | (save-excursion | |
c2ea5810 SM |
1218 | (let* ((pos (point)) |
1219 | (toklevel (smie-indent-backward-token)) | |
1220 | (tok (car toklevel)) | |
1221 | (tokinfo (assoc tok smie-indent-rules))) | |
8723cfa4 | 1222 | ;; Set some default indent rules. |
dd2c3c92 | 1223 | (if (and toklevel (null (cadr toklevel)) (null tokinfo)) |
c2ea5810 SM |
1224 | (setq tokinfo (list (car toklevel)))) |
1225 | ;; (if (and tokinfo (null toklevel)) | |
1226 | ;; (error "Token %S has indent rule but has no parsing info" tok)) | |
dd2c3c92 | 1227 | (when toklevel |
8723cfa4 SM |
1228 | (unless tokinfo |
1229 | ;; The default indentation after a keyword/operator is 0 for | |
1230 | ;; infix and t for prefix. | |
1231 | ;; Using the BNF syntax, we could come up with better | |
1232 | ;; defaults, but we only have the precedence levels here. | |
1233 | (setq tokinfo (list tok 'default-rule | |
2bc01104 | 1234 | (if (cadr toklevel) 0 (smie-indent--offset t))))) |
c2ea5810 | 1235 | (let ((offset |
2bc01104 SM |
1236 | (or (smie-indent--offset-rule tokinfo pos) |
1237 | (smie-indent--offset t)))) | |
8723cfa4 SM |
1238 | (let ((before (point))) |
1239 | (goto-char pos) | |
2bc01104 | 1240 | (smie-indent--column offset before))))))) |
dd2c3c92 SM |
1241 | |
1242 | (defun smie-indent-exps () | |
1243 | ;; Indentation of sequences of simple expressions without | |
1244 | ;; intervening keywords or operators. E.g. "a b c" or "g (balbla) f". | |
1245 | ;; Can be a list of expressions or a function call. | |
1246 | ;; If it's a function call, the first element is special (it's the | |
1247 | ;; function). We distinguish function calls from mere lists of | |
1248 | ;; expressions based on whether the preceding token is listed in | |
1249 | ;; the `list-intro' entry of smie-indent-rules. | |
1250 | ;; | |
1251 | ;; TODO: to indent Lisp code, we should add a way to specify | |
1252 | ;; particular indentation for particular args depending on the | |
1253 | ;; function (which would require always skipping back until the | |
1254 | ;; function). | |
1255 | ;; TODO: to indent C code, such as "if (...) {...}" we might need | |
1256 | ;; to add similar indentation hooks for particular positions, but | |
1257 | ;; based on the preceding token rather than based on the first exp. | |
1258 | (save-excursion | |
1259 | (let ((positions nil) | |
1260 | arg) | |
1261 | (while (and (null (car (smie-backward-sexp))) | |
1262 | (push (point) positions) | |
2bc01104 | 1263 | (not (smie-indent--bolp)))) |
dd2c3c92 SM |
1264 | (save-excursion |
1265 | ;; Figure out if the atom we just skipped is an argument rather | |
1266 | ;; than a function. | |
1267 | (setq arg (or (null (car (smie-backward-sexp))) | |
1268 | (member (funcall smie-backward-token-function) | |
1269 | (cdr (assoc 'list-intro smie-indent-rules)))))) | |
1270 | (cond | |
1271 | ((null positions) | |
1272 | ;; We're the first expression of the list. In that case, the | |
1273 | ;; indentation should be (have been) determined by its context. | |
1274 | nil) | |
1275 | (arg | |
1276 | ;; There's a previous element, and it's not special (it's not | |
1277 | ;; the function), so let's just align with that one. | |
1278 | (goto-char (car positions)) | |
1279 | (current-column)) | |
1280 | ((cdr positions) | |
1281 | ;; We skipped some args plus the function and bumped into something. | |
1282 | ;; Align with the first arg. | |
1283 | (goto-char (cadr positions)) | |
1284 | (current-column)) | |
1285 | (positions | |
1286 | ;; We're the first arg. | |
1287 | (goto-char (car positions)) | |
2bc01104 SM |
1288 | ;; FIXME: Use smie-indent--column. |
1289 | (+ (smie-indent--offset 'args) | |
83156c18 | 1290 | ;; We used to use (smie-indent-virtual), but that |
dd2c3c92 SM |
1291 | ;; doesn't seem right since it might then indent args less than |
1292 | ;; the function itself. | |
1293 | (current-column))))))) | |
1294 | ||
1295 | (defvar smie-indent-functions | |
4ddea91b SM |
1296 | '(smie-indent-fixindent smie-indent-bob smie-indent-close |
1297 | smie-indent-comment smie-indent-comment-continue smie-indent-comment-close | |
1298 | smie-indent-comment-inside smie-indent-keyword smie-indent-after-keyword | |
1299 | smie-indent-exps) | |
dd2c3c92 SM |
1300 | "Functions to compute the indentation. |
1301 | Each function is called with no argument, shouldn't move point, and should | |
1302 | return either nil if it has no opinion, or an integer representing the column | |
1303 | to which that point should be aligned, if we were to reindent it.") | |
1304 | ||
1305 | (defun smie-indent-calculate () | |
1306 | "Compute the indentation to use for point." | |
1307 | (run-hook-with-args-until-success 'smie-indent-functions)) | |
5ad4bef5 SM |
1308 | |
1309 | (defun smie-indent-line () | |
1310 | "Indent current line using the SMIE indentation engine." | |
1311 | (interactive) | |
1312 | (let* ((savep (point)) | |
8723cfa4 | 1313 | (indent (condition-case-no-debug nil |
5ad4bef5 SM |
1314 | (save-excursion |
1315 | (forward-line 0) | |
1316 | (skip-chars-forward " \t") | |
1317 | (if (>= (point) savep) (setq savep nil)) | |
1318 | (or (smie-indent-calculate) 0)) | |
1319 | (error 0)))) | |
1320 | (if (not (numberp indent)) | |
1321 | ;; If something funny is used (e.g. `noindent'), return it. | |
1322 | indent | |
1323 | (if (< indent 0) (setq indent 0)) ;Just in case. | |
1324 | (if savep | |
1325 | (save-excursion (indent-line-to indent)) | |
1326 | (indent-line-to indent))))) | |
1327 | ||
8723cfa4 SM |
1328 | (defun smie-indent-debug () |
1329 | "Show the rules used to compute indentation of current line." | |
1330 | (interactive) | |
1331 | (let ((smie-indent-debug-log '())) | |
1332 | (smie-indent-calculate) | |
1333 | ;; FIXME: please improve! | |
1334 | (message "%S" smie-indent-debug-log))) | |
1335 | ||
5ad4bef5 SM |
1336 | (defun smie-setup (op-levels indent-rules) |
1337 | (set (make-local-variable 'smie-indent-rules) indent-rules) | |
1338 | (set (make-local-variable 'smie-op-levels) op-levels) | |
1339 | (set (make-local-variable 'indent-line-function) 'smie-indent-line)) | |
1340 | ||
1341 | ||
1342 | (provide 'smie) | |
1343 | ;;; smie.el ends here |