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