]> 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 (require 'smie)
76 (condition-case nil (require 'skeleton) (error nil))
77
78 ;;; VARIABLES CONTROLLING INDENTATION
79
80 (defcustom sml-indent-level 4
81 "Indentation of blocks in ML (see also `sml-indent-rule')."
82 :group 'sml
83 :type '(integer))
84
85 (defcustom sml-indent-args sml-indent-level
86 "*Indentation of args placed on a separate line."
87 :group 'sml
88 :type '(integer))
89
90 ;; (defvar sml-indent-align-args t
91 ;; "*Whether the arguments should be aligned.")
92
93 ;; (defvar sml-case-indent nil
94 ;; "*How to indent case-of expressions.
95 ;; If t: case expr If nil: case expr of
96 ;; of exp1 => ... exp1 => ...
97 ;; | exp2 => ... | exp2 => ...
98
99 ;; The first seems to be the standard in SML/NJ, but the second
100 ;; seems nicer...")
101
102 (defcustom sml-electric-semi-mode nil
103 "*If non-nil, `\;' will self insert, reindent the line, and do a newline.
104 If nil, just insert a `\;'. (To insert while t, do: \\[quoted-insert] \;)."
105 :group 'sml
106 :type 'boolean)
107
108 (defcustom sml-rightalign-and t
109 "If non-nil, right-align `and' with its leader.
110 If nil: If t:
111 datatype a = A datatype a = A
112 and b = B and b = B"
113 :group 'sml
114 :type 'boolean)
115
116 ;;; OTHER GENERIC MODE VARIABLES
117
118 (defvar sml-mode-info "sml-mode"
119 "*Where to find Info file for `sml-mode'.
120 The default assumes the info file \"sml-mode.info\" is on Emacs' info
121 directory path. If it is not, either put the file on the standard path
122 or set the variable `sml-mode-info' to the exact location of this file
123
124 (setq sml-mode-info \"/usr/me/lib/info/sml-mode\")
125
126 in your .emacs file. You can always set it interactively with the
127 set-variable command.")
128
129 (defvar sml-mode-hook nil
130 "*Run upon entering `sml-mode'.
131 This is a good place to put your preferred key bindings.")
132
133 ;;; CODE FOR SML-MODE
134
135 (defun sml-mode-info ()
136 "Command to access the TeXinfo documentation for `sml-mode'.
137 See doc for the variable `sml-mode-info'."
138 (interactive)
139 (require 'info)
140 (condition-case nil
141 (info sml-mode-info)
142 (error (progn
143 (describe-variable 'sml-mode-info)
144 (message "Can't find it... set this variable first!")))))
145
146
147 ;;; Autoload functions -- no-doc is another idea cribbed from AucTeX!
148
149 (let ((sml-no-doc
150 "This function is part of sml-proc, and has not yet been loaded.
151 Full documentation will be available after autoloading the function."))
152
153 (autoload 'sml-compile "sml-proc" sml-no-doc t)
154 (autoload 'sml-load-file "sml-proc" sml-no-doc t)
155 (autoload 'switch-to-sml "sml-proc" sml-no-doc t)
156 (autoload 'sml-send-region "sml-proc" sml-no-doc t)
157 (autoload 'sml-send-buffer "sml-proc" sml-no-doc t))
158
159 ;; font-lock setup
160
161 (defconst sml-keywords-regexp
162 (sml-syms-re '("abstraction" "abstype" "and" "andalso" "as" "before" "case"
163 "datatype" "else" "end" "eqtype" "exception" "do" "fn"
164 "fun" "functor" "handle" "if" "in" "include" "infix"
165 "infixr" "let" "local" "nonfix" "of" "op" "open" "orelse"
166 "overload" "raise" "rec" "sharing" "sig" "signature"
167 "struct" "structure" "then" "type" "val" "where" "while"
168 "with" "withtype" "o"))
169 "A regexp that matches any and all keywords of SML.")
170
171 (defconst sml-tyvarseq-re
172 "\\(\\('+\\(\\sw\\|\\s_\\)+\\|(\\([,']\\|\\sw\\|\\s_\\|\\s-\\)+)\\)\\s-+\\)?")
173
174 ;;; Font-lock settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
175
176 (defcustom sml-font-lock-symbols nil
177 "Display \\ and -> and such using symbols in fonts.
178 This may sound like a neat trick, but be extra careful: it changes the
179 alignment and can thus lead to nasty surprises w.r.t layout.
180 If t, try to use whichever font is available. Otherwise you can
181 set it to a particular font of your preference among `japanese-jisx0208'
182 and `unicode'."
183 :type '(choice (const nil)
184 (const t)
185 (const unicode)
186 (const japanese-jisx0208)))
187
188 (defconst sml-font-lock-symbols-alist
189 (append
190 ;; The symbols can come from a JIS0208 font.
191 (and (fboundp 'make-char) (charsetp 'japanese-jisx0208)
192 (memq sml-font-lock-symbols '(t japanese-jisx0208))
193 (list (cons "fn" (make-char 'japanese-jisx0208 38 75))
194 (cons "andalso" (make-char 'japanese-jisx0208 34 74))
195 (cons "orelse" (make-char 'japanese-jisx0208 34 75))
196 ;; (cons "as" (make-char 'japanese-jisx0208 34 97))
197 (cons "not" (make-char 'japanese-jisx0208 34 76))
198 (cons "div" (make-char 'japanese-jisx0208 33 96))
199 ;; (cons "*" (make-char 'japanese-jisx0208 33 95))
200 (cons "->" (make-char 'japanese-jisx0208 34 42))
201 (cons "=>" (make-char 'japanese-jisx0208 34 77))
202 (cons "<-" (make-char 'japanese-jisx0208 34 43))
203 (cons "<>" (make-char 'japanese-jisx0208 33 98))
204 (cons ">=" (make-char 'japanese-jisx0208 33 102))
205 (cons "<=" (make-char 'japanese-jisx0208 33 101))
206 (cons "..." (make-char 'japanese-jisx0208 33 68))
207 ;; Some greek letters for type parameters.
208 (cons "'a" (make-char 'japanese-jisx0208 38 65))
209 (cons "'b" (make-char 'japanese-jisx0208 38 66))
210 (cons "'c" (make-char 'japanese-jisx0208 38 67))
211 (cons "'d" (make-char 'japanese-jisx0208 38 68))
212 ))
213 ;; Or a unicode font.
214 (and (fboundp 'decode-char)
215 (memq sml-font-lock-symbols '(t unicode))
216 (list (cons "fn" (decode-char 'ucs 955))
217 (cons "andalso" (decode-char 'ucs 8896))
218 (cons "orelse" (decode-char 'ucs 8897))
219 ;; (cons "as" (decode-char 'ucs 8801))
220 (cons "not" (decode-char 'ucs 172))
221 (cons "div" (decode-char 'ucs 247))
222 (cons "*" (decode-char 'ucs 215))
223 (cons "o" (decode-char 'ucs 9675))
224 (cons "->" (decode-char 'ucs 8594))
225 (cons "=>" (decode-char 'ucs 8658))
226 (cons "<-" (decode-char 'ucs 8592))
227 (cons "<>" (decode-char 'ucs 8800))
228 (cons ">=" (decode-char 'ucs 8805))
229 (cons "<=" (decode-char 'ucs 8804))
230 (cons "..." (decode-char 'ucs 8943))
231 ;; (cons "::" (decode-char 'ucs 8759))
232 ;; Some greek letters for type parameters.
233 (cons "'a" (decode-char 'ucs 945))
234 (cons "'b" (decode-char 'ucs 946))
235 (cons "'c" (decode-char 'ucs 947))
236 (cons "'d" (decode-char 'ucs 948))
237 ))))
238
239 (defun sml-font-lock-compose-symbol (alist)
240 "Compose a sequence of ascii chars into a symbol.
241 Regexp match data 0 points to the chars."
242 ;; Check that the chars should really be composed into a symbol.
243 (let* ((start (match-beginning 0))
244 (end (match-end 0))
245 (syntaxes (if (eq (char-syntax (char-after start)) ?w)
246 '(?w) '(?. ?\\))))
247 (if (or (memq (char-syntax (or (char-before start) ?\ )) syntaxes)
248 (memq (char-syntax (or (char-after end) ?\ )) syntaxes)
249 (memq (get-text-property start 'face)
250 '(font-lock-doc-face font-lock-string-face
251 font-lock-comment-face)))
252 ;; No composition for you. Let's actually remove any composition
253 ;; we may have added earlier and which is now incorrect.
254 (remove-text-properties start end '(composition))
255 ;; That's a symbol alright, so add the composition.
256 (compose-region start end (cdr (assoc (match-string 0) alist)))))
257 ;; Return nil because we're not adding any face property.
258 nil)
259
260 (defun sml-font-lock-symbols-keywords ()
261 (when (fboundp 'compose-region)
262 (let ((alist nil))
263 (dolist (x sml-font-lock-symbols-alist)
264 (when (and (if (fboundp 'char-displayable-p)
265 (char-displayable-p (cdr x))
266 t)
267 (not (assoc (car x) alist))) ;Not yet in alist.
268 (push x alist)))
269 (when alist
270 `((,(regexp-opt (mapcar 'car alist) t)
271 (0 (sml-font-lock-compose-symbol ',alist))))))))
272
273 ;; The font lock regular expressions.
274
275 (defconst sml-font-lock-keywords
276 `(;;(sml-font-comments-and-strings)
277 (,(concat "\\<\\(fun\\|and\\)\\s-+" sml-tyvarseq-re "\\(\\sw+\\)\\s-+[^ \t\n=]")
278 (1 font-lock-keyword-face)
279 (6 font-lock-function-name-face))
280 (,(concat "\\<\\(\\(data\\|abs\\|with\\|eq\\)?type\\)\\s-+" sml-tyvarseq-re "\\(\\sw+\\)")
281 (1 font-lock-keyword-face)
282 (7 font-lock-type-def-face))
283 ("\\<\\(val\\)\\s-+\\(\\sw+\\>\\s-*\\)?\\(\\sw+\\)\\s-*[=:]"
284 (1 font-lock-keyword-face)
285 ;;(6 font-lock-variable-def-face nil t)
286 (3 font-lock-variable-name-face))
287 ("\\<\\(structure\\|functor\\|abstraction\\)\\s-+\\(\\sw+\\)"
288 (1 font-lock-keyword-face)
289 (2 font-lock-module-def-face))
290 ("\\<\\(signature\\)\\s-+\\(\\sw+\\)"
291 (1 font-lock-keyword-face)
292 (2 font-lock-interface-def-face))
293
294 (,sml-keywords-regexp . font-lock-keyword-face)
295 ,@(sml-font-lock-symbols-keywords))
296 "Regexps matching standard SML keywords.")
297
298 (defface font-lock-type-def-face
299 '((t (:bold t)))
300 "Font Lock mode face used to highlight type definitions."
301 :group 'font-lock-highlighting-faces)
302 (defvar font-lock-type-def-face 'font-lock-type-def-face
303 "Face name to use for type definitions.")
304
305 (defface font-lock-module-def-face
306 '((t (:bold t)))
307 "Font Lock mode face used to highlight module definitions."
308 :group 'font-lock-highlighting-faces)
309 (defvar font-lock-module-def-face 'font-lock-module-def-face
310 "Face name to use for module definitions.")
311
312 (defface font-lock-interface-def-face
313 '((t (:bold t)))
314 "Font Lock mode face used to highlight interface definitions."
315 :group 'font-lock-highlighting-faces)
316 (defvar font-lock-interface-def-face 'font-lock-interface-def-face
317 "Face name to use for interface definitions.")
318
319 ;;
320 ;; Code to handle nested comments and unusual string escape sequences
321 ;;
322
323 (defsyntax sml-syntax-prop-table
324 '((?\\ . ".") (?* . "."))
325 "Syntax table for text-properties")
326
327 ;; For Emacsen that have no built-in support for nested comments
328 (defun sml-get-depth-st ()
329 (save-excursion
330 (let* ((disp (if (eq (char-before) ?\)) (progn (backward-char) -1) nil))
331 (_ (backward-char))
332 (disp (if (eq (char-before) ?\() (progn (backward-char) 0) disp))
333 (pt (point)))
334 (when disp
335 (let* ((depth
336 (save-match-data
337 (if (re-search-backward "\\*)\\|(\\*" nil t)
338 (+ (or (get-char-property (point) 'comment-depth) 0)
339 (case (char-after) (?\( 1) (?* 0))
340 disp)
341 0)))
342 (depth (if (> depth 0) depth)))
343 (put-text-property pt (1+ pt) 'comment-depth depth)
344 (when depth sml-syntax-prop-table))))))
345
346 (defconst sml-font-lock-syntactic-keywords
347 `(("^\\s-*\\(\\\\\\)" (1 ',sml-syntax-prop-table))
348 ,@(unless sml-builtin-nested-comments-flag
349 '(("(?\\(\\*\\))?" (1 (sml-get-depth-st)))))))
350
351 (defconst sml-font-lock-defaults
352 '(sml-font-lock-keywords nil nil ((?_ . "w") (?' . "w")) nil
353 (font-lock-syntactic-keywords . sml-font-lock-syntactic-keywords)))
354
355
356 ;;; Indentation with SMIE
357
358 (defconst sml-smie-op-levels
359 ;; We have 3 problem areas where SML's syntax can't be handled by an
360 ;; operator precedence grammar:
361 ;;
362 ;; "= A before B" is "= A) before B" if this is the
363 ;; boolean "=" but it is "= (A before B)" if it's the definitional "=".
364 ;; We can work around the problem by tweaking the lexer to return two
365 ;; different tokens for the two different kinds of "=".
366 ;; "of A | B" in a "case" we want "of (A | B, but in a datatype
367 ;; we want "of A) | B".
368 ;; "= A | B" can be "= A ) | B" if the = is from a "fun" definition,
369 ;; but it is "= (A | B" if it is a "datatype" definition (of course, if
370 ;; the previous introducing the = is "and", deciding whether
371 ;; it's a datatype or a function requires looking even further back).
372 (smie-prec2-levels
373 (smie-merge-prec2s
374 (smie-bnf-precedence-table
375 '((exp ("if" exp "then" exp "else" exp)
376 ("case" exp "of" branches)
377 ("let" decls "in" cmds "end")
378 (sexp)
379 (sexp "handle" branches)
380 ("fn" sexp "=>" exp))
381 (sexp (sexp ":" type) ("(" exps ")")
382 (sexp "orelse" sexp)
383 (sexp "andalso" sexp))
384 (cmds (cmds ";" cmds) (exp))
385 (exps (exps "," exps) (exp)) ; (exps ";" exps)
386 (branches (sexp "=>" exp) (branches "|" branches))
387 ;; Operator precedence grammars handle separators much better then
388 ;; starters/terminators, so let's pretend that let/fun are separators.
389 (decls (sexp "d=" exp)
390 (sexp "d=" databranches)
391 ("exception" sexp "=of" type)
392 ("local" decls "in" decls "end")
393 (decls "type" decls)
394 (decls "open" decls)
395 (decls "and" decls)
396 (decls "infix" decls)
397 (decls "infixr" decls)
398 (decls "nonfix" decls)
399 (decls "abstype" decls)
400 (decls "datatype" decls)
401 (decls "fun" decls)
402 (decls "val" decls))
403 (type (type "->" type)
404 (type "*" type))
405 (databranches (sexp "=of" type) (databranches "d|" databranches))
406 ;; Module language.
407 (mexp ("functor" marg "d=" mexp)
408 ("structure" marg "d=" mexp)
409 ("signature" marg "d=" mexp)
410 ("struct" decls "end")
411 ("sig" decls "end"))
412 (marg (marg ":" type) (marg ":>" type)))
413 ;; '((nonassoc "else") (right "handle"))
414 '((nonassoc "of") (assoc "|")) ; "case a of b => case c of d => e | f"
415 '((nonassoc "handle") (assoc "|")) ; Idem for "handle".
416 '((assoc "->") (assoc "*"))
417 '((assoc "val" "fun" "type" "datatype" "abstype" "open" "infix" "infixr"
418 "nonfix")
419 (assoc "and"))
420 '((assoc "orelse") (assoc "andalso") (nonassoc ":"))
421 '((assoc ";")) '((assoc ",")) '((assoc "d|")))
422
423 (smie-precs-precedence-table
424 '((nonassoc "andalso") ;To anchor the prec-table.
425 (assoc "before") ;0
426 (assoc ":=" "o") ;3
427 (nonassoc ">" ">=" "<>" "<" "<=" "=") ;4
428 (assoc "::" "@") ;5
429 (assoc "+" "-" "^") ;6
430 (assoc "/" "*" "quot" "rem" "div" "mod") ;7
431 (nonassoc " "))) ;Bogus anchor at the end.
432 )))
433
434 (defconst sml-smie-indent-rules
435 '(
436 ("of" 3)
437 ("struct" 0)
438 ((t . "of") . 1)
439 ((t . "|") . -2)
440 (("datatype" . "and") . 5)
441 )
442 )
443
444 (defun sml-smie-poly-equal-p ()
445 "Figure out which kind of \"=\" this is.
446 Assumes point is right before the = sign."
447 ;; The idea is to look backward for the first occurrence of a token that
448 ;; requires a definitional "=" and then see if there's such a definitional
449 ;; equal between that token and ourselves (in which case we're not
450 ;; a definitional = ourselves).
451 ;; The "search for =" is naive and will match "=>" and "<=", but it turns
452 ;; out to be OK in practice because such tokens very rarely (if ever) appear
453 ;; between the =-starter and the corresponding definitional equal.
454 ;; One known problem case is code like:
455 ;; "functor foo (structure s : S) where type t = s.t ="
456 ;; where the "type t = s.t" is mistaken for a type definition.
457 (let ((pos (point)))
458 (prog1
459 (and (re-search-backward sml-=-starter-re nil t)
460 (re-search-forward "=" pos t))
461 (goto-char pos))))
462
463 (defun sml-smie-nested-of-p ()
464 "Figure out which kind of \"of\" this is.
465 Assumes point is right before the \"of\" symbol."
466 (let ((pos (point)))
467 (prog1 (and (re-search-backward sml-non-nested-of-starter-re nil t)
468 (re-search-forward "\\<case\\>" pos t))
469 (goto-char pos))))
470
471 (defun sml-smie-datatype-|-p ()
472 "Figure out which kind of \"|\" this is.
473 Assumes point is right before the | symbol."
474 (save-excursion
475 (forward-char 1) ;Skip the |.
476 (sml-smie-forward-token-1) ;Skip the tag.
477 (member (sml-smie-forward-token-1)
478 '("|" "of" "in" "datatype" "and" "exception" "abstype" "infix"
479 "infixr" "nonfix" "local" "val" "fun" "structure" "functor"
480 "signature"))))
481
482 (defun sml-smie-forward-token-1 ()
483 (forward-comment (point-max))
484 (buffer-substring (point)
485 (progn
486 (or (/= 0 (skip-syntax-forward "'w_"))
487 (/= 0 (skip-syntax-forward ".'")))
488 (point))))
489
490 (defun sml-smie-forward-token ()
491 (let ((sym (sml-smie-forward-token-1)))
492 (cond
493 ((equal "op" sym)
494 (concat "op " (sml-smie-forward-token-1)))
495 ((member sym '("|" "of" "="))
496 (save-excursion (sml-smie-backward-token)))
497 (t sym))))
498
499 (defun sml-smie-backward-token-1 ()
500 (forward-comment (- (point)))
501 (buffer-substring (point)
502 (progn
503 (or (/= 0 (skip-syntax-backward ".'"))
504 (/= 0 (skip-syntax-backward "'w_")))
505 (point))))
506
507 (defun sml-smie-backward-token ()
508 (let ((sym (sml-smie-backward-token-1)))
509 (unless (zerop (length sym))
510 ;; FIXME: what should we do if `sym' = "op" ?
511 (let ((point (point)))
512 (if (equal "op" (sml-smie-backward-token-1))
513 (concat "op " sym)
514 (goto-char point)
515 (cond
516 ((string= sym "=") (if (sml-smie-poly-equal-p) "=" "d="))
517 ((string= sym "of") (if (sml-smie-nested-of-p) "of" "=of"))
518 ((string= sym "|") (if (sml-smie-datatype-|-p) "d|" "|"))
519 (t sym)))))))
520
521 ;;;;
522 ;;;; Imenu support
523 ;;;;
524
525 (defvar sml-imenu-regexp
526 (concat "^[ \t]*\\(let[ \t]+\\)?"
527 (regexp-opt (append sml-module-head-syms
528 '("and" "fun" "datatype" "abstype" "type")) t)
529 "\\>"))
530
531 (defun sml-imenu-create-index ()
532 (let (alist)
533 (goto-char (point-max))
534 (while (re-search-backward sml-imenu-regexp nil t)
535 (save-excursion
536 (let ((kind (match-string 2))
537 (column (progn (goto-char (match-beginning 2)) (current-column)))
538 (location
539 (progn (goto-char (match-end 0))
540 (sml-forward-spaces)
541 (when (looking-at sml-tyvarseq-re)
542 (goto-char (match-end 0)))
543 (point)))
544 (name (sml-forward-sym)))
545 ;; Eliminate trivial renamings.
546 (when (or (not (member kind '("structure" "signature")))
547 (progn (search-forward "=")
548 (sml-forward-spaces)
549 (looking-at "sig\\|struct")))
550 (push (cons (concat (make-string (/ column 2) ?\ ) name) location)
551 alist)))))
552 alist))
553
554 ;;; MORE CODE FOR SML-MODE
555
556 ;;;###autoload
557 (add-to-list 'auto-mode-alist '("\\.s\\(ml\\|ig\\)\\'" . sml-mode))
558
559 ;;;###autoload
560 (define-derived-mode sml-mode fundamental-mode "SML"
561 "\\<sml-mode-map>Major mode for editing ML code.
562 This mode runs `sml-mode-hook' just before exiting.
563 \\{sml-mode-map}"
564 (set (make-local-variable 'font-lock-defaults) sml-font-lock-defaults)
565 (set (make-local-variable 'outline-regexp) sml-outline-regexp)
566 (set (make-local-variable 'imenu-create-index-function)
567 'sml-imenu-create-index)
568 (set (make-local-variable 'add-log-current-defun-function)
569 'sml-current-fun-name)
570 ;; Treat paragraph-separators in comments as paragraph-separators.
571 (set (make-local-variable 'paragraph-separate)
572 (concat "\\([ \t]*\\*)?\\)?\\(" paragraph-separate "\\)"))
573 (set (make-local-variable 'require-final-newline) t)
574 (set (make-local-variable 'forward-sexp-function) 'smie-forward-sexp-command)
575 ;; For XEmacs
576 (easy-menu-add sml-mode-menu)
577 ;; Compatibility. FIXME: we should use `-' in Emacs-CVS.
578 (unless (boundp 'skeleton-positions) (set (make-local-variable '@) nil))
579 (sml-mode-variables))
580
581 (defun sml-mode-variables ()
582 (set-syntax-table sml-mode-syntax-table)
583 (setq local-abbrev-table sml-mode-abbrev-table)
584 (smie-setup sml-smie-op-levels sml-smie-indent-rules)
585 (set (make-local-variable 'smie-backward-token-function)
586 'sml-smie-backward-token)
587 (set (make-local-variable 'smie-forward-token-function)
588 'sml-smie-forward-token)
589 (set (make-local-variable 'comment-start) "(* ")
590 (set (make-local-variable 'comment-end) " *)")
591 (set (make-local-variable 'comment-start-skip) "(\\*+\\s-*")
592 (set (make-local-variable 'comment-end-skip) "\\s-*\\*+)")
593 ;; No need to quote nested comments markers.
594 (set (make-local-variable 'comment-quote-nested) nil))
595
596 (defun sml-funname-of-and ()
597 "Name of the function this `and' defines, or nil if not a function.
598 Point has to be right after the `and' symbol and is not preserved."
599 (sml-forward-spaces)
600 (if (looking-at sml-tyvarseq-re) (goto-char (match-end 0)))
601 (let ((sym (sml-forward-sym)))
602 (sml-forward-spaces)
603 (unless (or (member sym '(nil "d="))
604 (member (sml-forward-sym) '("d=")))
605 sym)))
606
607 (defun sml-electric-pipe ()
608 "Insert a \"|\".
609 Depending on the context insert the name of function, a \"=>\" etc."
610 (interactive)
611 (sml-with-ist
612 (unless (save-excursion (skip-chars-backward "\t ") (bolp)) (insert "\n"))
613 (insert "| ")
614 (let ((text
615 (save-excursion
616 (backward-char 2) ;back over the just inserted "| "
617 (let ((sym (sml-find-matching-starter sml-pipeheads
618 (sml-op-prec "|" 'back))))
619 (sml-forward-sym)
620 (sml-forward-spaces)
621 (cond
622 ((string= sym "|")
623 (let ((f (sml-forward-sym)))
624 (sml-find-forward "\\(=>\\|=\\||\\)\\S.")
625 (cond
626 ((looking-at "|") "") ;probably a datatype
627 ((looking-at "=>") " => ") ;`case', or `fn' or `handle'
628 ((looking-at "=") (concat f " = "))))) ;a function
629 ((string= sym "and")
630 ;; could be a datatype or a function
631 (setq sym (sml-funname-of-and))
632 (if sym (concat sym " = ") ""))
633 ;; trivial cases
634 ((string= sym "fun")
635 (while (and (setq sym (sml-forward-sym))
636 (string-match "^'" sym))
637 (sml-forward-spaces))
638 (concat sym " = "))
639 ((member sym '("case" "handle" "fn" "of")) " => ")
640 ;;((member sym '("abstype" "datatype")) "")
641 (t ""))))))
642
643 (insert text)
644 (indent-according-to-mode)
645 (beginning-of-line)
646 (skip-chars-forward "\t |")
647 (skip-syntax-forward "w")
648 (skip-chars-forward "\t ")
649 (when (eq ?= (char-after)) (backward-char)))))
650
651 (defun sml-electric-semi ()
652 "Insert a \;.
653 If variable `sml-electric-semi-mode' is t, indent the current line, insert
654 a newline, and indent."
655 (interactive)
656 (insert "\;")
657 (if sml-electric-semi-mode
658 (reindent-then-newline-and-indent)))
659
660 ;;; INDENTATION !!!
661
662 (defun sml-mark-function ()
663 "Synonym for `mark-paragraph' -- sorry.
664 If anyone has a good algorithm for this..."
665 (interactive)
666 (mark-paragraph))
667
668 (defun sml-indent-line ()
669 "Indent current line of ML code."
670 (interactive)
671 (let ((savep (> (current-column) (current-indentation)))
672 (indent (max (or (ignore-errors (sml-calculate-indentation)) 0) 0)))
673 (if savep
674 (save-excursion (indent-line-to indent))
675 (indent-line-to indent))))
676
677 (defun sml-back-to-outer-indent ()
678 "Unindents to the next outer level of indentation."
679 (interactive)
680 (save-excursion
681 (beginning-of-line)
682 (skip-chars-forward "\t ")
683 (let ((start-column (current-column))
684 (indent (current-column)))
685 (if (> start-column 0)
686 (progn
687 (save-excursion
688 (while (>= indent start-column)
689 (if (re-search-backward "^[^\n]" nil t)
690 (setq indent (current-indentation))
691 (setq indent 0))))
692 (backward-delete-char-untabify (- start-column indent)))))))
693
694 (defun sml-find-comment-indent ()
695 (save-excursion
696 (let ((depth 1))
697 (while (> depth 0)
698 (if (re-search-backward "(\\*\\|\\*)" nil t)
699 (cond
700 ;; FIXME: That's just a stop-gap.
701 ((eq (get-text-property (point) 'face) 'font-lock-string-face))
702 ((looking-at "*)") (incf depth))
703 ((looking-at comment-start-skip) (decf depth)))
704 (setq depth -1)))
705 (if (= depth 0)
706 (1+ (current-column))
707 nil))))
708
709 (defun sml-calculate-indentation ()
710 (save-excursion
711 (beginning-of-line) (skip-chars-forward "\t ")
712 (sml-with-ist
713 ;; Indentation for comments alone on a line, matches the
714 ;; proper indentation of the next line.
715 (when (looking-at "(\\*") (sml-forward-spaces))
716 (let (data
717 (sym (save-excursion (sml-forward-sym))))
718 (or
719 ;; Allow the user to override the indentation.
720 (when (looking-at (concat ".*" (regexp-quote comment-start)
721 "[ \t]*fixindent[ \t]*"
722 (regexp-quote comment-end)))
723 (current-indentation))
724
725 ;; Continued comment.
726 (and (looking-at "\\*") (sml-find-comment-indent))
727
728 ;; Continued string ? (Added 890113 lbn)
729 (and (looking-at "\\\\")
730 (or (save-excursion (forward-line -1)
731 (if (looking-at "[\t ]*\\\\")
732 (current-indentation)))
733 (save-excursion
734 (if (re-search-backward "[^\\\\]\"" nil t)
735 (1+ (current-column))
736 0))))
737
738 ;; Closing parens. Could be handled below with `sml-indent-relative'?
739 (and (looking-at "\\s)")
740 (save-excursion
741 (skip-syntax-forward ")")
742 (backward-sexp 1)
743 (if (sml-dangling-sym)
744 (sml-indent-default 'noindent)
745 (current-column))))
746
747 (and (setq data (assoc sym sml-close-paren))
748 (sml-indent-relative sym data))
749
750 (and (member sym sml-starters-syms)
751 (sml-indent-starter sym))
752
753 (and (string= sym "|") (sml-indent-pipe))
754
755 (sml-indent-arg)
756 (sml-indent-default))))))
757
758 (defsubst sml-bolp ()
759 (save-excursion (skip-chars-backward " \t|") (bolp)))
760
761 (defun sml-first-starter-p ()
762 "Non-nil if starter at point is immediately preceded by let/local/in/..."
763 (save-excursion
764 (let ((sym (unless (save-excursion (sml-backward-arg))
765 (sml-backward-spaces)
766 (sml-backward-sym))))
767 (if (member sym '(";" "d=")) (setq sym nil))
768 sym)))
769
770
771 (defun sml-indent-starter (orig-sym)
772 "Return the indentation to use for a symbol in `sml-starters-syms'.
773 Point should be just before the symbol ORIG-SYM and is not preserved."
774 (let ((sym (unless (save-excursion (sml-backward-arg))
775 (sml-backward-spaces)
776 (sml-backward-sym))))
777 (if (member sym '(";" "d=")) (setq sym nil))
778 (if sym (sml-get-sym-indent sym)
779 ;; FIXME: this can take a *long* time !!
780 (setq sym (sml-find-matching-starter sml-starters-syms))
781 (if (or (sml-first-starter-p)
782 ;; Don't align with `and' because it might be specially indented.
783 (and (or (equal orig-sym "and") (not (equal sym "and")))
784 (sml-bolp)))
785 (+ (current-column)
786 (if (and sml-rightalign-and (equal orig-sym "and"))
787 (- (length sym) 3) 0))
788 (sml-indent-starter orig-sym)))))
789
790 (defun sml-indent-relative (sym data)
791 (save-excursion
792 (sml-forward-sym) (sml-backward-sexp nil)
793 (unless (second data) (sml-backward-spaces) (sml-backward-sym))
794 (+ (or (cdr (assoc sym sml-symbol-indent)) 0)
795 (sml-delegated-indent))))
796
797 (defun sml-indent-pipe ()
798 (let ((sym (sml-find-matching-starter sml-pipeheads
799 (sml-op-prec "|" 'back))))
800 (when sym
801 (if (string= sym "|")
802 (if (sml-bolp) (current-column) (sml-indent-pipe))
803 (let ((pipe-indent (or (cdr (assoc "|" sml-symbol-indent)) -2)))
804 (when (or (member sym '("datatype" "abstype"))
805 (and (equal sym "and")
806 (save-excursion
807 (forward-word 1)
808 (not (sml-funname-of-and)))))
809 (re-search-forward "="))
810 (sml-forward-sym)
811 (sml-forward-spaces)
812 (+ pipe-indent (current-column)))))))
813
814 (defun sml-find-forward (re)
815 (sml-forward-spaces)
816 (while (and (not (looking-at re))
817 (progn
818 (or (ignore-errors (forward-sexp 1) t) (forward-char 1))
819 (sml-forward-spaces)
820 (not (looking-at re))))))
821
822 (defun sml-indent-arg ()
823 (and (save-excursion (ignore-errors (sml-forward-arg)))
824 ;;(not (looking-at sml-not-arg-re))
825 ;; looks like a function or an argument
826 (sml-move-if (sml-backward-arg))
827 ;; an argument
828 (if (save-excursion (not (sml-backward-arg)))
829 ;; a first argument
830 (+ (current-column) sml-indent-args)
831 ;; not a first arg
832 (while (and (/= (current-column) (current-indentation))
833 (sml-move-if (sml-backward-arg))))
834 (unless (save-excursion (sml-backward-arg))
835 ;; all earlier args are on the same line
836 (sml-forward-arg) (sml-forward-spaces))
837 (current-column))))
838
839 (defun sml-get-indent (data sym)
840 (let (d)
841 (cond
842 ((not (listp data)) data)
843 ((setq d (member sym data)) (cadr d))
844 ((and (consp data) (not (stringp (car data)))) (car data))
845 (t sml-indent-level))))
846
847 (defun sml-dangling-sym ()
848 "Non-nil if the symbol after point is dangling.
849 The symbol can be an SML symbol or an open-paren. \"Dangling\" means that
850 it is not on its own line but is the last element on that line."
851 (save-excursion
852 (and (not (sml-bolp))
853 (< (sml-point-after (end-of-line))
854 (sml-point-after (or (sml-forward-sym) (skip-syntax-forward "("))
855 (sml-forward-spaces))))))
856
857 (defun sml-delegated-indent ()
858 (if (sml-dangling-sym)
859 (sml-indent-default 'noindent)
860 (sml-move-if (backward-word 1)
861 (looking-at sml-agglomerate-re))
862 (current-column)))
863
864 (defun sml-get-sym-indent (sym &optional style)
865 "Find the indentation for the SYM we're `looking-at'.
866 If indentation is delegated, point will move to the start of the parent.
867 Optional argument STYLE is currently ignored."
868 (assert (equal sym (save-excursion (sml-forward-sym))))
869 (save-excursion
870 (let ((delegate (and (not (equal sym "end")) (assoc sym sml-close-paren)))
871 (head-sym sym))
872 (when (and delegate (not (eval (third delegate))))
873 ;;(sml-find-match-backward sym delegate)
874 (sml-forward-sym) (sml-backward-sexp nil)
875 (setq head-sym
876 (if (second delegate)
877 (save-excursion (sml-forward-sym))
878 (sml-backward-spaces) (sml-backward-sym))))
879
880 (let ((idata (assoc head-sym sml-indent-rule)))
881 (when idata
882 ;;(if (or style (not delegate))
883 ;; normal indentation
884 (let ((indent (sml-get-indent (cdr idata) sym)))
885 (when indent (+ (sml-delegated-indent) indent)))
886 ;; delgate indentation to the parent
887 ;;(sml-forward-sym) (sml-backward-sexp nil)
888 ;;(let* ((parent-sym (save-excursion (sml-forward-sym)))
889 ;; (parent-indent (cdr (assoc parent-sym sml-indent-starters))))
890 ;; check the special rules
891 ;;(+ (sml-delegated-indent)
892 ;; (or (sml-get-indent (cdr indent-data) 1 'strict)
893 ;; (sml-get-indent (cdr parent-indent) 1 'strict)
894 ;; (sml-get-indent (cdr indent-data) 0)
895 ;; (sml-get-indent (cdr parent-indent) 0))))))))
896 )))))
897
898 (defun sml-indent-default (&optional noindent)
899 (let* ((sym-after (save-excursion (sml-forward-sym)))
900 (_ (sml-backward-spaces))
901 (sym-before (sml-backward-sym))
902 (sym-indent (and sym-before (sml-get-sym-indent sym-before)))
903 (indent-after (or (cdr (assoc sym-after sml-symbol-indent)) 0)))
904 (when (equal sym-before "end")
905 ;; I don't understand what's really happening here, but when
906 ;; it's `end' clearly, we need to do something special.
907 (forward-word 1)
908 (setq sym-before nil sym-indent nil))
909 (cond
910 (sym-indent
911 ;; the previous sym is an indentation introducer: follow the rule
912 (if noindent
913 ;;(current-column)
914 sym-indent
915 (+ sym-indent indent-after)))
916 ;; If we're just after a hanging open paren.
917 ((and (eq (char-syntax (preceding-char)) ?\()
918 (save-excursion (backward-char) (sml-dangling-sym)))
919 (backward-char)
920 (sml-indent-default))
921 (t
922 ;; default-default
923 (let* ((prec-after (sml-op-prec sym-after 'back))
924 (prec (or (sml-op-prec sym-before 'back) prec-after 100)))
925 ;; go back until you hit a symbol that has a lower prec than the
926 ;; "current one", or until you backed over a sym that has the same prec
927 ;; but is at the beginning of a line.
928 (while (and (not (sml-bolp))
929 (while (sml-move-if (sml-backward-sexp (1- prec))))
930 (not (sml-bolp)))
931 (while (sml-move-if (sml-backward-sexp prec))))
932 (if noindent
933 ;; the `noindent' case does back over an introductory symbol
934 ;; such as `fun', ...
935 (progn
936 (sml-move-if
937 (sml-backward-spaces)
938 (member (sml-backward-sym) sml-starters-syms))
939 (current-column))
940 ;; Use `indent-after' for cases such as when , or ; should be
941 ;; outdented so that their following terms are aligned.
942 (+ (if (progn
943 (if (equal sym-after ";")
944 (sml-move-if
945 (sml-backward-spaces)
946 (member (sml-backward-sym) sml-starters-syms)))
947 (and sym-after (not (looking-at sym-after))))
948 indent-after 0)
949 (current-column))))))))
950
951
952 ;; maybe `|' should be set to word-syntax in our temp syntax table ?
953 (defun sml-current-indentation ()
954 (save-excursion
955 (beginning-of-line)
956 (skip-chars-forward " \t|")
957 (current-column)))
958
959
960 (defun sml-find-matching-starter (syms &optional prec)
961 (let (sym)
962 (ignore-errors
963 (while
964 (progn (sml-backward-sexp prec)
965 (setq sym (save-excursion (sml-forward-sym)))
966 (not (or (member sym syms) (bobp)))))
967 (if (member sym syms) sym))))
968
969 (defun sml-skip-siblings ()
970 (while (and (not (bobp)) (sml-backward-arg))
971 (sml-find-matching-starter sml-starters-syms))
972 (when (looking-at "in\\>\\|local\\>")
973 ;;skip over `local...in' and continue
974 (forward-word 1)
975 (sml-backward-sexp nil)
976 (sml-skip-siblings)))
977
978 (defun sml-beginning-of-defun ()
979 (let ((sym (sml-find-matching-starter sml-starters-syms)))
980 (if (member sym '("fun" "and" "functor" "signature" "structure"
981 "abstraction" "datatype" "abstype"))
982 (save-excursion (sml-forward-sym) (sml-forward-spaces)
983 (sml-forward-sym))
984 ;; We're inside a "non function declaration": let's skip all other
985 ;; declarations that we find at the same level and try again.
986 (sml-skip-siblings)
987 ;; Obviously, let's not try again if we're at bobp.
988 (unless (bobp) (sml-beginning-of-defun)))))
989
990 (defcustom sml-max-name-components 3
991 "Maximum number of components to use for the current function name."
992 :group 'sml
993 :type 'integer)
994
995 (defun sml-current-fun-name ()
996 (save-excursion
997 (let ((count sml-max-name-components)
998 fullname name)
999 (end-of-line)
1000 (while (and (> count 0)
1001 (setq name (sml-beginning-of-defun)))
1002 (decf count)
1003 (setq fullname (if fullname (concat name "." fullname) name))
1004 ;; Skip all other declarations that we find at the same level.
1005 (sml-skip-siblings))
1006 fullname)))
1007
1008
1009 ;;; INSERTING PROFORMAS (COMMON SML-FORMS)
1010
1011 (defvar sml-forms-alist nil
1012 "*Alist of code templates.
1013 You can extend this alist to your heart's content. For each additional
1014 template NAME in the list, declare a keyboard macro or function (or
1015 interactive command) called 'sml-form-NAME'.
1016 If 'sml-form-NAME' is a function it takes no arguments and should
1017 insert the template at point\; if this is a command it may accept any
1018 sensible interactive call arguments\; keyboard macros can't take
1019 arguments at all. Apropos keyboard macros, see `name-last-kbd-macro'
1020 and `sml-addto-forms-alist'.
1021 `sml-forms-alist' understands let, local, case, abstype, datatype,
1022 signature, structure, and functor by default.")
1023
1024 (defmacro sml-def-skeleton (name interactor &rest elements)
1025 (when (fboundp 'define-skeleton)
1026 (let ((fsym (intern (concat "sml-form-" name))))
1027 ;; TODO: don't do the expansion in comments and strings.
1028 `(progn
1029 (add-to-list 'sml-forms-alist ',(cons name fsym))
1030 (condition-case err
1031 ;; Try to use the new `system' flag.
1032 (define-abbrev sml-mode-abbrev-table ,name "" ',fsym nil 'system)
1033 (wrong-number-of-arguments
1034 (define-abbrev sml-mode-abbrev-table ,name "" ',fsym)))
1035 (when (fboundp 'abbrev-put)
1036 (let ((abbrev (abbrev-symbol ,name sml-mode-abbrev-table)))
1037 (abbrev-put abbrev :case-fixed t)
1038 (abbrev-put abbrev :enable-function
1039 (lambda () (not (nth 8 (syntax-ppss)))))))
1040 (define-skeleton ,fsym
1041 ,(format "SML-mode skeleton for `%s..' expressions" name)
1042 ,interactor
1043 ,(concat name " ") >
1044 ,@elements)))))
1045 (put 'sml-def-skeleton 'lisp-indent-function 2)
1046
1047 (sml-def-skeleton "let" nil
1048 @ "\nin " > _ "\nend" >)
1049
1050 (sml-def-skeleton "if" nil
1051 @ " then " > _ "\nelse " > _)
1052
1053 (sml-def-skeleton "local" nil
1054 @ "\nin" > _ "\nend" >)
1055
1056 (sml-def-skeleton "case" "Case expr: "
1057 str "\nof " > _ " => ")
1058
1059 (sml-def-skeleton "signature" "Signature name: "
1060 str " =\nsig" > "\n" > _ "\nend" >)
1061
1062 (sml-def-skeleton "structure" "Structure name: "
1063 str " =\nstruct" > "\n" > _ "\nend" >)
1064
1065 (sml-def-skeleton "functor" "Functor name: "
1066 str " () : =\nstruct" > "\n" > _ "\nend" >)
1067
1068 (sml-def-skeleton "datatype" "Datatype name and type params: "
1069 str " =" \n)
1070
1071 (sml-def-skeleton "abstype" "Abstype name and type params: "
1072 str " =" \n _ "\nwith" > "\nend" >)
1073
1074 ;;
1075
1076 (sml-def-skeleton "struct" nil
1077 _ "\nend" >)
1078
1079 (sml-def-skeleton "sig" nil
1080 _ "\nend" >)
1081
1082 (sml-def-skeleton "val" nil
1083 @ " = " > _)
1084
1085 (sml-def-skeleton "fn" nil
1086 @ " =>" > _)
1087
1088 (sml-def-skeleton "fun" nil
1089 @ " =" > _)
1090
1091 ;;
1092
1093 (defun sml-forms-menu (menu)
1094 (mapcar (lambda (x) (vector (car x) (cdr x) t))
1095 sml-forms-alist))
1096
1097 (defvar sml-last-form "let")
1098
1099 (defun sml-electric-space ()
1100 "Expand a symbol into an SML form, or just insert a space.
1101 If the point directly precedes a symbol for which an SML form exists,
1102 the corresponding form is inserted."
1103 (interactive)
1104 (let ((abbrev-mode (not abbrev-mode))
1105 (last-command-char ?\ )
1106 ;; Bind `this-command' to fool skeleton's special abbrev handling.
1107 (this-command 'self-insert-command))
1108 (call-interactively 'self-insert-command)))
1109
1110 (defun sml-insert-form (name newline)
1111 "Interactive short-cut to insert the NAME common ML form.
1112 If a prefix argument is given insert a NEWLINE and indent first, or
1113 just move to the proper indentation if the line is blank\; otherwise
1114 insert at point (which forces indentation to current column).
1115
1116 The default form to insert is 'whatever you inserted last time'
1117 \(just hit return when prompted\)\; otherwise the command reads with
1118 completion from `sml-forms-alist'."
1119 (interactive
1120 (list (completing-read
1121 (format "Form to insert: (default %s) " sml-last-form)
1122 sml-forms-alist nil t nil)
1123 current-prefix-arg))
1124 ;; default is whatever the last insert was...
1125 (if (string= name "") (setq name sml-last-form) (setq sml-last-form name))
1126 (unless (or (not newline)
1127 (save-excursion (beginning-of-line) (looking-at "\\s-*$")))
1128 (insert "\n"))
1129 (unless (/= ?w (char-syntax (preceding-char))) (insert " "))
1130 (let ((f (cdr (assoc name sml-forms-alist))))
1131 (cond
1132 ((commandp f) (command-execute f))
1133 (f (funcall f))
1134 (t (error "Undefined form: %s" name)))))
1135
1136 ;; See also macros.el in emacs lisp dir.
1137
1138 (defun sml-addto-forms-alist (name)
1139 "Assign a name to the last keyboard macro defined.
1140 Argument NAME is transmogrified to sml-form-NAME which is the symbol
1141 actually defined.
1142
1143 The symbol's function definition becomes the keyboard macro string.
1144
1145 If that works, NAME is added to `sml-forms-alist' so you'll be able to
1146 reinvoke the macro through \\[sml-insert-form]. You might want to save
1147 the macro to use in a later editing session -- see `insert-kbd-macro'
1148 and add these macros to your .emacs file.
1149
1150 See also `edit-kbd-macro' which is bound to \\[edit-kbd-macro]."
1151 (interactive "sName for last kbd macro (\"sml-form-\" will be added): ")
1152 (when (string= name "") (error "No command name given"))
1153 (let ((fsym (intern (concat "sml-form-" name))))
1154 (name-last-kbd-macro fsym)
1155 (message "Macro bound to %s" fsym)
1156 (add-to-list 'sml-forms-alist (cons name fsym))))
1157
1158 ;;;
1159 ;;; MLton support
1160 ;;;
1161
1162 (defvar sml-mlton-command "mlton"
1163 "Command to run MLton. Can include arguments.")
1164
1165 (defvar sml-mlton-mainfile nil)
1166
1167 (defconst sml-mlton-error-regexp-alist
1168 ;; I wish they just changed MLton to use one of the standard
1169 ;; error formats.
1170 `(("^\\(?:Error\\|\\(Warning\\)\\): \\(.+\\) \\([0-9]+\\)\\.\\([0-9]+\\)\\.$"
1171 2 3 4
1172 ;; If subgroup 1 matched, then it's a warning, otherwise it's an error.
1173 ,@(if (fboundp 'compilation-fake-loc) '((1))))))
1174
1175 (eval-after-load "compile"
1176 '(dolist (x sml-mlton-error-regexp-alist)
1177 (add-to-list 'compilation-error-regexp-alist x)))
1178
1179 (defun sml-mlton-typecheck (mainfile)
1180 "typecheck using MLton."
1181 (interactive
1182 (list (if (and mainfile (not current-prefix-arg))
1183 mainfile
1184 (read-file-name "Main file: "))))
1185 (save-some-buffers)
1186 (require 'compile)
1187 (dolist (x sml-mlton-error-regexp-alist)
1188 (add-to-list 'compilation-error-regexp-alist x))
1189 (with-current-buffer (find-file-noselect mainfile)
1190 (compile (concat sml-mlton-command
1191 " -stop tc " ;Stop right after type checking.
1192 (shell-quote-argument
1193 (file-relative-name buffer-file-name))))))
1194
1195 ;;;
1196 ;;; MLton's def-use info.
1197 ;;;
1198
1199 (defvar sml-defuse-file nil)
1200
1201 (defun sml-defuse-file ()
1202 (or sml-defuse-file (sml-defuse-set-file)))
1203
1204 (defun sml-defuse-set-file ()
1205 "Specify the def-use file to use."
1206 (interactive)
1207 (setq sml-defuse-file (read-file-name "Def-use file: ")))
1208
1209 (defun sml-defuse-symdata-at-point ()
1210 (save-excursion
1211 (sml-forward-sym)
1212 (let ((symname (sml-backward-sym)))
1213 (if (equal symname "op")
1214 (save-excursion (setq symname (sml-forward-sym))))
1215 (when (string-match "op " symname)
1216 (setq symname (substring symname (match-end 0)))
1217 (forward-word)
1218 (sml-forward-spaces))
1219 (list symname
1220 ;; Def-use files seem to count chars, not columns.
1221 ;; We hope here that they don't actually count bytes.
1222 ;; Also they seem to start counting at 1.
1223 (1+ (- (point) (progn (beginning-of-line) (point))))
1224 (save-restriction
1225 (widen) (1+ (count-lines (point-min) (point))))
1226 buffer-file-name))))
1227
1228 (defconst sml-defuse-def-regexp
1229 "^[[:alpha:]]+ \\([^ \n]+\\) \\(.+\\) \\([0-9]+\\)\\.\\([0-9]+\\)$")
1230 (defconst sml-defuse-use-regexp-format "^ %s %d\\.%d $")
1231
1232 (defun sml-defuse-jump-to-def ()
1233 "Jump to the definition corresponding to the symbol at point."
1234 (interactive)
1235 (let ((symdata (sml-defuse-symdata-at-point)))
1236 (if (null (car symdata))
1237 (error "Not on a symbol")
1238 (with-current-buffer (find-file-noselect (sml-defuse-file))
1239 (goto-char (point-min))
1240 (unless (re-search-forward
1241 (format sml-defuse-use-regexp-format
1242 (concat "\\(?:"
1243 ;; May be an absolute file name.
1244 (regexp-quote (nth 3 symdata))
1245 "\\|"
1246 ;; Or a relative file name.
1247 (regexp-quote (file-relative-name
1248 (nth 3 symdata)))
1249 "\\)")
1250 (nth 2 symdata)
1251 (nth 1 symdata))
1252 nil t)
1253 ;; FIXME: This is typically due to editing: any minor editing will
1254 ;; mess everything up. We should try to fail more gracefully.
1255 (error "Def-use info not found"))
1256 (unless (re-search-backward sml-defuse-def-regexp nil t)
1257 ;; This indicates a bug in this code.
1258 (error "Internal failure while looking up def-use"))
1259 (unless (equal (match-string 1) (nth 0 symdata))
1260 ;; FIXME: This again is most likely due to editing.
1261 (error "Incoherence in the def-use info found"))
1262 (let ((line (string-to-number (match-string 3)))
1263 (char (string-to-number (match-string 4))))
1264 (pop-to-buffer (find-file-noselect (match-string 2)))
1265 (goto-line line)
1266 (forward-char (1- char)))))))
1267
1268 ;;;
1269 ;;; SML/NJ's Compilation Manager support
1270 ;;;
1271
1272 (defvar sml-cm-mode-syntax-table sml-mode-syntax-table)
1273 (defvar sml-cm-font-lock-keywords
1274 `(,(concat "\\<" (regexp-opt '("library" "group" "is" "structure"
1275 "functor" "signature" "funsig") t)
1276 "\\>")))
1277 ;;;###autoload
1278 (add-to-list 'completion-ignored-extensions ".cm/")
1279 ;; This was used with the old compilation manager.
1280 (add-to-list 'completion-ignored-extensions "CM/")
1281 ;;;###autoload
1282 (add-to-list 'auto-mode-alist '("\\.cm\\'" . sml-cm-mode))
1283 ;;;###autoload
1284 (define-derived-mode sml-cm-mode fundamental-mode "SML-CM"
1285 "Major mode for SML/NJ's Compilation Manager configuration files."
1286 (local-set-key "\C-c\C-c" 'sml-compile)
1287 (set (make-local-variable 'font-lock-defaults)
1288 '(sml-cm-font-lock-keywords nil t nil nil)))
1289
1290 ;;;
1291 ;;; ML-Lex support
1292 ;;;
1293
1294 (defvar sml-lex-font-lock-keywords
1295 (append
1296 '(("^%\\sw+" . font-lock-builtin-face)
1297 ("^%%" . font-lock-module-def-face))
1298 sml-font-lock-keywords))
1299 (defconst sml-lex-font-lock-defaults
1300 (cons 'sml-lex-font-lock-keywords (cdr sml-font-lock-defaults)))
1301
1302 ;;;###autoload
1303 (define-derived-mode sml-lex-mode sml-mode "SML-Lex"
1304 "Major Mode for editing ML-Lex files."
1305 (set (make-local-variable 'font-lock-defaults) sml-lex-font-lock-defaults))
1306
1307 ;;;
1308 ;;; ML-Yacc support
1309 ;;;
1310
1311 (defface sml-yacc-bnf-face
1312 '((t (:foreground "darkgreen")))
1313 "Face used to highlight (non)terminals in `sml-yacc-mode'.")
1314 (defvar sml-yacc-bnf-face 'sml-yacc-bnf-face)
1315
1316 (defcustom sml-yacc-indent-action 16
1317 "Indentation column of the opening paren of actions."
1318 :group 'sml
1319 :type 'integer)
1320
1321 (defcustom sml-yacc-indent-pipe nil
1322 "Indentation column of the pipe char in the BNF.
1323 If nil, align it with `:' or with previous cases."
1324 :group 'sml
1325 :type 'integer)
1326
1327 (defcustom sml-yacc-indent-term nil
1328 "Indentation column of the (non)term part.
1329 If nil, align it with previous cases."
1330 :group 'sml
1331 :type 'integer)
1332
1333 (defvar sml-yacc-font-lock-keywords
1334 (cons '("^\\(\\sw+\\s-*:\\|\\s-*|\\)\\(\\s-*\\sw+\\)*\\s-*\\(\\(%\\sw+\\)\\s-+\\sw+\\|\\)"
1335 (0 (save-excursion
1336 (save-match-data
1337 (goto-char (match-beginning 0))
1338 (unless (or (re-search-forward "\\<of\\>" (match-end 0) 'move)
1339 (progn (sml-forward-spaces)
1340 (not (looking-at "("))))
1341 sml-yacc-bnf-face))))
1342 (4 font-lock-builtin-face t t))
1343 sml-lex-font-lock-keywords))
1344 (defconst sml-yacc-font-lock-defaults
1345 (cons 'sml-yacc-font-lock-keywords (cdr sml-font-lock-defaults)))
1346
1347 (defun sml-yacc-indent-line ()
1348 "Indent current line of ML-Yacc code."
1349 (let ((savep (> (current-column) (current-indentation)))
1350 (indent (max (or (ignore-errors (sml-yacc-indentation)) 0) 0)))
1351 (if savep
1352 (save-excursion (indent-line-to indent))
1353 (indent-line-to indent))))
1354
1355 (defun sml-yacc-indentation ()
1356 (save-excursion
1357 (back-to-indentation)
1358 (or (and (looking-at "%\\|\\(\\sw\\|\\s_\\)+\\s-*:") 0)
1359 (when (save-excursion
1360 (condition-case nil (progn (up-list -1) nil) (scan-error t)))
1361 ;; We're outside an action.
1362 (cond
1363 ;; Special handling of indentation inside %term and %nonterm
1364 ((save-excursion
1365 (and (re-search-backward "^%\\(\\sw+\\)" nil t)
1366 (member (match-string 1) '("term" "nonterm"))))
1367 (if (numberp sml-yacc-indent-term) sml-yacc-indent-term
1368 (let ((offset (if (looking-at "|") -2 0)))
1369 (forward-line -1)
1370 (looking-at "\\s-*\\(%\\sw*\\||\\)?\\s-*")
1371 (goto-char (match-end 0))
1372 (+ offset (current-column)))))
1373 ((looking-at "(") sml-yacc-indent-action)
1374 ((looking-at "|")
1375 (if (numberp sml-yacc-indent-pipe) sml-yacc-indent-pipe
1376 (backward-sexp 1)
1377 (while (progn (sml-backward-spaces)
1378 (/= 0 (skip-syntax-backward "w_"))))
1379 (sml-backward-spaces)
1380 (if (not (looking-at "\\s-$"))
1381 (1- (current-column))
1382 (skip-syntax-forward " ")
1383 (- (current-column) 2))))))
1384 ;; default to SML rules
1385 (sml-calculate-indentation))))
1386
1387 ;;;###autoload
1388 (add-to-list 'auto-mode-alist '("\\.grm\\'" . sml-yacc-mode))
1389 ;;;###autoload
1390 (define-derived-mode sml-yacc-mode sml-mode "SML-Yacc"
1391 "Major Mode for editing ML-Yacc files."
1392 (set (make-local-variable 'indent-line-function) 'sml-yacc-indent-line)
1393 (set (make-local-variable 'font-lock-defaults) sml-yacc-font-lock-defaults))
1394
1395 (provide 'sml-mode)
1396
1397 ;;; sml-mode.el ends here