]> code.delx.au - gnu-emacs-elpa/blob - sml-mode.el
-
[gnu-emacs-elpa] / sml-mode.el
1 ;;; sml-mode.el --- Major mode for editing (Standard) ML
2
3 ;; Copyright (C) 1999,2000,2004,2007,2010 Stefan Monnier
4 ;; Copyright (C) 1994-1997 Matthew J. Morley
5 ;; Copyright (C) 1989 Lars Bo Nielsen
6
7 ;; Author: Lars Bo Nielsen
8 ;; Olin Shivers
9 ;; Fritz Knabe (?)
10 ;; Steven Gilmore (?)
11 ;; Matthew Morley <mjm@scs.leeds.ac.uk> (aka <matthew@verisity.com>)
12 ;; Matthias Blume <blume@cs.princeton.edu> (aka <blume@kurims.kyoto-u.ac.jp>)
13 ;; (Stefan Monnier) <monnier@iro.umontreal.ca>
14 ;; Maintainer: (Stefan Monnier) <monnier@iro.umontreal.ca>
15 ;; Keywords: SML
16 ;; $Revision$
17 ;; $Date$
18
19 ;; This file is not part of GNU Emacs, but it is distributed under the
20 ;; same conditions.
21
22 ;; This program is free software; you can redistribute it and/or
23 ;; modify it under the terms of the GNU General Public License as
24 ;; published by the Free Software Foundation; either version 3, or (at
25 ;; your option) any later version.
26
27 ;; This program is distributed in the hope that it will be useful, but
28 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
29 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30 ;; General Public License for more details.
31
32 ;; You should have received a copy of the GNU General Public License
33 ;; along with GNU Emacs; see the file COPYING. If not, write to the
34 ;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
35
36 ;;; Commentary:
37
38 ;;; HISTORY
39
40 ;; Still under construction: History obscure, needs a biographer as
41 ;; well as a M-x doctor. Change Log on request.
42
43 ;; Hacked by Olin Shivers for comint from Lars Bo Nielsen's sml.el.
44
45 ;; Hacked by Matthew Morley to incorporate Fritz Knabe's hilite and
46 ;; font-lock patterns, some of Steven Gilmore's (reduced) easy-menus,
47 ;; and numerous bugs and bug-fixes.
48
49 ;;; DESCRIPTION
50
51 ;; See accompanying info file: sml-mode.info
52
53 ;;; FOR YOUR .EMACS FILE
54
55 ;; If sml-mode.el lives in some non-standard directory, you must tell
56 ;; emacs where to get it. This may or may not be necessary:
57
58 ;; (add-to-list 'load-path "~jones/lib/emacs/")
59
60 ;; Then to access the commands autoload sml-mode with that command:
61
62 ;; (load "sml-mode-startup")
63
64 ;; sml-mode-hook is run whenever a new sml-mode buffer is created.
65
66 ;; Finally, there are inferior-sml-{mode,load}-hooks -- see comments
67 ;; in sml-proc.el. For much more information consult the mode's *info*
68 ;; tree.
69
70 ;;; Code:
71
72 (eval-when-compile (require 'cl))
73 (require 'sml-util)
74 (require 'sml-defs)
75
76 (defvar sml-use-smie t)
77
78 (require 'smie nil 'noerror)
79 (unless (and sml-use-smie (fboundp 'smie-setup))
80 (require 'sml-indent))
81
82 (condition-case nil (require 'skeleton) (error nil))
83
84 ;;; VARIABLES CONTROLLING INDENTATION
85
86 (defcustom sml-indent-level 4
87 "Indentation of blocks in ML (see also `sml-indent-rule')."
88 :group 'sml
89 :type '(integer))
90
91 (defcustom sml-indent-args sml-indent-level
92 "*Indentation of args placed on a separate line."
93 :group 'sml
94 :type '(integer))
95
96 ;; (defvar sml-indent-align-args t
97 ;; "*Whether the arguments should be aligned.")
98
99 ;; (defvar sml-case-indent nil
100 ;; "*How to indent case-of expressions.
101 ;; If t: case expr If nil: case expr of
102 ;; of exp1 => ... exp1 => ...
103 ;; | exp2 => ... | exp2 => ...
104
105 ;; The first seems to be the standard in SML/NJ, but the second
106 ;; seems nicer...")
107
108 (defcustom sml-electric-semi-mode nil
109 "*If non-nil, `\;' will self insert, reindent the line, and do a newline.
110 If nil, just insert a `\;'. (To insert while t, do: \\[quoted-insert] \;)."
111 :group 'sml
112 :type 'boolean)
113
114 (defcustom sml-rightalign-and t
115 "If non-nil, right-align `and' with its leader.
116 If nil: If t:
117 datatype a = A datatype a = A
118 and b = B and b = B"
119 :group 'sml
120 :type 'boolean)
121
122 ;;; OTHER GENERIC MODE VARIABLES
123
124 (defvar sml-mode-info "sml-mode"
125 "*Where to find Info file for `sml-mode'.
126 The default assumes the info file \"sml-mode.info\" is on Emacs' info
127 directory path. If it is not, either put the file on the standard path
128 or set the variable `sml-mode-info' to the exact location of this file
129
130 (setq sml-mode-info \"/usr/me/lib/info/sml-mode\")
131
132 in your .emacs file. You can always set it interactively with the
133 set-variable command.")
134
135 (defvar sml-mode-hook nil
136 "*Run upon entering `sml-mode'.
137 This is a good place to put your preferred key bindings.")
138
139 ;;; CODE FOR SML-MODE
140
141 (defun sml-mode-info ()
142 "Command to access the TeXinfo documentation for `sml-mode'.
143 See doc for the variable `sml-mode-info'."
144 (interactive)
145 (require 'info)
146 (condition-case nil
147 (info sml-mode-info)
148 (error (progn
149 (describe-variable 'sml-mode-info)
150 (message "Can't find it... set this variable first!")))))
151
152
153 ;;; Autoload functions -- no-doc is another idea cribbed from AucTeX!
154
155 (let ((sml-no-doc
156 "This function is part of sml-proc, and has not yet been loaded.
157 Full documentation will be available after autoloading the function."))
158
159 (autoload 'sml-compile "sml-proc" sml-no-doc t)
160 (autoload 'sml-load-file "sml-proc" sml-no-doc t)
161 (autoload 'switch-to-sml "sml-proc" sml-no-doc t)
162 (autoload 'sml-send-region "sml-proc" sml-no-doc t)
163 (autoload 'sml-send-buffer "sml-proc" sml-no-doc t))
164
165 ;; font-lock setup
166
167 (defconst sml-keywords-regexp
168 (sml-syms-re '("abstraction" "abstype" "and" "andalso" "as" "before" "case"
169 "datatype" "else" "end" "eqtype" "exception" "do" "fn"
170 "fun" "functor" "handle" "if" "in" "include" "infix"
171 "infixr" "let" "local" "nonfix" "of" "op" "open" "orelse"
172 "overload" "raise" "rec" "sharing" "sig" "signature"
173 "struct" "structure" "then" "type" "val" "where" "while"
174 "with" "withtype" "o"))
175 "A regexp that matches any and all keywords of SML.")
176
177 (defconst sml-tyvarseq-re
178 "\\(\\('+\\(\\sw\\|\\s_\\)+\\|(\\([,']\\|\\sw\\|\\s_\\|\\s-\\)+)\\)\\s-+\\)?")
179
180 ;;; Font-lock settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
181
182 (defcustom sml-font-lock-symbols nil
183 "Display \\ and -> and such using symbols in fonts.
184 This may sound like a neat trick, but be extra careful: it changes the
185 alignment and can thus lead to nasty surprises w.r.t layout.
186 If t, try to use whichever font is available. Otherwise you can
187 set it to a particular font of your preference among `japanese-jisx0208'
188 and `unicode'."
189 :type '(choice (const nil)
190 (const t)
191 (const unicode)
192 (const japanese-jisx0208)))
193
194 (defconst sml-font-lock-symbols-alist
195 (append
196 ;; The symbols can come from a JIS0208 font.
197 (and (fboundp 'make-char) (charsetp 'japanese-jisx0208)
198 (memq sml-font-lock-symbols '(t japanese-jisx0208))
199 (list (cons "fn" (make-char 'japanese-jisx0208 38 75))
200 (cons "andalso" (make-char 'japanese-jisx0208 34 74))
201 (cons "orelse" (make-char 'japanese-jisx0208 34 75))
202 ;; (cons "as" (make-char 'japanese-jisx0208 34 97))
203 (cons "not" (make-char 'japanese-jisx0208 34 76))
204 (cons "div" (make-char 'japanese-jisx0208 33 96))
205 ;; (cons "*" (make-char 'japanese-jisx0208 33 95))
206 (cons "->" (make-char 'japanese-jisx0208 34 42))
207 (cons "=>" (make-char 'japanese-jisx0208 34 77))
208 (cons "<-" (make-char 'japanese-jisx0208 34 43))
209 (cons "<>" (make-char 'japanese-jisx0208 33 98))
210 (cons ">=" (make-char 'japanese-jisx0208 33 102))
211 (cons "<=" (make-char 'japanese-jisx0208 33 101))
212 (cons "..." (make-char 'japanese-jisx0208 33 68))
213 ;; Some greek letters for type parameters.
214 (cons "'a" (make-char 'japanese-jisx0208 38 65))
215 (cons "'b" (make-char 'japanese-jisx0208 38 66))
216 (cons "'c" (make-char 'japanese-jisx0208 38 67))
217 (cons "'d" (make-char 'japanese-jisx0208 38 68))
218 ))
219 ;; Or a unicode font.
220 (and (fboundp 'decode-char)
221 (memq sml-font-lock-symbols '(t unicode))
222 (list (cons "fn" (decode-char 'ucs 955))
223 (cons "andalso" (decode-char 'ucs 8896))
224 (cons "orelse" (decode-char 'ucs 8897))
225 ;; (cons "as" (decode-char 'ucs 8801))
226 (cons "not" (decode-char 'ucs 172))
227 (cons "div" (decode-char 'ucs 247))
228 (cons "*" (decode-char 'ucs 215))
229 (cons "o" (decode-char 'ucs 9675))
230 (cons "->" (decode-char 'ucs 8594))
231 (cons "=>" (decode-char 'ucs 8658))
232 (cons "<-" (decode-char 'ucs 8592))
233 (cons "<>" (decode-char 'ucs 8800))
234 (cons ">=" (decode-char 'ucs 8805))
235 (cons "<=" (decode-char 'ucs 8804))
236 (cons "..." (decode-char 'ucs 8943))
237 ;; (cons "::" (decode-char 'ucs 8759))
238 ;; Some greek letters for type parameters.
239 (cons "'a" (decode-char 'ucs 945))
240 (cons "'b" (decode-char 'ucs 946))
241 (cons "'c" (decode-char 'ucs 947))
242 (cons "'d" (decode-char 'ucs 948))
243 ))))
244
245 (defun sml-font-lock-compose-symbol (alist)
246 "Compose a sequence of ascii chars into a symbol.
247 Regexp match data 0 points to the chars."
248 ;; Check that the chars should really be composed into a symbol.
249 (let* ((start (match-beginning 0))
250 (end (match-end 0))
251 (syntaxes (if (eq (char-syntax (char-after start)) ?w)
252 '(?w) '(?. ?\\))))
253 (if (or (memq (char-syntax (or (char-before start) ?\ )) syntaxes)
254 (memq (char-syntax (or (char-after end) ?\ )) syntaxes)
255 (memq (get-text-property start 'face)
256 '(font-lock-doc-face font-lock-string-face
257 font-lock-comment-face)))
258 ;; No composition for you. Let's actually remove any composition
259 ;; we may have added earlier and which is now incorrect.
260 (remove-text-properties start end '(composition))
261 ;; That's a symbol alright, so add the composition.
262 (compose-region start end (cdr (assoc (match-string 0) alist)))))
263 ;; Return nil because we're not adding any face property.
264 nil)
265
266 (defun sml-font-lock-symbols-keywords ()
267 (when (fboundp 'compose-region)
268 (let ((alist nil))
269 (dolist (x sml-font-lock-symbols-alist)
270 (when (and (if (fboundp 'char-displayable-p)
271 (char-displayable-p (cdr x))
272 t)
273 (not (assoc (car x) alist))) ;Not yet in alist.
274 (push x alist)))
275 (when alist
276 `((,(regexp-opt (mapcar 'car alist) t)
277 (0 (sml-font-lock-compose-symbol ',alist))))))))
278
279 ;; The font lock regular expressions.
280
281 (defconst sml-font-lock-keywords
282 `(;;(sml-font-comments-and-strings)
283 (,(concat "\\<\\(fun\\|and\\)\\s-+" sml-tyvarseq-re "\\(\\sw+\\)\\s-+[^ \t\n=]")
284 (1 font-lock-keyword-face)
285 (6 font-lock-function-name-face))
286 (,(concat "\\<\\(\\(data\\|abs\\|with\\|eq\\)?type\\)\\s-+" sml-tyvarseq-re "\\(\\sw+\\)")
287 (1 font-lock-keyword-face)
288 (7 font-lock-type-def-face))
289 ("\\<\\(val\\)\\s-+\\(\\sw+\\>\\s-*\\)?\\(\\sw+\\)\\s-*[=:]"
290 (1 font-lock-keyword-face)
291 ;;(6 font-lock-variable-def-face nil t)
292 (3 font-lock-variable-name-face))
293 ("\\<\\(structure\\|functor\\|abstraction\\)\\s-+\\(\\sw+\\)"
294 (1 font-lock-keyword-face)
295 (2 font-lock-module-def-face))
296 ("\\<\\(signature\\)\\s-+\\(\\sw+\\)"
297 (1 font-lock-keyword-face)
298 (2 font-lock-interface-def-face))
299
300 (,sml-keywords-regexp . font-lock-keyword-face)
301 ,@(sml-font-lock-symbols-keywords))
302 "Regexps matching standard SML keywords.")
303
304 (defface font-lock-type-def-face
305 '((t (:bold t)))
306 "Font Lock mode face used to highlight type definitions."
307 :group 'font-lock-highlighting-faces)
308 (defvar font-lock-type-def-face 'font-lock-type-def-face
309 "Face name to use for type definitions.")
310
311 (defface font-lock-module-def-face
312 '((t (:bold t)))
313 "Font Lock mode face used to highlight module definitions."
314 :group 'font-lock-highlighting-faces)
315 (defvar font-lock-module-def-face 'font-lock-module-def-face
316 "Face name to use for module definitions.")
317
318 (defface font-lock-interface-def-face
319 '((t (:bold t)))
320 "Font Lock mode face used to highlight interface definitions."
321 :group 'font-lock-highlighting-faces)
322 (defvar font-lock-interface-def-face 'font-lock-interface-def-face
323 "Face name to use for interface definitions.")
324
325 ;;
326 ;; Code to handle nested comments and unusual string escape sequences
327 ;;
328
329 (defsyntax sml-syntax-prop-table
330 '((?\\ . ".") (?* . "."))
331 "Syntax table for text-properties")
332
333 ;; For Emacsen that have no built-in support for nested comments
334 (defun sml-get-depth-st ()
335 (save-excursion
336 (let* ((disp (if (eq (char-before) ?\)) (progn (backward-char) -1) nil))
337 (_ (backward-char))
338 (disp (if (eq (char-before) ?\() (progn (backward-char) 0) disp))
339 (pt (point)))
340 (when disp
341 (let* ((depth
342 (save-match-data
343 (if (re-search-backward "\\*)\\|(\\*" nil t)
344 (+ (or (get-char-property (point) 'comment-depth) 0)
345 (case (char-after) (?\( 1) (?* 0))
346 disp)
347 0)))
348 (depth (if (> depth 0) depth)))
349 (put-text-property pt (1+ pt) 'comment-depth depth)
350 (when depth sml-syntax-prop-table))))))
351
352 (defconst sml-font-lock-syntactic-keywords
353 `(("^\\s-*\\(\\\\\\)" (1 ',sml-syntax-prop-table))
354 ,@(unless sml-builtin-nested-comments-flag
355 '(("(?\\(\\*\\))?" (1 (sml-get-depth-st)))))))
356
357 (defconst sml-font-lock-defaults
358 '(sml-font-lock-keywords nil nil ((?_ . "w") (?' . "w")) nil
359 (font-lock-syntactic-keywords . sml-font-lock-syntactic-keywords)))
360
361
362 ;;; Indentation with SMIE
363
364 (defconst sml-smie-op-levels
365 (when (fboundp 'smie-prec2-levels)
366 ;; We have several problem areas where SML's syntax can't be handled by an
367 ;; operator precedence grammar:
368 ;;
369 ;; "= A before B" is "= A) before B" if this is the
370 ;; `boolean-=' but it is "= (A before B)" if it's the `definitional-='.
371 ;; We can work around the problem by tweaking the lexer to return two
372 ;; different tokens for the two different kinds of `='.
373 ;; "of A | B" in a "case" we want "of (A | B, but in a `datatype'
374 ;; we want "of A) | B".
375 ;; "= A | B" can be "= A ) | B" if the = is from a "fun" definition,
376 ;; but it is "= (A | B" if it is a `datatype' definition (of course, if
377 ;; the previous introducing the = is `and', deciding whether
378 ;; it's a datatype or a function requires looking even further back).
379 ;; "functor foo (...) where type a = b = ..." the first `=' looks very much
380 ;; like a `definitional-=' even tho it's just an equality constraint.
381 ;; Currently I don't even try to handle `where' at all.
382 (smie-prec2-levels
383 (smie-merge-prec2s
384 (smie-bnf-precedence-table
385 '((exp ("if" exp "then" exp "else" exp)
386 ("case" exp "of" branches)
387 ("let" decls "in" cmds "end")
388 ("struct" decls "end")
389 ("sig" decls "end")
390 (sexp)
391 (sexp "handle" branches)
392 ("fn" sexp "=>" exp))
393 ;; "simple exp"s are the ones that can appear to the left of `handle'.
394 (sexp (sexp ":" type) ("(" exps ")")
395 (sexp "orelse" sexp)
396 (marg ":>" type)
397 (sexp "andalso" sexp))
398 (cmds (cmds ";" cmds) (exp))
399 (exps (exps "," exps) (exp)) ; (exps ";" exps)
400 (branches (sexp "=>" exp) (branches "|" branches))
401 ;; Operator precedence grammars handle separators much better then
402 ;; starters/terminators, so let's pretend that let/fun are separators.
403 (decls (sexp "d=" exp)
404 (sexp "d=" databranches)
405 (funbranches "|" funbranches)
406 (sexp "=of" type) ;After "exception".
407 ("local" decls "in" decls "end")
408 (decls "functor" decls)
409 (decls "signature" decls)
410 (decls "structure" decls)
411 (decls "type" decls)
412 (decls "open" decls)
413 (decls "and" decls)
414 (decls "infix" decls)
415 (decls "infixr" decls)
416 (decls "nonfix" decls)
417 (decls "abstype" decls)
418 (decls "datatype" decls)
419 (decls "exception" decls)
420 (decls "fun" decls)
421 (decls "val" decls))
422 (type (type "->" type)
423 (type "*" type))
424 (funbranches (sexp "d=" exp))
425 (databranches (sexp "=of" type) (databranches "d|" databranches))
426 ;; Module language.
427 ;; (mexp ("functor" marg "d=" mexp)
428 ;; ("structure" marg "d=" mexp)
429 ;; ("signature" marg "d=" mexp))
430 (marg (marg ":" type) (marg ":>" type))
431 (toplevel (decls) (exp) (toplevel ";" toplevel)))
432 ;; '((nonassoc "else") (right "handle"))
433 '((nonassoc "of") (assoc "|")) ; "case a of b => case c of d => e | f"
434 '((nonassoc "handle") (assoc "|")) ; Idem for "handle".
435 '((assoc "->") (assoc "*"))
436 '((assoc "val" "fun" "type" "datatype" "abstype" "open" "infix" "infixr"
437 "nonfix" "functor" "signature" "structure" "exception")
438 (assoc "and"))
439 '((assoc "orelse") (assoc "andalso") (nonassoc ":"))
440 '((assoc ";")) '((assoc ",")) '((assoc "d|")))
441
442 (smie-precs-precedence-table
443 '((nonassoc "andalso") ;To anchor the prec-table.
444 (assoc "before") ;0
445 (assoc ":=" "o") ;3
446 (nonassoc ">" ">=" "<>" "<" "<=" "=") ;4
447 (assoc "::" "@") ;5
448 (assoc "+" "-" "^") ;6
449 (assoc "/" "*" "quot" "rem" "div" "mod") ;7
450 (nonassoc " -dummy- "))) ;Bogus anchor at the end.
451 ))))
452
453 (defun sml-smie-rules (kind token)
454 (pcase (cons kind token)
455 (`(:after . "struct") 0)
456 (`(:before . "=>") (if (smie-parent-p "fn") 3))
457 (`(:after . "=>") (if (smie-hanging-p) 0 2))
458 (`(:after . "of") 3)
459 (`(:before . "of") 1)
460 ((and `(:before . "|") (guard (smie-prev-p "of")))
461 1) ;; In case the language is extended to allow a | directly after of.
462 (`(,_ . ,(or `"|" `"d|" `";" `","))
463 ;; Shift single-char separators 2 columns left if they appear
464 ;; at the beginning of a line so the content is aligned
465 ;; (assuming exactly one space after the separator is used).
466 (if (and (smie-bolp) (eq kind :before))
467 (if (smie-parent-p "(" "{" "[" "=" "d=" "|" "d|") 0
468 (if (smie-parent-p "of" "in" "fun")
469 ;; FIXME: Adjust to correct nb of spaces.
470 '(+ point -2)))))
471 (`(:after . ,(or `"(" `"{" `"[")) (if (not (smie-hanging-p)) 2))
472 (`(:before . ,(or `"let" `"(" `"[" `"{")) (if (smie-hanging-p) 'parent))
473 (`(:before . "if") (if (smie-prev-p "else") 'parent)) ;'point
474 (`(:before . "fn") (if (smie-prev-p "=>") 'parent)) ;'point
475 (`(:after . "in") (if (smie-parent-p "local") 0))
476 (`(:after . "else") (if (smie-hanging-p) 0)) ;; (:next "if" 0)
477 (`(:before . "and")
478 (cond
479 ((smie-parent-p "datatype") 5) ;=(- (length "datatype") (length "and"))
480 ((smie-parent-p "fun" "val") 0)))
481 ;; (("datatype" . "with") . 4)
482 (`(:before . "d=")
483 (cond
484 ((smie-parent-p "datatype") (if (smie-bolp) 2 nil ;; 'point
485 ))
486 ((smie-parent-p "structure" "signature") 0)))
487 (`(:after . "d=") (if (and (smie-parent-p "val") (smie-next-p "fn")) -3))
488 (`(:list-intro . "fn") t)))
489
490 (defun sml-smie-definitional-equal-p ()
491 "Figure out which kind of \"=\" this is.
492 Assumes point is right before the = sign."
493 ;; The idea is to look backward for the first occurrence of a token that
494 ;; requires a definitional "=" and then see if there's such a definitional
495 ;; equal between that token and ourselves (in which case we're not
496 ;; a definitional = ourselves).
497 ;; The "search for =" is naive and will match "=>" and "<=", but it turns
498 ;; out to be OK in practice because such tokens very rarely (if ever) appear
499 ;; between the =-starter and the corresponding definitional equal.
500 ;; One known problem case is code like:
501 ;; "functor foo (structure s : S) where type t = s.t ="
502 ;; where the "type t = s.t" is mistaken for a type definition.
503 (save-excursion
504 (and (re-search-backward (concat "\\(" sml-=-starter-re "\\)\\|=") nil t)
505 (match-beginning 1))))
506
507 (defun sml-smie-non-nested-of-p ()
508 ;; FIXME: Maybe datatype-|-p makes this nested-of business unnecessary.
509 "Figure out which kind of \"of\" this is.
510 Assumes point is right before the \"of\" symbol."
511 (save-excursion
512 (and (re-search-backward (concat "\\(" sml-non-nested-of-starter-re
513 "\\)\\|\\<case\\>") nil t)
514 (match-beginning 1))))
515
516 (defun sml-smie-datatype-|-p ()
517 "Figure out which kind of \"|\" this is.
518 Assumes point is right before the | symbol."
519 (save-excursion
520 (forward-char 1) ;Skip the |.
521 (sml-smie-forward-token-1) ;Skip the tag.
522 (member (sml-smie-forward-token-1)
523 '("|" "of" "in" "datatype" "and" "exception" "abstype" "infix"
524 "infixr" "nonfix" "local" "val" "fun" "structure" "functor"
525 "signature"))))
526
527 (defun sml-smie-forward-token-1 ()
528 (forward-comment (point-max))
529 (buffer-substring (point)
530 (progn
531 (or (/= 0 (skip-syntax-forward "'w_"))
532 (skip-syntax-forward ".'"))
533 (point))))
534
535 (defun sml-smie-forward-token ()
536 (let ((sym (sml-smie-forward-token-1)))
537 (cond
538 ((equal "op" sym)
539 (concat "op " (sml-smie-forward-token-1)))
540 ((member sym '("|" "of" "="))
541 (save-excursion (sml-smie-backward-token)))
542 (t sym))))
543
544 (defun sml-smie-backward-token-1 ()
545 (forward-comment (- (point)))
546 (buffer-substring (point)
547 (progn
548 (or (/= 0 (skip-syntax-backward ".'"))
549 (skip-syntax-backward "'w_"))
550 (point))))
551
552 (defun sml-smie-backward-token ()
553 (let ((sym (sml-smie-backward-token-1)))
554 (unless (zerop (length sym))
555 ;; FIXME: what should we do if `sym' = "op" ?
556 (let ((point (point)))
557 (if (equal "op" (sml-smie-backward-token-1))
558 (concat "op " sym)
559 (goto-char point)
560 (cond
561 ((string= sym "=") (if (sml-smie-definitional-equal-p) "d=" "="))
562 ((string= sym "of") (if (sml-smie-non-nested-of-p) "=of" "of"))
563 ((string= sym "|") (if (sml-smie-datatype-|-p) "d|" "|"))
564 (t sym)))))))
565
566 ;;;;
567 ;;;; Imenu support
568 ;;;;
569
570 (defvar sml-imenu-regexp
571 (concat "^[ \t]*\\(let[ \t]+\\)?"
572 (regexp-opt (append sml-module-head-syms
573 '("and" "fun" "datatype" "abstype" "type")) t)
574 "\\>"))
575
576 (defun sml-imenu-create-index ()
577 (let (alist)
578 (goto-char (point-max))
579 (while (re-search-backward sml-imenu-regexp nil t)
580 (save-excursion
581 (let ((kind (match-string 2))
582 (column (progn (goto-char (match-beginning 2)) (current-column)))
583 (location
584 (progn (goto-char (match-end 0))
585 (forward-comment (point-max))
586 (when (looking-at sml-tyvarseq-re)
587 (goto-char (match-end 0)))
588 (point)))
589 (name (sml-forward-sym)))
590 ;; Eliminate trivial renamings.
591 (when (or (not (member kind '("structure" "signature")))
592 (progn (search-forward "=")
593 (forward-comment (point-max))
594 (looking-at "sig\\|struct")))
595 (push (cons (concat (make-string (/ column 2) ?\ ) name) location)
596 alist)))))
597 alist))
598
599 ;;; MORE CODE FOR SML-MODE
600
601 ;;;###autoload
602 (add-to-list 'auto-mode-alist '("\\.s\\(ml\\|ig\\)\\'" . sml-mode))
603
604 ;;;###autoload
605 (define-derived-mode sml-mode fundamental-mode "SML"
606 "\\<sml-mode-map>Major mode for editing ML code.
607 This mode runs `sml-mode-hook' just before exiting.
608 \\{sml-mode-map}"
609 (set (make-local-variable 'font-lock-defaults) sml-font-lock-defaults)
610 (set (make-local-variable 'outline-regexp) sml-outline-regexp)
611 (set (make-local-variable 'imenu-create-index-function)
612 'sml-imenu-create-index)
613 (set (make-local-variable 'add-log-current-defun-function)
614 'sml-current-fun-name)
615 ;; Treat paragraph-separators in comments as paragraph-separators.
616 (set (make-local-variable 'paragraph-separate)
617 (concat "\\([ \t]*\\*)?\\)?\\(" paragraph-separate "\\)"))
618 (set (make-local-variable 'require-final-newline) t)
619 ;; For XEmacs
620 (easy-menu-add sml-mode-menu)
621 ;; Compatibility. FIXME: we should use `-' in Emacs-CVS.
622 (unless (boundp 'skeleton-positions) (set (make-local-variable '@) nil))
623 (sml-mode-variables))
624
625 (defun sml-mode-variables ()
626 (set-syntax-table sml-mode-syntax-table)
627 (setq local-abbrev-table sml-mode-abbrev-table)
628 ;; Setup indentation and sexp-navigation.
629 (cond
630 ((and sml-use-smie (fboundp 'smie-setup))
631 (smie-setup sml-smie-op-levels #'sml-smie-rules
632 :backward-token #'sml-smie-backward-token
633 :forward-token #'sml-smie-forward-token))
634 (t
635 ;; forward-sexp-function is an experimental variable in my hacked Emacs.
636 (set (make-local-variable 'forward-sexp-function) 'sml-user-forward-sexp)
637 (set (make-local-variable 'indent-line-function) 'sml-indent-line)))
638 (set (make-local-variable 'parse-sexp-ignore-comments) t)
639 (set (make-local-variable 'comment-start) "(* ")
640 (set (make-local-variable 'comment-end) " *)")
641 (set (make-local-variable 'comment-start-skip) "(\\*+\\s-*")
642 (set (make-local-variable 'comment-end-skip) "\\s-*\\*+)")
643 ;; No need to quote nested comments markers.
644 (set (make-local-variable 'comment-quote-nested) nil))
645
646 (defun sml-funname-of-and ()
647 "Name of the function this `and' defines, or nil if not a function.
648 Point has to be right after the `and' symbol and is not preserved."
649 (forward-comment (point-max))
650 (if (looking-at sml-tyvarseq-re) (goto-char (match-end 0)))
651 (let ((sym (sml-forward-sym)))
652 (forward-comment (point-max))
653 (unless (or (member sym '(nil "d="))
654 (member (sml-forward-sym) '("d=")))
655 sym)))
656
657 (defun sml-electric-pipe ()
658 "Insert a \"|\".
659 Depending on the context insert the name of function, a \"=>\" etc."
660 (interactive)
661 (sml-with-ist
662 (unless (save-excursion (skip-chars-backward "\t ") (bolp)) (insert "\n"))
663 (insert "| ")
664 (let ((text
665 (save-excursion
666 (backward-char 2) ;back over the just inserted "| "
667 (let ((sym (sml-find-matching-starter sml-pipeheads
668 (sml-op-prec "|" 'back))))
669 (sml-forward-sym)
670 (forward-comment (point-max))
671 (cond
672 ((string= sym "|")
673 (let ((f (sml-forward-sym)))
674 (sml-find-forward "\\(=>\\|=\\||\\)\\S.")
675 (cond
676 ((looking-at "|") "") ;probably a datatype
677 ((looking-at "=>") " => ") ;`case', or `fn' or `handle'
678 ((looking-at "=") (concat f " = "))))) ;a function
679 ((string= sym "and")
680 ;; could be a datatype or a function
681 (setq sym (sml-funname-of-and))
682 (if sym (concat sym " = ") ""))
683 ;; trivial cases
684 ((string= sym "fun")
685 (while (and (setq sym (sml-forward-sym))
686 (string-match "^'" sym))
687 (forward-comment (point-max)))
688 (concat sym " = "))
689 ((member sym '("case" "handle" "fn" "of")) " => ")
690 ;;((member sym '("abstype" "datatype")) "")
691 (t ""))))))
692
693 (insert text)
694 (indent-according-to-mode)
695 (beginning-of-line)
696 (skip-chars-forward "\t |")
697 (skip-syntax-forward "w")
698 (skip-chars-forward "\t ")
699 (when (eq ?= (char-after)) (backward-char)))))
700
701 (defun sml-electric-semi ()
702 "Insert a \;.
703 If variable `sml-electric-semi-mode' is t, indent the current line, insert
704 a newline, and indent."
705 (interactive)
706 (insert "\;")
707 (if sml-electric-semi-mode
708 (reindent-then-newline-and-indent)))
709
710 ;;; Misc
711
712 (defun sml-mark-function ()
713 "Synonym for `mark-paragraph' -- sorry.
714 If anyone has a good algorithm for this..."
715 (interactive)
716 (mark-paragraph))
717
718 (defun sml-back-to-outer-indent ()
719 "Unindents to the next outer level of indentation."
720 (interactive)
721 (save-excursion
722 (beginning-of-line)
723 (skip-chars-forward "\t ")
724 (let ((start-column (current-column))
725 (indent (current-column)))
726 (if (> start-column 0)
727 (progn
728 (save-excursion
729 (while (>= indent start-column)
730 (if (re-search-backward "^[^\n]" nil t)
731 (setq indent (current-indentation))
732 (setq indent 0))))
733 (backward-delete-char-untabify (- start-column indent)))))))
734
735 (defun sml-skip-siblings ()
736 (while (and (not (bobp)) (sml-backward-arg))
737 (sml-find-matching-starter sml-starters-syms))
738 (when (looking-at "in\\>\\|local\\>")
739 ;;skip over `local...in' and continue
740 (forward-word 1)
741 (sml-backward-sexp nil)
742 (sml-skip-siblings)))
743
744 (defun sml-beginning-of-defun ()
745 (let ((sym (sml-find-matching-starter sml-starters-syms)))
746 (if (member sym '("fun" "and" "functor" "signature" "structure"
747 "abstraction" "datatype" "abstype"))
748 (save-excursion (sml-forward-sym) (forward-comment (point-max))
749 (sml-forward-sym))
750 ;; We're inside a "non function declaration": let's skip all other
751 ;; declarations that we find at the same level and try again.
752 (sml-skip-siblings)
753 ;; Obviously, let's not try again if we're at bobp.
754 (unless (bobp) (sml-beginning-of-defun)))))
755
756 (defcustom sml-max-name-components 3
757 "Maximum number of components to use for the current function name."
758 :group 'sml
759 :type 'integer)
760
761 (defun sml-current-fun-name ()
762 (save-excursion
763 (let ((count sml-max-name-components)
764 fullname name)
765 (end-of-line)
766 (while (and (> count 0)
767 (setq name (sml-beginning-of-defun)))
768 (decf count)
769 (setq fullname (if fullname (concat name "." fullname) name))
770 ;; Skip all other declarations that we find at the same level.
771 (sml-skip-siblings))
772 fullname)))
773
774
775 ;;; INSERTING PROFORMAS (COMMON SML-FORMS)
776
777 (defvar sml-forms-alist nil
778 "*Alist of code templates.
779 You can extend this alist to your heart's content. For each additional
780 template NAME in the list, declare a keyboard macro or function (or
781 interactive command) called 'sml-form-NAME'.
782 If 'sml-form-NAME' is a function it takes no arguments and should
783 insert the template at point\; if this is a command it may accept any
784 sensible interactive call arguments\; keyboard macros can't take
785 arguments at all. Apropos keyboard macros, see `name-last-kbd-macro'
786 and `sml-addto-forms-alist'.
787 `sml-forms-alist' understands let, local, case, abstype, datatype,
788 signature, structure, and functor by default.")
789
790 (defmacro sml-def-skeleton (name interactor &rest elements)
791 (when (fboundp 'define-skeleton)
792 (let ((fsym (intern (concat "sml-form-" name))))
793 ;; TODO: don't do the expansion in comments and strings.
794 `(progn
795 (add-to-list 'sml-forms-alist ',(cons name fsym))
796 (condition-case err
797 ;; Try to use the new `system' flag.
798 (define-abbrev sml-mode-abbrev-table ,name "" ',fsym nil 'system)
799 (wrong-number-of-arguments
800 (define-abbrev sml-mode-abbrev-table ,name "" ',fsym)))
801 (when (fboundp 'abbrev-put)
802 (let ((abbrev (abbrev-symbol ,name sml-mode-abbrev-table)))
803 (abbrev-put abbrev :case-fixed t)
804 (abbrev-put abbrev :enable-function
805 (lambda () (not (nth 8 (syntax-ppss)))))))
806 (define-skeleton ,fsym
807 ,(format "SML-mode skeleton for `%s..' expressions" name)
808 ,interactor
809 ,(concat name " ") >
810 ,@elements)))))
811 (put 'sml-def-skeleton 'lisp-indent-function 2)
812
813 (sml-def-skeleton "let" nil
814 @ "\nin " > _ "\nend" >)
815
816 (sml-def-skeleton "if" nil
817 @ " then " > _ "\nelse " > _)
818
819 (sml-def-skeleton "local" nil
820 @ "\nin" > _ "\nend" >)
821
822 (sml-def-skeleton "case" "Case expr: "
823 str "\nof " > _ " => ")
824
825 (sml-def-skeleton "signature" "Signature name: "
826 str " =\nsig" > "\n" > _ "\nend" >)
827
828 (sml-def-skeleton "structure" "Structure name: "
829 str " =\nstruct" > "\n" > _ "\nend" >)
830
831 (sml-def-skeleton "functor" "Functor name: "
832 str " () : =\nstruct" > "\n" > _ "\nend" >)
833
834 (sml-def-skeleton "datatype" "Datatype name and type params: "
835 str " =" \n)
836
837 (sml-def-skeleton "abstype" "Abstype name and type params: "
838 str " =" \n _ "\nwith" > "\nend" >)
839
840 ;;
841
842 (sml-def-skeleton "struct" nil
843 _ "\nend" >)
844
845 (sml-def-skeleton "sig" nil
846 _ "\nend" >)
847
848 (sml-def-skeleton "val" nil
849 @ " = " > _)
850
851 (sml-def-skeleton "fn" nil
852 @ " =>" > _)
853
854 (sml-def-skeleton "fun" nil
855 @ " =" > _)
856
857 ;;
858
859 (defun sml-forms-menu (menu)
860 (mapcar (lambda (x) (vector (car x) (cdr x) t))
861 sml-forms-alist))
862
863 (defvar sml-last-form "let")
864
865 (defun sml-electric-space ()
866 "Expand a symbol into an SML form, or just insert a space.
867 If the point directly precedes a symbol for which an SML form exists,
868 the corresponding form is inserted."
869 (interactive)
870 (let ((abbrev-mode (not abbrev-mode))
871 (last-command-event ?\ )
872 ;; Bind `this-command' to fool skeleton's special abbrev handling.
873 (this-command 'self-insert-command))
874 (call-interactively 'self-insert-command)))
875
876 (defun sml-insert-form (name newline)
877 "Interactive short-cut to insert the NAME common ML form.
878 If a prefix argument is given insert a NEWLINE and indent first, or
879 just move to the proper indentation if the line is blank\; otherwise
880 insert at point (which forces indentation to current column).
881
882 The default form to insert is 'whatever you inserted last time'
883 \(just hit return when prompted\)\; otherwise the command reads with
884 completion from `sml-forms-alist'."
885 (interactive
886 (list (completing-read
887 (format "Form to insert: (default %s) " sml-last-form)
888 sml-forms-alist nil t nil)
889 current-prefix-arg))
890 ;; default is whatever the last insert was...
891 (if (string= name "") (setq name sml-last-form) (setq sml-last-form name))
892 (unless (or (not newline)
893 (save-excursion (beginning-of-line) (looking-at "\\s-*$")))
894 (insert "\n"))
895 (unless (/= ?w (char-syntax (preceding-char))) (insert " "))
896 (let ((f (cdr (assoc name sml-forms-alist))))
897 (cond
898 ((commandp f) (command-execute f))
899 (f (funcall f))
900 (t (error "Undefined form: %s" name)))))
901
902 ;; See also macros.el in emacs lisp dir.
903
904 (defun sml-addto-forms-alist (name)
905 "Assign a name to the last keyboard macro defined.
906 Argument NAME is transmogrified to sml-form-NAME which is the symbol
907 actually defined.
908
909 The symbol's function definition becomes the keyboard macro string.
910
911 If that works, NAME is added to `sml-forms-alist' so you'll be able to
912 reinvoke the macro through \\[sml-insert-form]. You might want to save
913 the macro to use in a later editing session -- see `insert-kbd-macro'
914 and add these macros to your .emacs file.
915
916 See also `edit-kbd-macro' which is bound to \\[edit-kbd-macro]."
917 (interactive "sName for last kbd macro (\"sml-form-\" will be added): ")
918 (when (string= name "") (error "No command name given"))
919 (let ((fsym (intern (concat "sml-form-" name))))
920 (name-last-kbd-macro fsym)
921 (message "Macro bound to %s" fsym)
922 (add-to-list 'sml-forms-alist (cons name fsym))))
923
924 ;;;
925 ;;; MLton support
926 ;;;
927
928 (defvar sml-mlton-command "mlton"
929 "Command to run MLton. Can include arguments.")
930
931 (defvar sml-mlton-mainfile nil)
932
933 (defconst sml-mlton-error-regexp-alist
934 ;; I wish they just changed MLton to use one of the standard
935 ;; error formats.
936 `(("^\\(?:Error\\|\\(Warning\\)\\): \\(.+\\) \\([0-9]+\\)\\.\\([0-9]+\\)\\.$"
937 2 3 4
938 ;; If subgroup 1 matched, then it's a warning, otherwise it's an error.
939 ,@(if (fboundp 'compilation-fake-loc) '((1))))))
940
941 (eval-after-load "compile"
942 '(dolist (x sml-mlton-error-regexp-alist)
943 (add-to-list 'compilation-error-regexp-alist x)))
944
945 (defun sml-mlton-typecheck (mainfile)
946 "typecheck using MLton."
947 (interactive
948 (list (if (and mainfile (not current-prefix-arg))
949 mainfile
950 (read-file-name "Main file: "))))
951 (save-some-buffers)
952 (require 'compile)
953 (dolist (x sml-mlton-error-regexp-alist)
954 (add-to-list 'compilation-error-regexp-alist x))
955 (with-current-buffer (find-file-noselect mainfile)
956 (compile (concat sml-mlton-command
957 " -stop tc " ;Stop right after type checking.
958 (shell-quote-argument
959 (file-relative-name buffer-file-name))))))
960
961 ;;;
962 ;;; MLton's def-use info.
963 ;;;
964
965 (defvar sml-defuse-file nil)
966
967 (defun sml-defuse-file ()
968 (or sml-defuse-file (sml-defuse-set-file)))
969
970 (defun sml-defuse-set-file ()
971 "Specify the def-use file to use."
972 (interactive)
973 (setq sml-defuse-file (read-file-name "Def-use file: ")))
974
975 (defun sml-defuse-symdata-at-point ()
976 (save-excursion
977 (sml-forward-sym)
978 (let ((symname (sml-backward-sym)))
979 (if (equal symname "op")
980 (save-excursion (setq symname (sml-forward-sym))))
981 (when (string-match "op " symname)
982 (setq symname (substring symname (match-end 0)))
983 (forward-word)
984 (forward-comment (point-max)))
985 (list symname
986 ;; Def-use files seem to count chars, not columns.
987 ;; We hope here that they don't actually count bytes.
988 ;; Also they seem to start counting at 1.
989 (1+ (- (point) (progn (beginning-of-line) (point))))
990 (save-restriction
991 (widen) (1+ (count-lines (point-min) (point))))
992 buffer-file-name))))
993
994 (defconst sml-defuse-def-regexp
995 "^[[:alpha:]]+ \\([^ \n]+\\) \\(.+\\) \\([0-9]+\\)\\.\\([0-9]+\\)$")
996 (defconst sml-defuse-use-regexp-format "^ %s %d\\.%d $")
997
998 (defun sml-defuse-jump-to-def ()
999 "Jump to the definition corresponding to the symbol at point."
1000 (interactive)
1001 (let ((symdata (sml-defuse-symdata-at-point)))
1002 (if (null (car symdata))
1003 (error "Not on a symbol")
1004 (with-current-buffer (find-file-noselect (sml-defuse-file))
1005 (goto-char (point-min))
1006 (unless (re-search-forward
1007 (format sml-defuse-use-regexp-format
1008 (concat "\\(?:"
1009 ;; May be an absolute file name.
1010 (regexp-quote (nth 3 symdata))
1011 "\\|"
1012 ;; Or a relative file name.
1013 (regexp-quote (file-relative-name
1014 (nth 3 symdata)))
1015 "\\)")
1016 (nth 2 symdata)
1017 (nth 1 symdata))
1018 nil t)
1019 ;; FIXME: This is typically due to editing: any minor editing will
1020 ;; mess everything up. We should try to fail more gracefully.
1021 (error "Def-use info not found"))
1022 (unless (re-search-backward sml-defuse-def-regexp nil t)
1023 ;; This indicates a bug in this code.
1024 (error "Internal failure while looking up def-use"))
1025 (unless (equal (match-string 1) (nth 0 symdata))
1026 ;; FIXME: This again is most likely due to editing.
1027 (error "Incoherence in the def-use info found"))
1028 (let ((line (string-to-number (match-string 3)))
1029 (char (string-to-number (match-string 4))))
1030 (pop-to-buffer (find-file-noselect (match-string 2)))
1031 (goto-char (point-min))
1032 (forward-line (1- line))
1033 (forward-char (1- char)))))))
1034
1035 ;;;
1036 ;;; SML/NJ's Compilation Manager support
1037 ;;;
1038
1039 (defvar sml-cm-mode-syntax-table sml-mode-syntax-table)
1040 (defvar sml-cm-font-lock-keywords
1041 `(,(concat "\\<" (regexp-opt '("library" "group" "is" "structure"
1042 "functor" "signature" "funsig") t)
1043 "\\>")))
1044 ;;;###autoload
1045 (add-to-list 'completion-ignored-extensions ".cm/")
1046 ;; This was used with the old compilation manager.
1047 (add-to-list 'completion-ignored-extensions "CM/")
1048 ;;;###autoload
1049 (add-to-list 'auto-mode-alist '("\\.cm\\'" . sml-cm-mode))
1050 ;;;###autoload
1051 (define-derived-mode sml-cm-mode fundamental-mode "SML-CM"
1052 "Major mode for SML/NJ's Compilation Manager configuration files."
1053 (local-set-key "\C-c\C-c" 'sml-compile)
1054 (set (make-local-variable 'font-lock-defaults)
1055 '(sml-cm-font-lock-keywords nil t nil nil)))
1056
1057 ;;;
1058 ;;; ML-Lex support
1059 ;;;
1060
1061 (defvar sml-lex-font-lock-keywords
1062 (append
1063 '(("^%\\sw+" . font-lock-builtin-face)
1064 ("^%%" . font-lock-module-def-face))
1065 sml-font-lock-keywords))
1066 (defconst sml-lex-font-lock-defaults
1067 (cons 'sml-lex-font-lock-keywords (cdr sml-font-lock-defaults)))
1068
1069 ;;;###autoload
1070 (define-derived-mode sml-lex-mode sml-mode "SML-Lex"
1071 "Major Mode for editing ML-Lex files."
1072 (set (make-local-variable 'font-lock-defaults) sml-lex-font-lock-defaults))
1073
1074 ;;;
1075 ;;; ML-Yacc support
1076 ;;;
1077
1078 (defface sml-yacc-bnf-face
1079 '((t (:foreground "darkgreen")))
1080 "Face used to highlight (non)terminals in `sml-yacc-mode'.")
1081 (defvar sml-yacc-bnf-face 'sml-yacc-bnf-face)
1082
1083 (defcustom sml-yacc-indent-action 16
1084 "Indentation column of the opening paren of actions."
1085 :group 'sml
1086 :type 'integer)
1087
1088 (defcustom sml-yacc-indent-pipe nil
1089 "Indentation column of the pipe char in the BNF.
1090 If nil, align it with `:' or with previous cases."
1091 :group 'sml
1092 :type 'integer)
1093
1094 (defcustom sml-yacc-indent-term nil
1095 "Indentation column of the (non)term part.
1096 If nil, align it with previous cases."
1097 :group 'sml
1098 :type 'integer)
1099
1100 (defvar sml-yacc-font-lock-keywords
1101 (cons '("^\\(\\sw+\\s-*:\\|\\s-*|\\)\\(\\s-*\\sw+\\)*\\s-*\\(\\(%\\sw+\\)\\s-+\\sw+\\|\\)"
1102 (0 (save-excursion
1103 (save-match-data
1104 (goto-char (match-beginning 0))
1105 (unless (or (re-search-forward "\\<of\\>" (match-end 0) 'move)
1106 (progn (forward-comment (point-max))
1107 (not (looking-at "("))))
1108 sml-yacc-bnf-face))))
1109 (4 font-lock-builtin-face t t))
1110 sml-lex-font-lock-keywords))
1111 (defconst sml-yacc-font-lock-defaults
1112 (cons 'sml-yacc-font-lock-keywords (cdr sml-font-lock-defaults)))
1113
1114 (defun sml-yacc-indent-line ()
1115 "Indent current line of ML-Yacc code."
1116 (let ((savep (> (current-column) (current-indentation)))
1117 (indent (max (or (ignore-errors (sml-yacc-indentation)) 0) 0)))
1118 (if savep
1119 (save-excursion (indent-line-to indent))
1120 (indent-line-to indent))))
1121
1122 (defun sml-yacc-indentation ()
1123 (save-excursion
1124 (back-to-indentation)
1125 (or (and (looking-at "%\\|\\(\\sw\\|\\s_\\)+\\s-*:") 0)
1126 (when (save-excursion
1127 (condition-case nil (progn (up-list -1) nil) (scan-error t)))
1128 ;; We're outside an action.
1129 (cond
1130 ;; Special handling of indentation inside %term and %nonterm
1131 ((save-excursion
1132 (and (re-search-backward "^%\\(\\sw+\\)" nil t)
1133 (member (match-string 1) '("term" "nonterm"))))
1134 (if (numberp sml-yacc-indent-term) sml-yacc-indent-term
1135 (let ((offset (if (looking-at "|") -2 0)))
1136 (forward-line -1)
1137 (looking-at "\\s-*\\(%\\sw*\\||\\)?\\s-*")
1138 (goto-char (match-end 0))
1139 (+ offset (current-column)))))
1140 ((looking-at "(") sml-yacc-indent-action)
1141 ((looking-at "|")
1142 (if (numberp sml-yacc-indent-pipe) sml-yacc-indent-pipe
1143 (backward-sexp 1)
1144 (while (progn (forward-comment (- (point)))
1145 (/= 0 (skip-syntax-backward "w_"))))
1146 (forward-comment (- (point)))
1147 (if (not (looking-at "\\s-$"))
1148 (1- (current-column))
1149 (skip-syntax-forward " ")
1150 (- (current-column) 2))))))
1151 ;; default to SML rules
1152 (sml-calculate-indentation))))
1153
1154 ;;;###autoload
1155 (add-to-list 'auto-mode-alist '("\\.grm\\'" . sml-yacc-mode))
1156 ;;;###autoload
1157 (define-derived-mode sml-yacc-mode sml-mode "SML-Yacc"
1158 "Major Mode for editing ML-Yacc files."
1159 (set (make-local-variable 'indent-line-function) 'sml-yacc-indent-line)
1160 (set (make-local-variable 'font-lock-defaults) sml-yacc-font-lock-defaults))
1161
1162 (provide 'sml-mode)
1163
1164 ;;; sml-mode.el ends here