1 ;;; sml-mode.el. Major mode for editing (Standard) ML. Version 3.3(beta)
3 (defconst rcsid-sml-mode "@(#)$Name$:$Id$")
5 ;; Copyright (C) 1989-1999, Lars Bo Nielsen; 1994,1997, Matthew J. Morley
10 ;; This file is not part of GNU Emacs, but it is distributed under the
13 ;; ====================================================================
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.
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.
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.
29 ;; ====================================================================
33 ;; Still under construction: History obscure, needs a biographer as
34 ;; well as a M-x doctor. Change Log on request.
36 ;; Hacked by Olin Shivers for comint from Lars Bo Nielsen's sml.el.
38 ;; Hacked by Matthew Morley to incorporate Fritz Knabe's hilite and
39 ;; font-lock patterns, some of Steven Gilmore's (reduced) easy-menus,
40 ;; and numerous bugs and bug-fixes.
42 ;; Author: Lars Bo Nielsen
46 ;; Matthew Morley <mjm@scs.leeds.ac.uk> (aka <matthew@verisity.com>)
47 ;; Matthias Blume <blume@cs.princeton.edu> (aka <blume@kurims.kyoto-u.ac.jp>)
48 ;; (Stefan Monnier) monnier@cs.yale.edu
49 ;; Maintainer: (Stefan Monnier) monnier+lists/emacs/sml@tequila.cs.yale.edu
54 ;; See accompanying info file: sml-mode.info
56 ;;; FOR YOUR .EMACS FILE
58 ;; If sml-mode.el lives in some non-standard directory, you must tell
59 ;; emacs where to get it. This may or may not be necessary:
61 ;; (setq load-path (cons (expand-file-name "~jones/lib/emacs") load-path))
63 ;; Then to access the commands autoload sml-mode with that command:
65 ;; (autoload 'sml-mode "sml-mode" "Major mode for editing ML programs." t)
67 ;; If files ending in ".sml" or ".ML" are hereafter considered to contain
68 ;; Standard ML source, put their buffers into sml-mode automatically:
70 ;; (setq auto-mode-alist
71 ;; (cons '(("\\.sml$" . sml-mode)
72 ;; ("\\.ML$" . sml-mode)) auto-mode-alist))
74 ;; Here's an example of setting things up in the sml-mode-hook:
76 ;; (setq sml-mode-hook
77 ;; '(lambda() "ML mode hacks"
78 ;; (setq sml-indent-level 2 ; conserve on horiz. space
79 ;; indent-tabs-mode nil))) ; whatever
81 ;; sml-mode-hook is run whenever a new sml-mode buffer is created.
82 ;; There is an sml-load-hook too, which is only run when this file is
83 ;; loaded. One use for this hook is to select your preferred
84 ;; highlighting scheme, like this:
86 ;; (setq sml-load-hook
87 ;; '(lambda() "Highlights." (require 'sml-hilite)))
89 ;; hilit19 is the magic that actually does the highlighting. My set up
90 ;; for hilit19 runs something like this:
93 ;; (setq hilit-background-mode t ; monochrome (alt: 'dark or 'light)
94 ;; hilit-inhibit-hooks nil
95 ;; hilit-inhibit-rebinding nil
98 ;; Alternatively, you can (require 'sml-font) which uses the font-lock
101 ;; Finally, there are inferior-sml-{mode,load}-hooks -- see comments
102 ;; in sml-proc.el. For much more information consult the mode's *info*
107 (defconst sml-mode-version-string
108 "sml-mode, version 3.3")
115 ;;; VARIABLES CONTROLLING INDENTATION
117 (defvar sml-indent-level 4
118 "*Indentation of blocks in ML (see also `sml-structure-indent').")
120 (defvar sml-structure-indent 4 ; Not currently an option.
121 "*Indentation of signature/structure/functor declarations.")
123 (defvar sml-pipe-indent -2
124 "*Extra (usually negative) indentation for lines beginning with `|'.")
126 (defvar sml-indent-case-arm 0
127 "*Indentation of case arms.")
129 (defvar sml-indent-case-of 2
130 "*Indentation of an `of' on its own line.")
132 (defvar sml-indent-equal -2
133 "*Extra (usually negative) indenting for lines beginning with `='.")
135 (defvar sml-indent-fn -3
136 "*Extra (usually negative) indenting for lines beginning with `fn'.")
138 ;;(defvar sml-indent-paren -1
139 ;; "*Extra (usually negative) indenting for lines beginning with `('.")
141 (defvar sml-indent-args 4
142 "*Indentation of args placed on a separate line.")
144 (defvar sml-indent-align-args t
145 "*Whether the arguments should be aligned.")
147 (defvar sml-case-indent nil
148 "*How to indent case-of expressions.
149 If t: case expr If nil: case expr of
150 of exp1 => ... exp1 => ...
151 | exp2 => ... | exp2 => ...
153 The first seems to be the standard in SML/NJ, but the second
156 (defvar sml-nested-if-indent nil
157 "*Determine how nested if-then-else will be formatted:
158 If t: if exp1 then exp2 If nil: if exp1 then exp2
159 else if exp3 then exp4 else if exp3 then exp4
160 else if exp5 then exp6 else if exp5 then exp6
161 else exp7 else exp7")
163 (defvar sml-type-of-indent nil
164 "*How to indent `let' `struct' etc.
165 If t: fun foo bar = let If nil: fun foo bar = let
171 Will not have any effect if the starting keyword is first on the line.")
173 (defvar sml-electric-semi-mode nil
174 "*If t, `\;' will self insert, reindent the line, and do a newline.
175 If nil, just insert a `\;'. (To insert while t, do: C-q \;).")
177 (defvar sml-paren-lookback 1000
178 "*How far back (in chars) the indentation algorithm should look
179 for open parenthesis. High value means slow indentation algorithm. A
180 value of 1000 (being the equivalent of 20-30 lines) should suffice
181 most uses. (A value of nil, means do not look at all)")
183 ;;; OTHER GENERIC MODE VARIABLES
185 (defvar sml-mode-info "sml-mode"
186 "*Where to find Info file for sml-mode.
187 The default assumes the info file \"sml-mode.info\" is on Emacs' info
188 directory path. If it is not, either put the file on the standard path
189 or set the variable sml-mode-info to the exact location of this file
190 which is part of the sml-mode 3.2 (and later) distribution. E.g:
192 (setq sml-mode-info \"/usr/me/lib/info/sml-mode\")
194 in your .emacs file. You can always set it interactively with the
195 set-variable command.")
197 (defvar sml-mode-hook nil
198 "*This hook is run when sml-mode is loaded, or a new sml-mode buffer created.
199 This is a good place to put your preferred key bindings.")
201 (defvar sml-load-hook nil
202 "*This hook is run when sml-mode (sml-mode.el) is loaded into Emacs.")
204 (defvar sml-mode-abbrev-table nil "*SML mode abbrev table (default nil)")
206 (defvar sml-error-overlay t
207 "*Non-nil means use an overlay to highlight errorful code in the buffer.
209 This gets set when `sml-mode' is invoked\; if you don't like/want SML
210 source errors to be highlighted in this way, do something like
212 \(setq-default sml-error-overlay nil\)
214 in your `sml-load-hook', say.")
216 (make-variable-buffer-local 'sml-error-overlay)
218 ;;; CODE FOR SML-MODE
220 (defun sml-mode-info ()
221 "Command to access the TeXinfo documentation for sml-mode.
222 See doc for the variable sml-mode-info."
226 (Info-goto-node (concat "(" sml-mode-info ")"))
228 (describe-variable 'sml-mode-info)
229 (message "Can't find it... set this variable first!")))))
232 ;;; Autoload functions -- no-doc is another idea cribbed from AucTeX!
235 "This function is part of sml-proc, and has not yet been loaded.
236 Full documentation will be available after autoloading the function."))
238 (autoload 'run-sml "sml-proc" sml-no-doc t)
239 (autoload 'sml-make "sml-proc" sml-no-doc t)
240 (autoload 'sml-load-file "sml-proc" sml-no-doc t)
242 (autoload 'switch-to-sml "sml-proc" sml-no-doc t)
243 (autoload 'sml-send-region "sml-proc" sml-no-doc t)
244 (autoload 'sml-send-buffer "sml-proc" sml-no-doc t)
245 (autoload 'sml-next-error "sml-proc" sml-no-doc t))
249 (defconst sml-keywords-regexp
250 (sml-syms-re "abstraction" "abstype" "and" "andalso" "as" "before" "case"
251 "datatype" "else" "end" "eqtype" "exception" "do" "fn"
252 "fun" "functor" "handle" "if" "in" "include" "infix"
253 "infixr" "let" "local" "nonfix" "of" "op" "open" "orelse"
254 "overload" "raise" "rec" "sharing" "sig" "signature"
255 "struct" "structure" "then" "type" "val" "where" "while"
257 "A regexp that matches any and all keywords of SML.")
259 (defconst sml-font-lock-keywords
260 `(;;(sml-font-comments-and-strings)
261 ("\\<\\(fun\\|and\\)\\s-+\\(\\sw+\\)"
262 (1 font-lock-keyword-face)
263 (2 font-lock-function-def-face))
264 ("\\<\\(\\(data\\|abs\\|with\\|eq\\)?type\\)\\s-+\\('\\s-*\\sw+\\s-+\\)*\\(\\sw+\\)"
265 (1 font-lock-keyword-face)
266 (4 font-lock-type-def-face))
267 ("\\<\\(val\\)\\s-+\\(\\sw+\\>\\s-*\\)?\\(\\sw+\\)\\s-*="
268 (1 font-lock-keyword-face)
269 ;;(6 font-lock-variable-def-face nil t)
270 (3 font-lock-variable-def-face))
271 ("\\<\\(structure\\|functor\\|abstraction\\)\\s-+\\(\\sw+\\)"
272 (1 font-lock-keyword-face)
273 (2 font-lock-module-def-face))
274 ("\\<\\(signature\\)\\s-+\\(\\sw+\\)"
275 (1 font-lock-keyword-face)
276 (2 font-lock-interface-def-face))
278 (,sml-keywords-regexp . font-lock-keyword-face))
279 "Regexps matching standard SML keywords.")
281 ;; default faces values
282 (flet ((def-face (face def)
283 "Define a face for font-lock."
284 (unless (boundp face)
287 ((facep def) (copy-face def face))
289 (def-face 'font-lock-function-def-face 'font-lock-function-name-face)
290 (def-face 'font-lock-type-def-face 'font-lock-type-face)
291 (def-face 'font-lock-module-def-face 'font-lock-function-name-face)
292 (def-face 'font-lock-interface-def-face 'font-lock-type-face)
293 (def-face 'font-lock-variable-def-face 'font-lock-variable-name-face))
295 (defvar sml-syntax-prop-table
296 (let ((st (make-syntax-table)))
297 (modify-syntax-entry ?l "(d" st)
298 (modify-syntax-entry ?s "(d" st)
299 (modify-syntax-entry ?d ")l" st)
300 (modify-syntax-entry ?* "." st)
303 (defun sml-get-depth-st ()
305 (let* ((disp (if (eq (char-before) ?\)) (progn (backward-char) -1) nil))
306 (foo (backward-char))
307 (disp (if (eq (char-before) ?\() (progn (backward-char) 0) disp))
312 (if (re-search-backward "\\*)\\|(\\*" nil t)
313 (+ (or (get-char-property (point) 'comment-depth) 0)
314 (case (char-after) (?\( 1) (?* 0))
317 (depth (if (> depth 0) depth)))
318 (put-text-property pt (1+ pt) 'comment-depth depth)
319 (when depth sml-syntax-prop-table))))))
321 (defconst sml-font-lock-syntactic-keywords
322 `(;;("\\<\\(l\\)\\(et\\|ocal\\)\\>" (1 ',sml-syntax-prop-table))
323 ;;("\\<\\(s\\)\\(ig\\truct\\)\\>" (1 ',sml-syntax-prop-table))
324 ;;("\\<en\\(d\\)\\>" (1 ',sml-syntax-prop-table))
325 ("(?\\(\\*\\))?" (1 (sml-get-depth-st)))))
327 (defconst sml-font-lock-defaults
328 '(sml-font-lock-keywords nil nil ((?_ . "w") (?' . "w")) nil
329 (font-lock-syntactic-keywords . sml-font-lock-syntactic-keywords)))
331 ;; code to get comment fontification working in the face of recursive
332 ;; comments. It's lots more work than it should be. -- stefan
333 ;; (defvar sml-font-cache '((0 . normal))
334 ;; "List of (POSITION . STATE) pairs for an SML buffer.
335 ;; The STATE is either `normal', `comment', or `string'. The POSITION is
336 ;; immediately after the token that caused the state change.")
337 ;; (make-variable-buffer-local 'sml-font-cache)
339 ;; (defun sml-font-comments-and-strings (limit)
340 ;; "Fontify SML comments and strings up to LIMIT.
341 ;; Handles nested comments and SML's escapes for breaking a string over lines.
342 ;; Uses sml-font-cache to maintain the fontification state over the buffer."
343 ;; (let ((beg (point))
345 ;; (while (< beg limit)
346 ;; (while (and sml-font-cache
347 ;; (> (caar sml-font-cache) beg))
348 ;; (pop sml-font-cache))
349 ;; (setq last (caar sml-font-cache))
350 ;; (setq class (cdar sml-font-cache))
353 ;; ((eq class 'normal)
355 ;; ((not (re-search-forward "\\((\\*\\)\\|\\(\"\\)" limit t))
356 ;; (goto-char limit))
357 ;; ((match-beginning 1)
358 ;; (push (cons (point) 'comment) sml-font-cache))
359 ;; ((match-beginning 2)
360 ;; (push (cons (point) 'string) sml-font-cache))))
361 ;; ((eq class 'comment)
364 ;; (while (and (> nest 0)
365 ;; (re-search-forward "\\((\\*\\)\\|\\(\\*)\\)" limit t))
367 ;; ((match-beginning 1) (incf nest))
368 ;; ((match-beginning 2) (decf nest))))
370 ;; (goto-char limit))
372 ;; (push (cons (point) 'normal) sml-font-cache)))
373 ;; (put-text-property (- last 2) (point) 'face 'font-lock-comment-face))
374 ;; ((eq class 'string)
375 ;; (while (and (re-search-forward
376 ;; "\\(\"\\)\\|\\(\\\\\\s-*\\\\\\)\\|\\(\\\\\"\\)" limit t)
377 ;; (not (match-beginning 1))))
379 ;; ((match-beginning 1)
380 ;; (push (cons (point) 'normal) sml-font-cache))
382 ;; (goto-char limit)))
383 ;; (put-text-property (- last 1) (point) 'face 'font-lock-string-face)))
384 ;; (setq beg (point)))))
386 ;;; H A C K A T T A C K ! X E M A C S V E R S U S E M A C S
388 ;; (cond ((fboundp 'make-extent)
389 ;; ;; suppose this is XEmacs
391 ;; (defun sml-make-overlay ()
392 ;; "Create a new text overlay (extent) for the SML buffer."
393 ;; (let ((ex (make-extent 1 1)))
394 ;; (set-extent-property ex 'face 'zmacs-region) ex))
396 ;; (defalias 'sml-is-overlay 'extentp)
398 ;; (defun sml-overlay-active-p ()
399 ;; "Determine whether the current buffer's error overlay is visible."
400 ;; (and (sml-is-overlay sml-error-overlay)
401 ;; (not (zerop (extent-length sml-error-overlay)))))
403 ;; (defalias 'sml-move-overlay 'set-extent-endpoints))
405 ;; ((fboundp 'make-overlay)
406 ;; otherwise assume it's Emacs
408 (defun sml-make-overlay ()
409 "Create a new text overlay (extent) for the SML buffer."
410 (let ((ex (make-overlay 0 0)))
411 (overlay-put ex 'face 'region) ex))
413 (defalias 'sml-is-overlay 'overlayp)
415 (defun sml-overlay-active-p ()
416 "Determine whether the current buffer's error overlay is visible."
417 (and (sml-is-overlay sml-error-overlay)
418 (not (equal (overlay-start sml-error-overlay)
419 (overlay-end sml-error-overlay)))))
421 (defalias 'sml-move-overlay 'move-overlay);;)
423 ;; ;; what *is* this!?
424 ;; (defalias 'sml-is-overlay 'ignore)
425 ;; (defalias 'sml-overlay-active-p 'ignore)
426 ;; (defalias 'sml-make-overlay 'ignore)
427 ;; (defalias 'sml-move-overlay 'ignore)))
429 ;;; MORE CODE FOR SML-MODE
431 (defun sml-mode-version ()
432 "This file's version number (sml-mode)."
434 (message sml-mode-version-string))
438 "Major mode for editing ML code.
439 Tab indents for ML code.
440 Comments are delimited with (* ... *).
441 Blank lines and form-feeds separate paragraphs.
442 Delete converts tabs to spaces as it moves back.
444 For information on running an inferior ML process, see the documentation
445 for inferior-sml-mode (set this up with \\[sml]).
447 Customisation: Entry to this mode runs the hooks on sml-mode-hook.
449 Variables controlling the indentation
450 =====================================
452 Seek help (\\[describe-variable]) on individual variables to get current settings.
454 sml-indent-level (default 4)
455 The indentation of a block of code.
457 sml-pipe-indent (default -2)
458 Extra indentation of a line starting with \"|\".
460 sml-case-indent (default nil)
461 Determine the way to indent case-of expression.
463 sml-nested-if-indent (default nil)
464 Determine how nested if-then-else expressions are formatted.
466 sml-type-of-indent (default nil)
467 How to indent let, struct, local, etc.
468 Will not have any effect if the starting keyword is first on the line.
470 sml-electric-semi-mode (default nil)
471 If t, a `\;' will reindent line, and perform a newline.
473 sml-paren-lookback (default 1000)
474 Determines how far back (in chars) the indentation algorithm should
475 look to match parenthesis. A value of nil, means do not look at all.
482 (kill-all-local-variables)
484 (use-local-map sml-mode-map)
485 (setq major-mode 'sml-mode)
486 (setq mode-name "SML")
487 (set (make-local-variable 'outline-regexp) sml-outline-regexp)
488 (run-hooks 'sml-mode-hook)) ; Run the hook last
490 (defun sml-mode-variables ()
491 (set-syntax-table sml-mode-syntax-table)
492 (setq local-abbrev-table sml-mode-abbrev-table)
493 ;; A paragraph is separated by blank lines or ^L only.
495 (set (make-local-variable 'paragraph-start)
496 (concat "^[\t ]*$\\|" page-delimiter))
497 (set (make-local-variable 'paragraph-separate) paragraph-start)
498 (set (make-local-variable 'indent-line-function) 'sml-indent-line)
499 (set (make-local-variable 'comment-start) "(* ")
500 (set (make-local-variable 'comment-end) " *)")
501 (set (make-local-variable 'comment-column) 40)
502 (set (make-local-variable 'comment-start-skip) "(\\*+[ \t]?")
503 (set (make-local-variable 'comment-indent-function) 'sml-comment-indent)
504 (set (make-local-variable 'font-lock-defaults) sml-font-lock-defaults)
505 ;;(set (make-local-variable 'parse-sexp-lookup-properties) t)
506 ;;(set (make-local-variable 'parse-sexp-ignore-comments) t)
507 (setq sml-error-overlay (and sml-error-overlay (sml-make-overlay))))
509 (defun sml-error-overlay (undo &optional beg end buffer)
510 "Move `sml-error-overlay' so it surrounds the text region in the
511 current buffer. If the buffer-local variable `sml-error-overlay' is
512 non-nil it should be an overlay \(or extent, in XEmacs speak\)\; this
513 function moves the overlay over the current region. If the optional
514 BUFFER argument is given, move the overlay in that buffer instead of
517 Called interactively, the optional prefix argument UNDO indicates that
518 the overlay should simply be removed: \\[universal-argument] \
519 \\[sml-error-overlay]."
522 (set-buffer (or buffer (current-buffer)))
523 (if (sml-is-overlay sml-error-overlay)
525 (sml-move-overlay sml-error-overlay 1 1)
526 ;; if active regions, signals mark not active if no region set
527 (let ((beg (or beg (region-beginning)))
528 (end (or end (region-end))))
529 (sml-move-overlay sml-error-overlay beg end))))))
531 (defun sml-electric-pipe ()
533 Depending on the context insert the name of function, a \"=>\" etc."
538 (sml-find-matching-starter sml-pipehead-re)
540 ;; It was a function, insert the function name
541 ((or (looking-at "fun\\>")
542 (and (looking-at "and\\>")
544 (sml-find-matching-starter
545 (sml-syms-re "datatype" "abstype" "fun"))
546 (looking-at "fun\\>"))))
547 (forward-word 1) (sml-forward-spaces)
549 (buffer-substring (point) (progn (forward-word 1) (point)))
552 ((looking-at (sml-syms-re "case" "handle" "fn")) " => ")
553 ((looking-at (sml-syms-re "abstype" "datatype" "and")) "")
554 (t (error "Wow, now, there's a bug"))))))
556 (unless (save-excursion (skip-chars-backward "\t ") (bolp)) (insert "\n"))
560 (skip-chars-forward "\t |")
561 (skip-syntax-forward "w")
562 (skip-chars-forward "\t ")
563 (when (= ?= (char-after)) (backward-char)))))
565 (defun sml-electric-semi ()
567 If variable sml-electric-semi-mode is t, indent the current line, insert
568 a newline, and indent."
571 (if sml-electric-semi-mode
572 (reindent-then-newline-and-indent)))
576 (defun sml-mark-function ()
577 "Synonym for mark-paragraph -- sorry.
578 If anyone has a good algorithm for this..."
582 (defun sml-indent-region (begin end)
583 "Indent region of ML code."
585 (message "Indenting region...")
587 (goto-char end) (setq end (point-marker)) (goto-char begin)
588 (while (< (point) end)
589 (skip-chars-forward "\t\n ")
592 (move-marker end nil))
593 (message "Indenting region... done"))
595 (defun sml-indent-line ()
596 "Indent current line of ML code."
598 (let ((indent (sml-calculate-indentation)))
599 (if (/= (current-indentation) indent)
600 (save-excursion ;; Added 890601 (point now stays)
601 (let ((beg (progn (beginning-of-line) (point))))
602 (skip-chars-forward "\t ")
603 (delete-region beg (point))
604 (indent-to indent))))
605 ;; If point is before indentation, move point to indentation
606 (if (< (current-column) (current-indentation))
607 (skip-chars-forward "\t "))))
609 (defun sml-back-to-outer-indent ()
610 "Unindents to the next outer level of indentation."
614 (skip-chars-forward "\t ")
615 (let ((start-column (current-column))
616 (indent (current-column)))
617 (if (> start-column 0)
620 (while (>= indent start-column)
621 (if (re-search-backward "^[^\n]" nil t)
622 (setq indent (current-indentation))
624 (backward-delete-char-untabify (- start-column indent)))))))
626 (defun sml-find-comment-indent ()
630 (if (re-search-backward "(\\*\\|\\*)" nil t)
632 ((looking-at "*)") (incf depth))
633 ((looking-at comment-start-skip) (decf depth)))
639 (defun sml-calculate-indentation ()
641 (beginning-of-line) (skip-chars-forward "\t ")
648 ;; Indentation for comments alone on a line, matches the
649 ;; proper indentation of the next line.
650 (and (looking-at comment-start-skip) (sml-forward-spaces) nil)
653 (and (looking-at "\\*") (setq indent (sml-find-comment-indent))
656 ;; Continued string ? (Added 890113 lbn)
657 (and (looking-at "\\\\")
659 (if (save-excursion (previous-line 1)
661 (looking-at "[\t ]*\\\\"))
662 (progn (previous-line 1) (current-indentation))
663 (if (re-search-backward "[^\\\\]\"" nil t)
664 (1+ (current-indentation))
667 (and (looking-at "in\\>") ; Match the beginning let/local
668 (sml-find-match-indent "\\<in\\>" "\\<l\\(ocal\\|et\\)\\>"))
670 (and (looking-at "end\\>") ; Match the beginning
671 ;; FIXME: should match "in" if available. Or maybe not
672 (sml-find-match-indent "\\<end\\>" sml-begin-symbols-re))
674 (and (looking-at "else\\>") ; Match the if
676 (sml-find-match-backward "\\<else\\>" "\\<if\\>")
677 (sml-move-if (backward-word 1)
678 (and sml-nested-if-indent
679 (looking-at "else[ \t]+if\\>")))
682 (and (looking-at "then\\>") ; Match the if + extra indentation
683 (sml-find-match-indent "\\<then\\>" "\\<if\\>" t))
685 (and (looking-at "of\\>")
687 (sml-find-match-backward "\\<of\\>" "\\<case\\>")
688 (+ (current-column) sml-indent-case-of)))
690 (and (looking-at sml-starters-re)
691 (let ((sym (sml-move-read (sml-move-if (not (sml-backward-arg))))))
692 (if sym (sml-get-sym-indent sym)
693 (sml-find-matching-starter sml-starters-re)
696 (and (looking-at "|") (sml-indent-pipe))
699 (sml-indent-default))))))
701 ;; (let ((indent (current-column)))
702 ;; ;;(skip-chars-forward "\t (")
704 ;; ;; a "let fun" or "let val"
705 ;; ((looking-at "let \\(fun\\|val\\)\\>")
706 ;; (+ (current-column) 4 sml-indent-level))
707 ;; ;; Started val/fun/structure...
708 ;; ;; Indent after "=>" pattern, but only if its not an fn _ =>
710 ;; ((looking-at ".*=>")
711 ;; (if (looking-at ".*\\<fn\\>.*=>")
713 ;; (+ indent sml-indent-case-arm)))
714 ;; ;; else keep the same indentation as previous line
715 ;; (t indent)))))))))
718 ;;(and (setq indent (sml-get-indent)) nil)
720 ;;(and (looking-at "=[^>]") (+ indent sml-indent-equal))
721 ;;(and (looking-at "fn\\>") (+ indent sml-indent-fn))
722 ;; (and (looking-at "(") (+ indent sml-indent-paren))
724 ;;(and sml-paren-lookback ; Look for open parenthesis ?
725 ;; (max indent (sml-get-paren-indent)))
728 (defun sml-indent-pipe ()
729 (when (sml-find-matching-starter (concat "|\\|\\<of\\>\\|" sml-pipehead-re)
730 (sml-op-prec "|" 'back))
732 (if (sml-bolp) (current-column) (sml-indent-pipe))
734 ((looking-at "datatype")
735 (re-search-forward "=")
737 ((looking-at "case\\>")
738 (sml-forward-sym) ;skip `case'
739 (sml-find-match-forward "\\<case\\>" "\\<of\\>"))
743 (+ sml-pipe-indent (current-column)))))
746 (defun sml-indent-arg ()
747 (and (save-excursion (ignore-errors (sml-forward-arg)))
748 ;;(not (looking-at sml-not-arg-re))
749 ;; looks like a function or an argument
750 (sml-move-if (sml-backward-arg))
752 (if (save-excursion (not (sml-backward-arg)))
754 (+ (current-column) sml-indent-args)
756 (while (and (/= (current-column) (current-indentation))
757 (sml-move-if (sml-backward-arg))))
758 (unless (save-excursion (sml-backward-arg))
759 ;; all earlier args are on the same line
760 (sml-forward-arg) (sml-forward-spaces))
763 (defun sml-re-assoc (al sym)
766 :test (lambda (x y) (string-match y x))))))
767 (defun sml-get-indent (data n &optional strict)
768 (eval (if (listp data)
770 (and (not strict) data))))
772 (defun sml-dangling-sym ()
774 (and (not (sml-bolp))
775 (< (sml-point-after (end-of-line))
776 (sml-point-after (sml-forward-sym)
777 (sml-forward-spaces))))))
779 (defun sml-get-sym-indent (sym &optional style)
780 "expects to be looking-at SYM."
781 (let ((indent-data (sml-re-assoc sml-indent-starters sym))
782 (delegate (eval (sml-re-assoc sml-delegate sym))))
783 (or (when indent-data
784 (if (or style (not delegate))
785 ;; normal indentation
786 (let ((indent (sml-get-indent indent-data (or style 0))))
788 (+ (if (sml-dangling-sym)
789 (sml-indent-default 'noindent)
792 ;; delgate indentation to the parent
793 (sml-forward-sym) (sml-backward-sexp nil)
794 (let* ((parent-sym (save-excursion (sml-move-read (sml-forward-sym))))
795 (parent-indent (sml-re-assoc sml-indent-starters parent-sym)))
796 ;; check the special rules
797 (sml-move-if (backward-word 1)
798 (looking-at "\\<else[ \t]+if\\>"))
799 (+ (if (sml-dangling-sym)
800 (sml-indent-default 'noindent)
802 (or (sml-get-indent indent-data 1 'strict)
803 (sml-get-indent parent-indent 1 'strict)
804 (sml-get-indent indent-data 0)
805 (sml-get-indent parent-indent 0))))))
808 ;; (when (> (sml-point-after (end-of-line))
809 ;; (progn (sml-forward-spaces) (point)))
810 ;; (current-column)))
813 (defun sml-indent-default (&optional noindent)
814 (let* ((sym-after (save-excursion (sml-move-read (sml-forward-sym))))
815 (prec-after (sml-op-prec sym-after 'back))
816 (_ (sml-backward-spaces))
817 (sym-before (sml-move-read (sml-backward-sym)))
818 (prec (or (sml-op-prec sym-before 'back) prec-after 100))
820 (or (and sym-before (sml-get-sym-indent sym-before))
823 (while (and (not (sml-bolp))
824 (sml-move-if (sml-backward-sexp (1- prec)))
826 (while (sml-move-if (sml-backward-sexp prec))))
827 (or (and (not (sml-bolp))
828 (= prec 65) (string-equal "=" sym-before) ;Yuck!!
830 (sml-backward-spaces)
831 (let* ((sym (sml-move-read (sml-backward-sym)))
832 (sym-indent (sml-re-assoc sml-indent-starters sym)))
836 (sml-get-sym-indent sym 1))))))
837 (current-column))))))
842 (skip-chars-backward " \t|") (bolp)))
844 ;; (defun sml-goto-first-subexp ()
845 ;; (let ((initpoint (point)))
847 ;; (let ((argp (and (looking-at "[[({a-zA-Z0-9_'#~]\\|$")
848 ;; (not (looking-at (concat "[ \t]*" sml-not-arg-regexp))))))
849 ;; (while (and argp (not (bobp)))
850 ;; (let* ((endpoint (point))
851 ;; (startpoint endpoint))
854 ;; (sml-backward-sexp t)
855 ;; (setq startpoint (point))
856 ;; (and (not (looking-at (concat "[[(]\\|" sml-keywords-regexp)))
857 ;; (progn (sml-forward-sexp)
859 ;; (>= (point) endpoint)))))
860 ;; (goto-char (if argp startpoint endpoint))))
861 ;; (let ((res (point)))
862 ;; (sml-backward-spaces) (skip-syntax-backward "^ ")
863 ;; (if (looking-at "*\\|:[^=]\\|->\\|of\\>")
864 ;; (goto-char initpoint)
866 ;; (sml-skip-spaces))))))
868 ;; maybe `|' should be set to word-syntax in our temp syntax table ?
869 (defun sml-current-indentation ()
872 (skip-chars-forward " \t|")
875 ;; (defun sml-get-indent ()
877 ;; ;;(let ((endpoint (point)))
879 ;; ;; let's try to see whether we are inside an `f a1 a2 ..' expression
880 ;; ;;(sml-goto-first-subexp)
881 ;; ;;(setq rover (current-column))
882 ;; ;;(sml-skip-spaces)
884 ;; ;; ((< (point) endpoint)
885 ;; ;; ;; we're not the first subexp
886 ;; ;; (sml-forward-sexp)
887 ;; ;; (if (and sml-indent-align-args
888 ;; ;; (progn (sml-skip-spaces) (< (point) endpoint)))
889 ;; ;; ;; we're not the second subexp
890 ;; ;; (current-column)
891 ;; ;; (+ rover sml-indent-args)))
893 ;; ;; we're not inside an `f a1 a2 ..' expr
894 ;; ((progn ;;(goto-char endpoint)
895 ;; (sml-backward-spaces)
896 ;; (/= (skip-chars-backward ";,") 0))
897 ;; (sml-backward-sexps (concat "[[(]\\'\\|" sml-user-begin-symbols-re))
901 ;; (while (/= (current-column) (current-indentation))
902 ;; (sml-backward-sexp t))
903 ;; (when (looking-at "\\<of\\>") (forward-word 1))
904 ;; (skip-chars-forward "\t |")
905 ;; (let ((indent (current-column)))
906 ;; ;;(skip-chars-forward "\t (")
908 ;; ;; a "let fun" or "let val"
909 ;; ((looking-at "let \\(fun\\|val\\)\\>")
910 ;; (+ (current-column) 4 sml-indent-level))
911 ;; ;; Started val/fun/structure...
912 ;; ((looking-at sml-indent-starters-reg)
913 ;; (+ (current-column) sml-indent-level))
914 ;; ;; Indent after "=>" pattern, but only if its not an fn _ =>
916 ;; ((looking-at ".*=>")
917 ;; (if (looking-at ".*\\<fn\\>.*=>")
919 ;; (+ indent sml-indent-case-arm)))
920 ;; ;; else keep the same indentation as previous line
923 ;; (defun sml-get-paren-indent ()
925 ;; (condition-case ()
928 ;; (if (save-excursion
930 ;; (looking-at sml-indent-starters-reg))
931 ;; (1+ (+ (current-column) sml-indent-level))
932 ;; (1+ (current-column))))
935 ;; (defun sml-inside-comment-or-string-p ()
936 ;; (let ((start (point)))
937 ;; (if (save-excursion
938 ;; (condition-case ()
940 ;; (search-backward "(*")
941 ;; (search-forward "*)")
942 ;; (forward-char -1) ; A "*)" is not inside the comment
943 ;; (> (point) start))
949 ;; (narrow-to-region (progn (beginning-of-line) (point)) start)
950 ;; (condition-case ()
952 ;; (search-forward "\"")
953 ;; (setq numb (1+ numb)))
954 ;; (error (if (and (not (zerop numb))
955 ;; (not (zerop (% numb 2))))
958 ;; (defun sml-find-match-backward (unquoted-this this match)
959 ;; (let ((case-fold-search nil)
961 ;; (pattern (concat this "\\|" match)))
962 ;; (while (not (zerop level))
963 ;; (if (sml-re-search-backward pattern)
965 ;; ((looking-at this) (1+ level))
966 ;; ((looking-at match) (1- level))))
967 ;; ;; The right match couldn't be found
968 ;; (error (concat "Unbalanced: " unquoted-this))))))
970 (defun sml-find-match-indent (this match &optional indented)
972 (sml-find-match-backward this match)
973 (if (or indented (not (sml-dangling-sym)))
975 (sml-indent-default 'noindent))))
977 (defun sml-find-matching-starter (regexp &optional prec)
978 (sml-backward-sexp prec)
979 (while (not (or (looking-at regexp) (bobp)))
980 (sml-backward-sexp prec))
983 ;; (defun sml-re-search-backward (regexpr)
984 ;; (let ((case-fold-search nil) (found t))
985 ;; (if (re-search-backward regexpr nil t)
987 ;; (condition-case ()
988 ;; (while (sml-inside-comment-or-string-p)
989 ;; (re-search-backward regexpr))
990 ;; (error (setq found nil)))
994 (defun sml-comment-indent ()
995 (if (looking-at "^(\\*") ; Existing comment at beginning
996 0 ; of line stays there.
998 (skip-chars-backward " \t")
999 (max (1+ (current-column)) ; Else indent at comment column
1000 comment-column)))) ; except leave at least one space.
1002 ;;; INSERTING PROFORMAS (COMMON SML-FORMS)
1004 (defvar sml-forms-alist
1005 '(("let") ("local") ("case") ("abstype") ("datatype")
1006 ("signature") ("structure") ("functor"))
1007 "*The list of templates to auto-insert.
1009 You can extend this alist to your heart's content. For each additional
1010 template NAME in the list, declare a keyboard macro or function (or
1011 interactive command) called 'sml-form-NAME'.
1013 If 'sml-form-NAME' is a function it takes no arguments and should
1014 insert the template at point\; if this is a command it may accept any
1015 sensible interactive call arguments\; keyboard macros can't take
1016 arguments at all. Apropos keyboard macros, see `name-last-kbd-macro'
1017 and `sml-addto-forms-alist'.
1019 `sml-forms-alist' understands let, local, case, abstype, datatype,
1020 signature, structure, and functor by default.")
1022 ;; See also macros.el in emacs lisp dir.
1024 (defun sml-addto-forms-alist (name)
1025 "Assign a name to the last keyboard macro defined.
1026 Argument NAME is transmogrified to sml-form-NAME which is the symbol
1029 The symbol's function definition becomes the keyboard macro string.
1031 If that works, NAME is added to `sml-forms-alist' so you'll be able to
1032 reinvoke the macro through \\[sml-insert-form]. You might want to save
1033 the macro to use in a later editing session -- see `insert-kbd-macro'
1034 and add these macros to your .emacs file.
1036 See also `edit-kbd-macro' which is bound to \\[edit-kbd-macro]."
1037 (interactive "sName for last kbd macro (\"sml-form-\" will be added): ")
1038 (if (string-equal name "")
1039 (error "No command name given")
1040 (name-last-kbd-macro (intern (concat "sml-form-" name)))
1041 (message (concat "Macro bound to sml-form-" name))
1042 (or (assoc name sml-forms-alist)
1043 (setq sml-forms-alist (cons (list name) sml-forms-alist)))))
1045 ;; at a pinch these could be added to SML/Forms menu through the good
1046 ;; offices of activate-menubar-hook or something... but documentation
1047 ;; of this and/or menu-bar-update-hook is sparse in 19.33. anyway, use
1048 ;; completing read for sml-insert-form prompt...
1050 (defvar sml-last-form "let"
1051 "The most recent sml form inserted.")
1053 (defun sml-insert-form (arg)
1054 "Interactive short-cut to insert a common ML form.
1055 If a perfix argument is given insert a newline and indent first, or
1056 just move to the proper indentation if the line is blank\; otherwise
1057 insert at point (which forces indentation to current column).
1059 The default form to insert is 'whatever you inserted last time'
1060 \(just hit return when prompted\)\; otherwise the command reads with
1061 completion from `sml-forms-alist'."
1063 (let ((name (completing-read
1064 (format "Form to insert: (default %s) " sml-last-form)
1065 sml-forms-alist nil t nil)))
1066 ;; default is whatever the last insert was...
1067 (if (string= name "") (setq name sml-last-form))
1068 (setq sml-last-form name)
1070 (if (save-excursion (beginning-of-line) (looking-at "[ \t]*$"))
1072 (newline-and-indent)))
1073 (cond ((string= name "let") (sml-form-let))
1074 ((string= name "local") (sml-form-local))
1075 ((string= name "case") (sml-form-case))
1076 ((string= name "abstype") (sml-form-abstype))
1077 ((string= name "datatype") (sml-form-datatype))
1078 ((string= name "functor") (sml-form-functor))
1079 ((string= name "structure") (sml-form-structure))
1080 ((string= name "signature") (sml-form-signature))
1082 (let ((template (intern (concat "sml-form-" name))))
1083 (if (fboundp template)
1084 (if (commandp template)
1085 ;; it may be a named kbd macro too
1086 (command-execute template)
1089 (format "Undefined format function: %s" template))))))))
1091 (defun sml-form-let ()
1092 "Insert a `let in end' template."
1094 (sml-let-local "let"))
1096 (defun sml-form-local ()
1097 "Insert a `local in end' template."
1099 (sml-let-local "local"))
1101 (defun sml-let-local (starter)
1102 "Insert a let or local template, depending on STARTER string."
1103 (let ((indent (current-column)))
1105 (insert "\n") (indent-to (+ sml-indent-level indent))
1106 (save-excursion ; so point returns here
1110 (indent-to (+ sml-indent-level indent))
1115 (defun sml-form-case ()
1116 "Insert a case expression template, prompting for the case-expresion."
1118 (let ((expr (read-string "Case expr: "))
1119 (indent (current-column)))
1120 (insert (concat "case " expr))
1124 (indent-to (+ 2 indent))
1127 (indent-to (+ indent sml-indent-level)))
1128 (save-excursion (insert " => "))))
1130 (defun sml-form-signature ()
1131 "Insert a generative signature binding, prompting for the name."
1133 (let ((indent (current-column))
1134 (name (read-string "Signature name: ")))
1135 (insert (concat "signature " name " ="))
1137 (indent-to (+ sml-structure-indent indent))
1139 (indent-to (+ sml-structure-indent sml-indent-level indent))
1142 (indent-to (+ sml-structure-indent indent))
1145 (defun sml-form-structure ()
1146 "Insert a generative structure binding, prompting for the name.
1147 The command also prompts for any signature constraint -- you should
1148 specify \":\" or \":>\" and the constraining signature."
1150 (let ((indent (current-column))
1151 (name (read-string (concat "Structure name: ")))
1152 (signame (read-string "Signature constraint (default none): ")))
1153 (insert (concat "structure " name " "))
1154 (insert (if (string= "" signame) "=" (concat signame " =")))
1156 (indent-to (+ sml-structure-indent indent))
1158 (indent-to (+ sml-structure-indent sml-indent-level indent))
1161 (indent-to (+ sml-structure-indent indent))
1164 (defun sml-form-functor ()
1165 "Insert a genarative functor binding, prompting for the name.
1166 The command also prompts for the required signature constraint -- you
1167 should specify \":\" or \":>\" and the constraining signature."
1169 (let ((indent(current-indentation))
1170 (name (read-string "Name of functor: "))
1171 (signame (read-string "Signature constraint: " ":" )))
1172 (insert (concat "functor " name " () " signame " ="))
1174 (indent-to (+ sml-structure-indent indent))
1176 (indent-to (+ sml-structure-indent sml-indent-level indent))
1177 (save-excursion ; return to () instead?
1179 (indent-to (+ sml-structure-indent indent))
1182 (defun sml-form-datatype ()
1183 "Insert a datatype declaration, prompting for name and type parameter."
1185 (let ((indent (current-indentation))
1186 (type (read-string "Datatype type parameter (default none): "))
1187 (name (read-string (concat "Name of datatype: "))))
1188 (insert (concat "datatype "
1189 (if (string= type "") "" (concat type " "))
1192 (indent-to (+ sml-indent-level indent))))
1194 (defun sml-form-abstype ()
1195 "Insert an abstype declaration, prompting for name and type parameter."
1197 (let ((indent(current-indentation))
1198 (type (read-string "Abstype type parameter (default none): "))
1199 (name (read-string "Name of abstype: ")))
1200 (insert (concat "abstype "
1201 (if (string= type "") "" (concat type " "))
1204 (indent-to (+ sml-indent-level indent))
1209 (indent-to (+ sml-indent-level indent))
1214 ;;; Load the menus, if they can be found on the load-path
1217 (require 'sml-menus)
1218 (error (message "Sorry, not able to load SML mode menus.")))
1220 ;;; & do the user's customisation
1222 (add-hook 'sml-load-hook 'sml-mode-version t)
1224 (run-hooks 'sml-load-hook)
1226 ;;; sml-mode.el has just finished.