]> code.delx.au - gnu-emacs-elpa/blob - sml-mode.el
aa68af8758015c7a7fded6517b1013f61e6baeb0
[gnu-emacs-elpa] / sml-mode.el
1 ;;; sml-mode.el --- Major mode for editing (Standard) ML
2
3 ;; Copyright (C) 1989 Lars Bo Nielsen
4 ;; Copyright (C) 1994-1997 Matthew J. Morley
5 ;; Copyright (C) 1999-2000 Stefan Monnier
6
7 ;; $Revision$
8 ;; $Date$
9
10 ;; This file is not part of GNU Emacs, but it is distributed under the
11 ;; same conditions.
12
13 ;; ====================================================================
14
15 ;; This program is free software; you can redistribute it and/or
16 ;; modify it under the terms of the GNU General Public License as
17 ;; published by the Free Software Foundation; either version 2, or (at
18 ;; your option) any later version.
19
20 ;; This program is distributed in the hope that it will be useful, but
21 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 ;; General Public License for more details.
24
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with GNU Emacs; see the file COPYING. If not, write to the
27 ;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
28
29 ;; ====================================================================
30
31
32 ;;; Commentary:
33 ;;
34
35 ;;; HISTORY
36
37 ;; Still under construction: History obscure, needs a biographer as
38 ;; well as a M-x doctor. Change Log on request.
39
40 ;; Hacked by Olin Shivers for comint from Lars Bo Nielsen's sml.el.
41
42 ;; Hacked by Matthew Morley to incorporate Fritz Knabe's hilite and
43 ;; font-lock patterns, some of Steven Gilmore's (reduced) easy-menus,
44 ;; and numerous bugs and bug-fixes.
45
46 ;; Author: Lars Bo Nielsen
47 ;; Olin Shivers
48 ;; Fritz Knabe (?)
49 ;; Steven Gilmore (?)
50 ;; Matthew Morley <mjm@scs.leeds.ac.uk> (aka <matthew@verisity.com>)
51 ;; Matthias Blume <blume@cs.princeton.edu> (aka <blume@kurims.kyoto-u.ac.jp>)
52 ;; (Stefan Monnier) monnier@cs.yale.edu
53 ;; Maintainer: (Stefan Monnier) monnier+lists/emacs/sml@tequila.cs.yale.edu
54 ;; Keywords: SML
55
56 ;;; DESCRIPTION
57
58 ;; See accompanying info file: sml-mode.info
59
60 ;;; FOR YOUR .EMACS FILE
61
62 ;; If sml-mode.el lives in some non-standard directory, you must tell
63 ;; emacs where to get it. This may or may not be necessary:
64
65 ;; (setq load-path (cons (expand-file-name "~jones/lib/emacs") load-path))
66
67 ;; Then to access the commands autoload sml-mode with that command:
68
69 ;; (autoload 'sml-mode "sml-mode" "Major mode for editing ML programs." t)
70 ;;
71 ;; If files ending in ".sml" or ".ML" are hereafter considered to contain
72 ;; Standard ML source, put their buffers into sml-mode automatically:
73
74 ;; (setq auto-mode-alist
75 ;; (cons '(("\\.sml$" . sml-mode)
76 ;; ("\\.ML$" . sml-mode)) auto-mode-alist))
77
78 ;; Here's an example of setting things up in the sml-mode-hook:
79
80 ;; (setq sml-mode-hook
81 ;; '(lambda() "ML mode hacks"
82 ;; (setq sml-indent-level 2 ; conserve on horiz. space
83 ;; indent-tabs-mode nil))) ; whatever
84
85 ;; sml-mode-hook is run whenever a new sml-mode buffer is created.
86
87 ;; Finally, there are inferior-sml-{mode,load}-hooks -- see comments
88 ;; in sml-proc.el. For much more information consult the mode's *info*
89 ;; tree.
90
91 ;;; Code:
92
93 (eval-when-compile (require 'cl))
94 (require 'sml-util)
95 (require 'sml-move)
96 (require 'sml-defs)
97
98 ;;; VARIABLES CONTROLLING INDENTATION
99
100 (defcustom sml-indent-level 4
101 "*Indentation of blocks in ML (see also `sml-structure-indent')."
102 :group 'sml
103 :type '(integer))
104
105 (defcustom sml-indent-args sml-indent-level
106 "*Indentation of args placed on a separate line."
107 :group 'sml
108 :type '(integer))
109
110 ;; (defvar sml-indent-align-args t
111 ;; "*Whether the arguments should be aligned.")
112
113 ;; (defvar sml-case-indent nil
114 ;; "*How to indent case-of expressions.
115 ;; If t: case expr If nil: case expr of
116 ;; of exp1 => ... exp1 => ...
117 ;; | exp2 => ... | exp2 => ...
118
119 ;; The first seems to be the standard in SML/NJ, but the second
120 ;; seems nicer...")
121
122 (defcustom sml-electric-semi-mode nil
123 "*If non-nil, `\;' will self insert, reindent the line, and do a newline.
124 If nil, just insert a `\;'. (To insert while t, do: \\[quoted-insert] \;)."
125 :group 'sml
126 :type '(boolean))
127
128 ;;; OTHER GENERIC MODE VARIABLES
129
130 (defvar sml-mode-info "sml-mode"
131 "*Where to find Info file for `sml-mode'.
132 The default assumes the info file \"sml-mode.info\" is on Emacs' info
133 directory path. If it is not, either put the file on the standard path
134 or set the variable `sml-mode-info' to the exact location of this file
135
136 (setq sml-mode-info \"/usr/me/lib/info/sml-mode\")
137
138 in your .emacs file. You can always set it interactively with the
139 set-variable command.")
140
141 (defvar sml-mode-hook nil
142 "*Run upon entering `sml-mode'.
143 This is a good place to put your preferred key bindings.")
144
145 ;;; CODE FOR SML-MODE
146
147 (defun sml-mode-info ()
148 "Command to access the TeXinfo documentation for `sml-mode'.
149 See doc for the variable `sml-mode-info'."
150 (interactive)
151 (require 'info)
152 (condition-case nil
153 (info sml-mode-info)
154 (error (progn
155 (describe-variable 'sml-mode-info)
156 (message "Can't find it... set this variable first!")))))
157
158
159 ;;; Autoload functions -- no-doc is another idea cribbed from AucTeX!
160
161 (let ((sml-no-doc
162 "This function is part of sml-proc, and has not yet been loaded.
163 Full documentation will be available after autoloading the function."))
164
165 (autoload 'run-sml "sml-proc" sml-no-doc t)
166 (autoload 'sml-compile "sml-proc" sml-no-doc t)
167 (autoload 'sml-load-file "sml-proc" sml-no-doc t)
168 (autoload 'switch-to-sml "sml-proc" sml-no-doc t)
169 (autoload 'sml-send-region "sml-proc" sml-no-doc t)
170 (autoload 'sml-send-buffer "sml-proc" sml-no-doc t))
171
172 ;; font-lock setup
173
174 (defconst sml-keywords-regexp
175 (sml-syms-re "abstraction" "abstype" "and" "andalso" "as" "before" "case"
176 "datatype" "else" "end" "eqtype" "exception" "do" "fn"
177 "fun" "functor" "handle" "if" "in" "include" "infix"
178 "infixr" "let" "local" "nonfix" "of" "op" "open" "orelse"
179 "overload" "raise" "rec" "sharing" "sig" "signature"
180 "struct" "structure" "then" "type" "val" "where" "while"
181 "with" "withtype" "o")
182 "A regexp that matches any and all keywords of SML.")
183
184 (defconst sml-font-lock-keywords
185 `(;;(sml-font-comments-and-strings)
186 ("\\<\\(fun\\|and\\)\\s-+\\('\\sw+\\s-+\\)*\\(\\sw+\\)"
187 (1 font-lock-keyword-face)
188 (3 font-lock-function-name-face))
189 ("\\<\\(\\(data\\|abs\\|with\\|eq\\)?type\\)\\s-+\\('\\sw+\\s-+\\)*\\(\\sw+\\)"
190 (1 font-lock-keyword-face)
191 (4 font-lock-type-def-face))
192 ("\\<\\(val\\)\\s-+\\(\\sw+\\>\\s-*\\)?\\(\\sw+\\)\\s-*[=:]"
193 (1 font-lock-keyword-face)
194 ;;(6 font-lock-variable-def-face nil t)
195 (3 font-lock-variable-name-face))
196 ("\\<\\(structure\\|functor\\|abstraction\\)\\s-+\\(\\sw+\\)"
197 (1 font-lock-keyword-face)
198 (2 font-lock-module-def-face))
199 ("\\<\\(signature\\)\\s-+\\(\\sw+\\)"
200 (1 font-lock-keyword-face)
201 (2 font-lock-interface-def-face))
202
203 (,sml-keywords-regexp . font-lock-keyword-face))
204 "Regexps matching standard SML keywords.")
205
206 (defface font-lock-type-def-face
207 '((t (:bold t)))
208 "Font Lock mode face used to highlight type definitions."
209 :group 'font-lock-highlighting-faces)
210 (defvar font-lock-type-def-face 'font-lock-type-def-face
211 "Face name to use for type definitions.")
212
213 (defface font-lock-module-def-face
214 '((t (:bold t)))
215 "Font Lock mode face used to highlight module definitions."
216 :group 'font-lock-highlighting-faces)
217 (defvar font-lock-module-def-face 'font-lock-module-def-face
218 "Face name to use for module definitions.")
219
220 (defface font-lock-interface-def-face
221 '((t (:bold t)))
222 "Font Lock mode face used to highlight interface definitions."
223 :group 'font-lock-highlighting-faces)
224 (defvar font-lock-interface-def-face 'font-lock-interface-def-face
225 "Face name to use for interface definitions.")
226
227 ;;;
228 ;;; Code to handle nested comments and unusual string escape sequences
229 ;;;
230
231 (defsyntax sml-syntax-prop-table
232 '((?\\ . ".") (?* . "."))
233 "Syntax table for text-properties")
234
235 ;; For Emacsen that have no built-in support for nested comments
236 (defun sml-get-depth-st ()
237 (save-excursion
238 (let* ((disp (if (eq (char-before) ?\)) (progn (backward-char) -1) nil))
239 (foo (backward-char))
240 (disp (if (eq (char-before) ?\() (progn (backward-char) 0) disp))
241 (pt (point)))
242 (when disp
243 (let* ((depth
244 (save-match-data
245 (if (re-search-backward "\\*)\\|(\\*" nil t)
246 (+ (or (get-char-property (point) 'comment-depth) 0)
247 (case (char-after) (?\( 1) (?* 0))
248 disp)
249 0)))
250 (depth (if (> depth 0) depth)))
251 (put-text-property pt (1+ pt) 'comment-depth depth)
252 (when depth sml-syntax-prop-table))))))
253
254 (defconst sml-font-lock-syntactic-keywords
255 `(("^\\s-*\\(\\\\\\)" (1 ',sml-syntax-prop-table))
256 ,@(unless sml-builtin-nested-comments-flag
257 '(("(?\\(\\*\\))?" (1 (sml-get-depth-st)))))))
258
259 (defconst sml-font-lock-defaults
260 '(sml-font-lock-keywords nil nil ((?_ . "w") (?' . "w")) nil
261 (font-lock-syntactic-keywords . sml-font-lock-syntactic-keywords)))
262
263
264 ;;; MORE CODE FOR SML-MODE
265
266 ;;;###Autoload
267 (add-to-list 'auto-mode-alist '("\\.s\\(ml\\|ig\\)\\'" . sml-mode))
268
269 ;;;###Autoload
270 (define-derived-mode sml-mode fundamental-mode "SML"
271 "\\<sml-mode-map>Major mode for editing ML code.
272 This mode runs `sml-mode-hook' just before exiting.
273 \\{sml-mode-map}"
274 (set (make-local-variable 'font-lock-defaults) sml-font-lock-defaults)
275 (set (make-local-variable 'outline-regexp) sml-outline-regexp)
276 (set (make-local-variable 'add-log-current-defun-function)
277 'sml-current-fun-name)
278 ;; forward-sexp-function is an experimental variable in my hacked Emacs.
279 (set (make-local-variable 'forward-sexp-function) 'sml-user-forward-sexp)
280 (sml-mode-variables))
281
282 (defun sml-mode-variables ()
283 (set-syntax-table sml-mode-syntax-table)
284 (setq local-abbrev-table sml-mode-abbrev-table)
285 ;; A paragraph is separated by blank lines or ^L only.
286
287 (set (make-local-variable 'paragraph-start)
288 (concat "^[\t ]*$\\|" page-delimiter))
289 (set (make-local-variable 'paragraph-separate) paragraph-start)
290 (set (make-local-variable 'indent-line-function) 'sml-indent-line)
291 (set (make-local-variable 'comment-start) "(* ")
292 (set (make-local-variable 'comment-end) " *)")
293 (set (make-local-variable 'comment-nested) t)
294 ;;(set (make-local-variable 'block-comment-start) "* ")
295 ;;(set (make-local-variable 'block-comment-end) "")
296 (set (make-local-variable 'comment-column) 40)
297 (set (make-local-variable 'comment-start-skip) "(\\*+\\s-*")
298 (set (make-local-variable 'comment-indent-function) 'sml-comment-indent))
299
300 (defun sml-electric-pipe ()
301 "Insert a \"|\".
302 Depending on the context insert the name of function, a \"=>\" etc."
303 (interactive)
304 (sml-with-ist
305 (unless (save-excursion (skip-chars-backward "\t ") (bolp)) (insert "\n"))
306 (insert "| ")
307 (let ((text
308 (save-excursion
309 (backward-char 2) ;back over the just inserted "| "
310 (let ((sym (sml-find-matching-starter sml-pipeheads
311 (sml-op-prec "|" 'back))))
312 (sml-forward-sym)
313 (sml-forward-spaces)
314 (cond
315 ((string= sym "|")
316 (let ((f (sml-forward-sym)))
317 (sml-find-forward "\\(=>\\|=\\||\\)\\S.")
318 (cond
319 ((looking-at "|") "") ;probably a datatype
320 ((looking-at "=>") " => ") ;`case', or `fn' or `handle'
321 ((looking-at "=") (concat f " = "))))) ;a function
322 ((string= sym "and")
323 ;; could be a datatype or a function
324 (while (and (setq sym (sml-forward-sym))
325 (string-match "^'" sym))
326 (sml-forward-spaces))
327 (sml-forward-spaces)
328 (if (or (not sym)
329 (equal (sml-forward-sym) "d="))
330 ""
331 (concat sym " = ")))
332 ;; trivial cases
333 ((string= sym "fun")
334 (while (and (setq sym (sml-forward-sym))
335 (string-match "^'" sym))
336 (sml-forward-spaces))
337 (concat sym " = "))
338 ((member sym '("case" "handle" "fn" "of")) " => ")
339 ;;((member sym '("abstype" "datatype")) "")
340 (t ""))))))
341
342 (insert text)
343 (indent-according-to-mode)
344 (beginning-of-line)
345 (skip-chars-forward "\t |")
346 (skip-syntax-forward "w")
347 (skip-chars-forward "\t ")
348 (when (= ?= (char-after)) (backward-char)))))
349
350 (defun sml-electric-semi ()
351 "Insert a \;.
352 If variable `sml-electric-semi-mode' is t, indent the current line, insert
353 a newline, and indent."
354 (interactive)
355 (insert "\;")
356 (if sml-electric-semi-mode
357 (reindent-then-newline-and-indent)))
358
359 ;;; INDENTATION !!!
360
361 (defun sml-mark-function ()
362 "Synonym for `mark-paragraph' -- sorry.
363 If anyone has a good algorithm for this..."
364 (interactive)
365 (mark-paragraph))
366
367 ;; (defun sml-indent-region (begin end)
368 ;; "Indent region of ML code."
369 ;; (interactive "r")
370 ;; (message "Indenting region...")
371 ;; (save-excursion
372 ;; (goto-char end) (setq end (point-marker)) (goto-char begin)
373 ;; (while (< (point) end)
374 ;; (skip-chars-forward "\t\n ")
375 ;; (indent-according-to-mode)
376 ;; (end-of-line))
377 ;; (move-marker end nil))
378 ;; (message "Indenting region... done"))
379
380 (defun sml-indent-line ()
381 "Indent current line of ML code."
382 (interactive)
383 (let ((savep (> (current-column) (current-indentation)))
384 (indent (max (or (ignore-errors (sml-calculate-indentation)) 0) 0)))
385 (if savep
386 (save-excursion (indent-line-to indent))
387 (indent-line-to indent))))
388
389 (defun sml-back-to-outer-indent ()
390 "Unindents to the next outer level of indentation."
391 (interactive)
392 (save-excursion
393 (beginning-of-line)
394 (skip-chars-forward "\t ")
395 (let ((start-column (current-column))
396 (indent (current-column)))
397 (if (> start-column 0)
398 (progn
399 (save-excursion
400 (while (>= indent start-column)
401 (if (re-search-backward "^[^\n]" nil t)
402 (setq indent (current-indentation))
403 (setq indent 0))))
404 (backward-delete-char-untabify (- start-column indent)))))))
405
406 (defun sml-find-comment-indent ()
407 (save-excursion
408 (let ((depth 1))
409 (while (> depth 0)
410 (if (re-search-backward "(\\*\\|\\*)" nil t)
411 (cond
412 ((looking-at "*)") (incf depth))
413 ((looking-at comment-start-skip) (decf depth)))
414 (setq depth -1)))
415 (if (= depth 0)
416 (1+ (current-column))
417 nil))))
418
419 (defun sml-calculate-indentation ()
420 (save-excursion
421 (beginning-of-line) (skip-chars-forward "\t ")
422 (sml-with-ist
423 ;; Indentation for comments alone on a line, matches the
424 ;; proper indentation of the next line.
425 (when (looking-at "(\\*") (sml-forward-spaces))
426 (let (data
427 (sml-point (point))
428 (sym (save-excursion (sml-forward-sym))))
429 (or
430 ;; allow the user to override the indentation
431 (when (looking-at (concat ".*" (regexp-quote comment-start)
432 "[ \t]*fixindent[ \t]*"
433 (regexp-quote comment-end)))
434 (current-indentation))
435
436 ;; continued comment
437 (and (looking-at "\\*") (sml-find-comment-indent))
438
439 ;; Continued string ? (Added 890113 lbn)
440 (and (looking-at "\\\\")
441 (save-excursion
442 (if (save-excursion (previous-line 1)
443 (beginning-of-line)
444 (looking-at "[\t ]*\\\\"))
445 (progn (previous-line 1) (current-indentation))
446 (if (re-search-backward "[^\\\\]\"" nil t)
447 (1+ (current-column))
448 0))))
449
450 (and (setq data (assoc sym sml-close-paren))
451 (sml-indent-relative sym data))
452
453 (and (member (save-excursion (sml-forward-sym)) sml-starters-syms)
454 (let ((sym (unless (save-excursion (sml-backward-arg))
455 (sml-backward-spaces)
456 (sml-backward-sym))))
457 (if sym (sml-get-sym-indent sym)
458 ;; FIXME: this can take a *long* time !!
459 (sml-find-matching-starter sml-starters-syms)
460 (current-column))))
461
462 (and (string= sym "|") (sml-indent-pipe))
463
464 (sml-indent-arg)
465 (sml-indent-default))))))
466
467 (defun sml-indent-relative (sym data)
468 (save-excursion
469 (sml-forward-sym) (sml-backward-sexp nil)
470 (unless (second data) (sml-backward-spaces) (sml-backward-sym))
471 (+ (or (cdr (assoc sym sml-symbol-indent)) 0)
472 (sml-delegated-indent))))
473
474 (defun sml-indent-pipe ()
475 (let ((sym (sml-find-matching-starter sml-pipeheads
476 (sml-op-prec "|" 'back))))
477 (when sym
478 (if (string= sym "|")
479 (if (sml-bolp) (current-column) (sml-indent-pipe))
480 (let ((pipe-indent (or (cdr (assoc "|" sml-symbol-indent)) -2)))
481 (when (member sym '("datatype" "abstype"))
482 (re-search-forward "="))
483 (sml-forward-sym)
484 (sml-forward-spaces)
485 (+ pipe-indent (current-column)))))))
486
487 (defun sml-find-forward (re)
488 (sml-forward-spaces)
489 (while (and (not (looking-at re))
490 (progn
491 (or (ignore-errors (forward-sexp 1) t) (forward-char 1))
492 (sml-forward-spaces)
493 (not (looking-at re))))))
494
495 (defun sml-indent-arg ()
496 (and (save-excursion (ignore-errors (sml-forward-arg)))
497 ;;(not (looking-at sml-not-arg-re))
498 ;; looks like a function or an argument
499 (sml-move-if (sml-backward-arg))
500 ;; an argument
501 (if (save-excursion (not (sml-backward-arg)))
502 ;; a first argument
503 (+ (current-column) sml-indent-args)
504 ;; not a first arg
505 (while (and (/= (current-column) (current-indentation))
506 (sml-move-if (sml-backward-arg))))
507 (unless (save-excursion (sml-backward-arg))
508 ;; all earlier args are on the same line
509 (sml-forward-arg) (sml-forward-spaces))
510 (current-column))))
511
512 (defun sml-get-indent (data sym)
513 (let ((head-sym (pop data)) d)
514 (cond
515 ((not (listp data)) data)
516 ((setq d (member sym data)) (second d))
517 ((and (consp data) (not (stringp (car data)))) (car data))
518 (t sml-indent-level))))
519
520 (defun sml-dangling-sym ()
521 (save-excursion
522 (and (not (sml-bolp))
523 (< (sml-point-after (end-of-line))
524 (sml-point-after (sml-forward-sym)
525 (sml-forward-spaces))))))
526
527 (defun sml-delegated-indent ()
528 (if (sml-dangling-sym)
529 (sml-indent-default 'noindent)
530 (sml-move-if (backward-word 1)
531 (looking-at sml-agglomerate-re))
532 (current-column)))
533
534 (defun sml-get-sym-indent (sym &optional style)
535 "Find the indentation for the SYM we're `looking-at'.
536 If indentation is delegated, the point will be at the start of
537 the parent at the end of this function.
538 Optional argument STYLE is currently ignored"
539 (assert (equal sym (save-excursion (sml-forward-sym))))
540 (save-excursion
541 (let ((delegate (assoc sym sml-close-paren))
542 (head-sym sym))
543 (when (and delegate (not (eval (third delegate))))
544 ;;(sml-find-match-backward sym delegate)
545 (sml-forward-sym) (sml-backward-sexp nil)
546 (setq head-sym
547 (if (second delegate)
548 (save-excursion (sml-forward-sym))
549 (sml-backward-spaces) (sml-backward-sym))))
550
551 (let ((idata (assoc head-sym sml-indent-rule)))
552 (when idata
553 ;;(if (or style (not delegate))
554 ;; normal indentation
555 (let ((indent (sml-get-indent idata sym)))
556 (when indent (+ (sml-delegated-indent) indent)))
557 ;; delgate indentation to the parent
558 ;;(sml-forward-sym) (sml-backward-sexp nil)
559 ;;(let* ((parent-sym (save-excursion (sml-forward-sym)))
560 ;; (parent-indent (cdr (assoc parent-sym sml-indent-starters))))
561 ;; check the special rules
562 ;;(+ (sml-delegated-indent)
563 ;; (or (sml-get-indent indent-data 1 'strict)
564 ;; (sml-get-indent parent-indent 1 'strict)
565 ;; (sml-get-indent indent-data 0)
566 ;; (sml-get-indent parent-indent 0))))))))
567 )))))
568
569 (defun sml-indent-default (&optional noindent)
570 (let* ((sym-after (save-excursion (sml-forward-sym)))
571 (_ (sml-backward-spaces))
572 (sym-before (sml-backward-sym))
573 (sym-indent (and sym-before (sml-get-sym-indent sym-before))))
574 (if sym-indent
575 ;; the previous sym is an indentation introducer: follow the rule
576 (let ((indent-after (or (cdr (assoc sym-after sml-symbol-indent)) 0)))
577 (if noindent
578 ;;(current-column)
579 sym-indent
580 (+ sym-indent indent-after)))
581 ;; default-default
582 (let* ((prec-after (sml-op-prec sym-after 'back))
583 (prec (or (sml-op-prec sym-before 'back) prec-after 100)))
584 ;; go back until you hit a symbol that has a lower prec than the
585 ;; "current one", or until you backed over a sym that has the same prec
586 ;; but is at the beginning of a line.
587 (while (and (not (sml-bolp))
588 (sml-move-if (sml-backward-sexp (1- prec)))
589 (not (sml-bolp)))
590 (while (sml-move-if (sml-backward-sexp prec))))
591 ;; the `noindent' case does back over an introductory symbol
592 ;; such as `fun', ...
593 (when noindent
594 (sml-move-if
595 (sml-backward-spaces)
596 (member (sml-backward-sym) sml-starters-syms)))
597 (current-column)))))
598
599
600 (defun sml-bolp ()
601 (save-excursion
602 (skip-chars-backward " \t|") (bolp)))
603
604
605 ;; maybe `|' should be set to word-syntax in our temp syntax table ?
606 (defun sml-current-indentation ()
607 (save-excursion
608 (beginning-of-line)
609 (skip-chars-forward " \t|")
610 (current-column)))
611
612
613 (defun sml-find-matching-starter (syms &optional prec)
614 (let (sym)
615 (ignore-errors
616 (while
617 (progn (sml-backward-sexp prec)
618 (setq sym (save-excursion (sml-forward-sym)))
619 (not (or (member sym syms) (bobp)))))
620 (unless (bobp) sym))))
621
622 (defun sml-skip-siblings ()
623 (while (and (not (bobp)) (sml-backward-arg))
624 (sml-find-matching-starter sml-starters-syms))
625 (when (looking-at "in\\>\\|local\\>")
626 ;;skip over `local...in' and continue
627 (forward-word 1)
628 (sml-backward-sexp nil)
629 (sml-skip-siblings)))
630
631 (defun sml-beginning-of-defun ()
632 (let ((sym (sml-find-matching-starter sml-starters-syms)))
633 (if (member sym '("fun" "functor" "signature" "structure"
634 "abstraction" "datatype" "abstype"))
635 (save-excursion (sml-forward-sym) (sml-forward-spaces)
636 (sml-forward-sym))
637 ;; We're inside a "non function declaration": let's skip all other
638 ;; declarations that we find at the same level and try again.
639 (sml-skip-siblings)
640 ;; Obviously, let's not try again if we're at bobp.
641 (unless (bobp) (sml-beginning-of-defun)))))
642
643 (defcustom sml-max-name-components 3
644 "Maximum number of components to use for the current function name."
645 :group 'sml
646 :type 'integer)
647
648 (defun sml-current-fun-name ()
649 (save-excursion
650 (let ((count sml-max-name-components)
651 fullname name)
652 (end-of-line)
653 (while (and (> count 0)
654 (setq name (sml-beginning-of-defun)))
655 (decf count)
656 (setq fullname (if fullname (concat name "." fullname) name))
657 ;; Skip all other declarations that we find at the same level.
658 (sml-skip-siblings))
659 fullname)))
660
661
662 (defun sml-comment-indent ()
663 (if (looking-at "^(\\*") ; Existing comment at beginning
664 0 ; of line stays there.
665 comment-column))
666
667 ;;; INSERTING PROFORMAS (COMMON SML-FORMS)
668
669 (defvar sml-forms-alist nil
670 "*Alist of code templates.
671 You can extend this alist to your heart's content. For each additional
672 template NAME in the list, declare a keyboard macro or function (or
673 interactive command) called 'sml-form-NAME'.
674 If 'sml-form-NAME' is a function it takes no arguments and should
675 insert the template at point\; if this is a command it may accept any
676 sensible interactive call arguments\; keyboard macros can't take
677 arguments at all. Apropos keyboard macros, see `name-last-kbd-macro'
678 and `sml-addto-forms-alist'.
679 `sml-forms-alist' understands let, local, case, abstype, datatype,
680 signature, structure, and functor by default.")
681
682 (defmacro sml-def-skeleton (name interactor &rest elements)
683 (let ((fsym (intern (concat "sml-form-" name))))
684 `(progn
685 (add-to-list 'sml-forms-alist ',(cons name fsym))
686 (define-abbrev sml-mode-abbrev-table ,name "" ',fsym)
687 (define-skeleton ,fsym
688 ,(format "SML-mode skeleton for `%s..' expressions" name)
689 ,interactor
690 ,(concat name " ") >
691 ,@elements))))
692 (put 'sml-def-skeleton 'lisp-indent-function 2)
693
694 (sml-def-skeleton "let" nil
695 _ "\nin" > "\nend" >)
696
697 (sml-def-skeleton "if" nil
698 _ " then " > "\nelse " >)
699
700 (sml-def-skeleton "local" nil
701 _ "\nin" > "\nend" >)
702
703 (sml-def-skeleton "case" "Case expr: "
704 str "\nof " > _ " => ")
705
706 (sml-def-skeleton "signature" "Signature name: "
707 str " =\nsig" > "\n" > _ "\nend" >)
708
709 (sml-def-skeleton "structure" "Structure name: "
710 str " =\nstruct" > "\n" > _ "\nend" >)
711
712 (sml-def-skeleton "functor" "Functor name: "
713 str " () : =\nstruct" > "\n" > _ "\nend" >)
714
715 (sml-def-skeleton "datatype" "Datatype name and type params: "
716 str " =" \n)
717
718 (sml-def-skeleton "abstype" "Abstype name and type params: "
719 str " =" \n _ "\nwith" > "\nend" >)
720
721 ;;
722
723 (sml-def-skeleton "struct" nil
724 _ "\nend" >)
725
726 (sml-def-skeleton "sig" nil
727 _ "\nend" >)
728
729 (sml-def-skeleton "val" nil
730 _ " = " >)
731
732 (sml-def-skeleton "fn" nil
733 _ " =>" >)
734
735 (sml-def-skeleton "fun" nil
736 _ " =" >)
737
738 ;;
739
740 (defun sml-forms-menu (menu)
741 (easy-menu-filter-return
742 (easy-menu-create-menu "Forms"
743 (mapcar (lambda (x)
744 (let ((name (car x))
745 (fsym (cdr x)))
746 (vector name fsym t)))
747 sml-forms-alist))))
748
749 (defvar sml-last-form "let")
750
751 (defun sml-electric-space ()
752 "Expand a symbol into an SML form, or just insert a space.
753 If the point directly precedes a symbol for which an SML form exists,
754 the corresponding form is inserted."
755 (interactive)
756 (let ((abbrev-mode (not abbrev-mode))
757 (last-command-char ?\ )
758 ;; Bind `this-command' to fool skeleton's special abbrev handling.
759 (this-command 'self-insert-command))
760 (call-interactively 'self-insert-command)))
761
762 (defun sml-insert-form (name newline)
763 "Interactive short-cut to insert the NAME common ML form.
764 If a prefix argument is given insert a NEWLINE and indent first, or
765 just move to the proper indentation if the line is blank\; otherwise
766 insert at point (which forces indentation to current column).
767
768 The default form to insert is 'whatever you inserted last time'
769 \(just hit return when prompted\)\; otherwise the command reads with
770 completion from `sml-forms-alist'."
771 (interactive
772 (list (completing-read
773 (format "Form to insert: (default %s) " sml-last-form)
774 sml-forms-alist nil t nil)
775 current-prefix-arg))
776 ;; default is whatever the last insert was...
777 (if (string= name "") (setq name sml-last-form) (setq sml-last-form name))
778 (unless (or (not newline)
779 (save-excursion (beginning-of-line) (looking-at "\\s-*$")))
780 (insert "\n"))
781 (unless (/= ?w (char-syntax (char-before))) (insert " "))
782 (let ((f (cdr (assoc name sml-forms-alist))))
783 (cond
784 ((commandp f) (command-execute f))
785 (f (funcall f))
786 (t (error "Undefined form: %s" name)))))
787
788 ;; See also macros.el in emacs lisp dir.
789
790 (defun sml-addto-forms-alist (name)
791 "Assign a name to the last keyboard macro defined.
792 Argument NAME is transmogrified to sml-form-NAME which is the symbol
793 actually defined.
794
795 The symbol's function definition becomes the keyboard macro string.
796
797 If that works, NAME is added to `sml-forms-alist' so you'll be able to
798 reinvoke the macro through \\[sml-insert-form]. You might want to save
799 the macro to use in a later editing session -- see `insert-kbd-macro'
800 and add these macros to your .emacs file.
801
802 See also `edit-kbd-macro' which is bound to \\[edit-kbd-macro]."
803 (interactive "sName for last kbd macro (\"sml-form-\" will be added): ")
804 (when (string= name "") (error "No command name given"))
805 (let ((fsym (intern (concat "sml-form-" name))))
806 (name-last-kbd-macro fsym)
807 (message "Macro bound to %s" fsym)
808 (add-to-list 'sml-forms-alist (cons name fsym))))
809
810 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
811 ;;;; SML/NJ's Compilation Manager support ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
812 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
813
814 ;;;###autoload
815 (add-to-list 'completion-ignored-extensions "CM/")
816 ;;;###autoload
817 (add-to-list 'auto-mode-alist '("\\.cm\\'" . sml-cm-mode))
818 ;;;###autoload
819 (define-generic-mode 'sml-cm-mode
820 '(("(*" . "*)"))
821 '("library" "Library" "LIBRARY" "group" "Group" "GROUP" "is" "IS"
822 "structure" "functor" "signature" "funsig")
823 nil '("\\.cm\\'")
824 (list (lambda () (local-set-key "\C-c\C-c" 'sml-compile)))
825 "Generic mode for SML/NJ's Compilation Manager configuration files.")
826
827
828 (provide 'sml-mode)
829
830 ;;; sml-mode.el ends here