]> code.delx.au - gnu-emacs-elpa/blob - sml-mode.el
cdf7759b514ca07b4fbc305099aa66f76c6c3354
[gnu-emacs-elpa] / sml-mode.el
1 ;;; sml-mode.el --- Major mode for editing (Standard) ML -*- lexical-binding: t; coding: utf-8 -*-
2
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
6
7 ;; Author: Lars Bo Nielsen
8 ;; Olin Shivers
9 ;; Fritz Knabe (?)
10 ;; Steven Gilmore (?)
11 ;; Matthew Morley <mjm@scs.leeds.ac.uk> (aka <matthew@verisity.com>)
12 ;; Matthias Blume <blume@cs.princeton.edu> (aka <blume@kurims.kyoto-u.ac.jp>)
13 ;; (Stefan Monnier) <monnier@iro.umontreal.ca>
14 ;; Maintainer: (Stefan Monnier) <monnier@iro.umontreal.ca>
15 ;; Keywords: SML
16
17 ;; This file is not part of GNU Emacs, but it is distributed under the
18 ;; same conditions.
19
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.
24
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.
29
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.
33
34 ;;; Commentary:
35
36 ;;; HISTORY
37
38 ;; Still under construction: History obscure, needs a biographer as
39 ;; well as a M-x doctor. Change Log on request.
40
41 ;; Hacked by Olin Shivers for comint from Lars Bo Nielsen's sml.el.
42
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.
46
47 ;;; DESCRIPTION
48
49 ;; See accompanying info file: sml-mode.info
50
51 ;;; FOR YOUR .EMACS FILE
52
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:
55
56 ;; (add-to-list 'load-path "~jones/lib/emacs/")
57
58 ;; Then to access the commands autoload sml-mode with that command:
59
60 ;; (load "sml-mode-startup")
61
62 ;; sml-mode-hook is run whenever a new sml-mode buffer is created.
63
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.
67
68 ;;; Code:
69
70 (eval-when-compile (require 'cl))
71 (require 'smie nil 'noerror)
72 (require 'sml-prog-proc)
73
74 (defgroup sml ()
75 "Editing SML code."
76 :group 'languages)
77
78 (defcustom sml-indent-level 4
79 "Basic indentation step for SML code."
80 :type 'integer)
81
82 (defcustom sml-indent-args sml-indent-level
83 "Indentation of args placed on a separate line."
84 :type 'integer)
85
86 (defcustom sml-rightalign-and t
87 "If non-nil, right-align `and' with its leader.
88 If nil: If t:
89 datatype a = A datatype a = A
90 and b = B and b = B"
91 :type 'boolean)
92
93 (defvar sml-mode-hook nil
94 "Run upon entering `sml-mode'.
95 This is a good place to put your preferred key bindings.")
96
97 ;;; Autoload functions -- no-doc is another idea cribbed from AucTeX!
98 ;; FIXME-copyright: probably include sml-proc.el in sml-mode.el.
99 (let ((sml-no-doc
100 "This function is part of sml-proc, and has not yet been loaded.
101 Full documentation will be available after autoloading the function."))
102
103 (autoload 'sml-compile "sml-proc" sml-no-doc t)
104 (autoload 'sml-load-file "sml-proc" sml-no-doc t)
105 (autoload 'switch-to-sml "sml-proc" sml-no-doc t)
106 (autoload 'sml-send-region "sml-proc" sml-no-doc t)
107 (autoload 'sml-send-buffer "sml-proc" sml-no-doc t))
108
109 ;; font-lock setup
110
111 (defvar sml-outline-regexp
112 ;; `st' and `si' are to match structure and signature.
113 "\f\\|s[ti]\\|[ \t]*\\(let[ \t]+\\)?\\(fun\\|and\\)\\_>"
114 "Regexp matching a major heading.
115 This actually can't work without extending `outline-minor-mode' with the
116 notion of \"the end of an outline\".")
117
118 ;;
119 ;; Internal defines
120 ;;
121
122 (defvar sml-mode-map
123 (let ((map (make-sparse-keymap)))
124 ;; Text-formatting commands:
125 (define-key map "\C-c\C-m" 'sml-insert-form)
126 (define-key map "\M-|" 'sml-electric-pipe)
127 (define-key map "\M-\ " 'sml-electric-space)
128 (define-key map [backtab] 'sml-back-to-outer-indent)
129 map)
130 "The keymap used in `sml-mode'.")
131
132 (defvar sml-mode-syntax-table
133 (let ((st (make-syntax-table)))
134 (modify-syntax-entry ?\* ". 23n" st)
135 (modify-syntax-entry ?\( "()1" st)
136 (modify-syntax-entry ?\) ")(4" st)
137 (mapc (lambda (c) (modify-syntax-entry c "_" st)) "._'")
138 (mapc (lambda (c) (modify-syntax-entry c "." st)) ",;")
139 ;; `!' is not really a prefix-char, oh well!
140 (mapc (lambda (c) (modify-syntax-entry c "'" st)) "~#!")
141 (mapc (lambda (c) (modify-syntax-entry c "." st)) "%&$+-/:<=>?@`^|")
142 st)
143 "The syntax table used in `sml-mode'.")
144
145
146 (easy-menu-define sml-mode-menu sml-mode-map "Menu used in `sml-mode'."
147 '("SML"
148 ("Process" ;FIXME-copyright.
149 ["Start default ML compiler" run-sml t]
150 ["-" nil nil]
151 ["run CM.make" sml-compile t]
152 ["load ML source file" sml-load-file t]
153 ["switch to ML buffer" switch-to-sml t]
154 ["--" nil nil]
155 ["send buffer contents" sml-send-buffer t]
156 ["send region" sml-send-region t]
157 ["send function" sml-send-function t]
158 ["goto next error" next-error (featurep 'sml-proc)]
159 ["---" nil nil]
160 ["Help for Inferior ML" (describe-function 'inferior-sml-mode)
161 :active (featurep 'sml-proc)])
162 ["insert SML form" sml-insert-form t] ;FIXME-copyright.
163 ("Forms" :filter sml-forms-menu)
164 ("Format/Mode Variables" ;FIXME-copyright.
165 ["indent region" indent-region t]
166 ["outdent" sml-back-to-outer-indent t]
167 ;; ["-" nil nil]
168 ;; ["set indent-level" sml-indent-level t]
169 ;; ["set pipe-indent" sml-pipe-indent t]
170 ;; ["--" nil nil]
171 ;; ["toggle type-of-indent" sml-type-of-indent t]
172 ;; ["toggle nested-if-indent" sml-nested-if-indent t]
173 )
174 ["-----" nil nil]
175 ["SML mode help (brief)" describe-mode t])) ;FIXME-copyright.
176
177 ;;
178 ;; Regexps
179 ;;
180
181 (defun sml-syms-re (syms)
182 (concat "\\_<" (regexp-opt syms t) "\\_>"))
183
184 ;;
185
186 (defconst sml-module-head-syms
187 '("signature" "structure" "functor" "abstraction"))
188
189
190 (defconst sml-=-starter-syms
191 (list* "|" "val" "fun" "and" "datatype" "type" "abstype" "eqtype"
192 sml-module-head-syms)
193 "Symbols that can be followed by a `='.")
194 (defconst sml-=-starter-re
195 (concat "\\S.|\\S.\\|" (sml-syms-re (cdr sml-=-starter-syms)))
196 "Symbols that can be followed by a `='.")
197
198 (defconst sml-non-nested-of-starter-re
199 (sml-syms-re '("datatype" "abstype" "exception"))
200 "Symbols that can introduce an `of' that shouldn't behave like a paren.")
201
202 (defconst sml-starters-syms
203 (append sml-module-head-syms
204 '("abstype" "datatype" "exception" "fun"
205 "local" "infix" "infixr" "sharing" "nonfix"
206 "open" "type" "val" "and"
207 "withtype" "with"))
208 "The starters of new expressions.")
209
210 (defconst sml-pipeheads
211 '("|" "of" "fun" "fn" "and" "handle" "datatype" "abstype")
212 "A `|' corresponds to one of these.")
213
214 (defconst sml-keywords-regexp
215 (sml-syms-re '("abstraction" "abstype" "and" "andalso" "as" "before" "case"
216 "datatype" "else" "end" "eqtype" "exception" "do" "fn"
217 "fun" "functor" "handle" "if" "in" "include" "infix"
218 "infixr" "let" "local" "nonfix" "o" "of" "op" "open" "orelse"
219 "overload" "raise" "rec" "sharing" "sig" "signature"
220 "struct" "structure" "then" "type" "val" "where" "while"
221 "with" "withtype"))
222 "A regexp that matches any and all keywords of SML.")
223
224 (eval-and-compile
225 (defconst sml-id-re "\\sw\\(?:\\sw\\|\\s_\\)*"))
226
227 (defconst sml-tyvarseq-re
228 (concat "\\(\\('+" sml-id-re "\\|(\\([,']\\|" sml-id-re
229 "\\|\\s-\\)+)\\)\\s-+\\)?"))
230
231 ;;; Font-lock settings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
232
233 (defcustom sml-font-lock-symbols nil
234 "Display \\ and -> and such using symbols in fonts.
235 This may sound like a neat trick, but be extra careful: it changes the
236 alignment and can thus lead to nasty surprises w.r.t layout."
237 :type 'boolean)
238
239 (defconst sml-font-lock-symbols-alist
240 '(("fn" . ?λ)
241 ("andalso" . ?∧) ;; ?⋀
242 ("orelse" . ?∨) ;; ?⋁
243 ;; ("as" . ?≡)
244 ("not" . ?¬)
245 ("div" . ?÷)
246 ("*" . ?×)
247 ("o" . ?○)
248 ("->" . ?→)
249 ("=>" . ?⇒)
250 ("<-" . ?←)
251 ("<>" . ?≠)
252 (">=" . ?≥)
253 ("<=" . ?≤)
254 ("..." . ?⋯)
255 ;; ("::" . ?∷)
256 ;; Some greek letters for type parameters.
257 ("'a" . ?α)
258 ("'b" . ?β)
259 ("'c" . ?γ)
260 ("'d" . ?δ)
261 ))
262
263 (defun sml-font-lock-compose-symbol ()
264 "Compose a sequence of ascii chars into a symbol.
265 Regexp match data 0 points to the chars."
266 ;; Check that the chars should really be composed into a symbol.
267 (let* ((start (match-beginning 0))
268 (end (match-end 0))
269 (syntaxes (if (eq (char-syntax (char-after start)) ?w)
270 '(?w) '(?. ?\\))))
271 (if (or (memq (char-syntax (or (char-before start) ?\ )) syntaxes)
272 (memq (char-syntax (or (char-after end) ?\ )) syntaxes)
273 (memq (get-text-property start 'face)
274 '(font-lock-doc-face font-lock-string-face
275 font-lock-comment-face)))
276 ;; No composition for you. Let's actually remove any composition
277 ;; we may have added earlier and which is now incorrect.
278 (remove-text-properties start end '(composition))
279 ;; That's a symbol alright, so add the composition.
280 (compose-region start end (cdr (assoc (match-string 0)
281 sml-font-lock-symbols-alist)))))
282 ;; Return nil because we're not adding any face property.
283 nil)
284
285 (defun sml-font-lock-symbols-keywords ()
286 (when sml-font-lock-symbols
287 `((,(regexp-opt (mapcar 'car sml-font-lock-symbols-alist) t)
288 (0 (sml-font-lock-compose-symbol))))))
289
290 ;; The font lock regular expressions.
291
292 (defconst sml-font-lock-keywords
293 `(;;(sml-font-comments-and-strings)
294 (,(concat "\\_<\\(fun\\|and\\)\\s-+" sml-tyvarseq-re
295 "\\(" sml-id-re "\\)\\s-+[^ \t\n=]")
296 (1 font-lock-keyword-face)
297 (6 font-lock-function-name-face))
298 (,(concat "\\_<\\(\\(data\\|abs\\|with\\|eq\\)?type\\)\\s-+"
299 sml-tyvarseq-re "\\(" sml-id-re "\\)")
300 (1 font-lock-keyword-face)
301 (7 font-lock-type-def-face))
302 (,(concat "\\_<\\(val\\)\\s-+\\(" sml-id-re "\\_>\\s-*\\)?\\("
303 sml-id-re "\\)\\s-*[=:]")
304 (1 font-lock-keyword-face)
305 ;;(6 font-lock-variable-def-face nil t)
306 (3 font-lock-variable-name-face))
307 (,(concat "\\_<\\(structure\\|functor\\|abstraction\\)\\s-+\\("
308 sml-id-re "\\)")
309 (1 font-lock-keyword-face)
310 (2 font-lock-module-def-face))
311 (,(concat "\\_<\\(signature\\)\\s-+\\(" sml-id-re "\\)")
312 (1 font-lock-keyword-face)
313 (2 font-lock-interface-def-face))
314
315 (,sml-keywords-regexp . font-lock-keyword-face)
316 ,@(sml-font-lock-symbols-keywords))
317 "Regexps matching standard SML keywords.")
318
319 (defface font-lock-type-def-face
320 '((t (:bold t)))
321 "Font Lock mode face used to highlight type definitions."
322 :group 'font-lock-highlighting-faces)
323 (defvar font-lock-type-def-face 'font-lock-type-def-face
324 "Face name to use for type definitions.")
325
326 (defface font-lock-module-def-face
327 '((t (:bold t)))
328 "Font Lock mode face used to highlight module definitions."
329 :group 'font-lock-highlighting-faces)
330 (defvar font-lock-module-def-face 'font-lock-module-def-face
331 "Face name to use for module definitions.")
332
333 (defface font-lock-interface-def-face
334 '((t (:bold t)))
335 "Font Lock mode face used to highlight interface definitions."
336 :group 'font-lock-highlighting-faces)
337 (defvar font-lock-interface-def-face 'font-lock-interface-def-face
338 "Face name to use for interface definitions.")
339
340 ;;
341 ;; Code to handle nested comments and unusual string escape sequences
342 ;;
343
344 (defvar sml-syntax-prop-table
345 (let ((st (make-syntax-table)))
346 (modify-syntax-entry ?\\ "." st)
347 (modify-syntax-entry ?* "." st)
348 st)
349 "Syntax table for text-properties")
350
351 (defconst sml-font-lock-syntactic-keywords
352 `(("^\\s-*\\(\\\\\\)" (1 ',sml-syntax-prop-table))))
353
354 (defconst sml-font-lock-defaults
355 '(sml-font-lock-keywords nil nil nil nil
356 (font-lock-syntactic-keywords . sml-font-lock-syntactic-keywords)))
357
358
359 ;;; Indentation with SMIE
360
361 (defconst sml-smie-grammar
362 ;; We have several problem areas where SML's syntax can't be handled by an
363 ;; operator precedence grammar:
364 ;;
365 ;; "= A before B" is "= A) before B" if this is the
366 ;; `boolean-=' but it is "= (A before B)" if it's the `definitional-='.
367 ;; We can work around the problem by tweaking the lexer to return two
368 ;; different tokens for the two different kinds of `='.
369 ;; "of A | B" in a "case" we want "of (A | B, but in a `datatype'
370 ;; we want "of A) | B".
371 ;; "= A | B" can be "= A ) | B" if the = is from a "fun" definition,
372 ;; but it is "= (A | B" if it is a `datatype' definition (of course, if
373 ;; the previous token introducing the = is `and', deciding whether
374 ;; it's a datatype or a function requires looking even further back).
375 ;; "functor foo (...) where type a = b = ..." the first `=' looks very much
376 ;; like a `definitional-=' even tho it's just an equality constraint.
377 ;; Currently I don't even try to handle `where' at all.
378 (smie-prec2->grammar
379 (smie-merge-prec2s
380 (smie-bnf->prec2
381 '((exp ("if" exp "then" exp "else" exp)
382 ("case" exp "of" branches)
383 ("let" decls "in" cmds "end")
384 ("struct" decls "end")
385 ("sig" decls "end")
386 (sexp)
387 (sexp "handle" branches)
388 ("fn" sexp "=>" exp))
389 ;; "simple exp"s are the ones that can appear to the left of `handle'.
390 (sexp (sexp ":" type) ("(" exps ")")
391 (sexp "orelse" sexp)
392 (marg ":>" type)
393 (sexp "andalso" sexp))
394 (cmds (cmds ";" cmds) (exp))
395 (exps (exps "," exps) (exp)) ; (exps ";" exps)
396 (branches (sexp "=>" exp) (branches "|" branches))
397 ;; Operator precedence grammars handle separators much better then
398 ;; starters/terminators, so let's pretend that let/fun are separators.
399 (decls (sexp "d=" exp)
400 (sexp "d=" databranches)
401 (funbranches "|" funbranches)
402 (sexp "=of" type) ;After "exception".
403 ;; FIXME: Just like PROCEDURE in Pascal and Modula-2, this
404 ;; interacts poorly with the other constructs since I
405 ;; can't make "local" a separator like fun/val/type/...
406 ("local" decls "in" decls "end")
407 ;; (decls "local" decls "in" decls "end")
408 (decls "functor" decls)
409 (decls "signature" decls)
410 (decls "structure" decls)
411 (decls "type" decls)
412 (decls "open" decls)
413 (decls "and" decls)
414 (decls "infix" decls)
415 (decls "infixr" decls)
416 (decls "nonfix" decls)
417 (decls "abstype" decls)
418 (decls "datatype" decls)
419 (decls "exception" decls)
420 (decls "fun" decls)
421 (decls "val" decls))
422 (type (type "->" type)
423 (type "*" type))
424 (funbranches (sexp "d=" exp))
425 (databranches (sexp "=of" type) (databranches "d|" databranches))
426 ;; Module language.
427 ;; (mexp ("functor" marg "d=" mexp)
428 ;; ("structure" marg "d=" mexp)
429 ;; ("signature" marg "d=" mexp))
430 (marg (marg ":" type) (marg ":>" type))
431 (toplevel (decls) (exp) (toplevel ";" toplevel)))
432 ;; '(("local" . opener))
433 ;; '((nonassoc "else") (right "handle"))
434 '((nonassoc "of") (assoc "|")) ; "case a of b => case c of d => e | f"
435 '((nonassoc "handle") (assoc "|")) ; Idem for "handle".
436 '((assoc "->") (assoc "*"))
437 '((assoc "val" "fun" "type" "datatype" "abstype" "open" "infix" "infixr"
438 "nonfix" "functor" "signature" "structure" "exception"
439 ;; "local"
440 )
441 (assoc "and"))
442 '((assoc "orelse") (assoc "andalso") (nonassoc ":"))
443 '((assoc ";")) '((assoc ",")) '((assoc "d|")))
444
445 (smie-precs->prec2
446 '((nonassoc "andalso") ;To anchor the prec-table.
447 (assoc "before") ;0
448 (assoc ":=" "o") ;3
449 (nonassoc ">" ">=" "<>" "<" "<=" "=") ;4
450 (assoc "::" "@") ;5
451 (assoc "+" "-" "^") ;6
452 (assoc "/" "*" "quot" "rem" "div" "mod") ;7
453 (nonassoc " -dummy- "))) ;Bogus anchor at the end.
454 )))
455
456 (defvar sml-indent-separator-outdent 2)
457
458 (defun sml-smie-rules (kind token)
459 ;; I much preferred the pcase version of the code, especially while
460 ;; edebugging the code. But that will have to wait until we get rid of
461 ;; support for Emacs-23.
462 (case kind
463 (:elem (case token
464 (basic sml-indent-level)
465 (args sml-indent-args)))
466 (:list-intro (member token '("fn")))
467 (:after
468 (cond
469 ((equal token "struct") 0)
470 ((equal token "=>") (if (smie-rule-hanging-p) 0 2))
471 ((equal token "in") (if (smie-rule-parent-p "local") 0))
472 ((equal token "of") 3)
473 ((member token '("(" "{" "[")) (if (not (smie-rule-hanging-p)) 2))
474 ((equal token "else") (if (smie-rule-hanging-p) 0)) ;; (:next "if" 0)
475 ((member token '("|" "d|" ";" ",")) (smie-rule-separator kind))
476 ((equal token "d=")
477 (if (and (smie-rule-parent-p "val") (smie-rule-next-p "fn")) -3))))
478 (:before
479 (cond
480 ((equal token "=>") (if (smie-rule-parent-p "fn") 3))
481 ((equal token "of") 1)
482 ;; In case the language is extended to allow a | directly after of.
483 ((and (equal token "|") (smie-rule-prev-p "of")) 1)
484 ((member token '("|" "d|" ";" ",")) (smie-rule-separator kind))
485 ;; Treat purely syntactic block-constructs as being part of their parent,
486 ;; when the opening statement is hanging.
487 ((member token '("let" "(" "[" "{"))
488 (if (smie-rule-hanging-p) (smie-rule-parent)))
489 ;; Treat if ... else if ... as a single long syntactic construct.
490 ;; Similarly, treat fn a => fn b => ... as a single construct.
491 ((member token '("if" "fn"))
492 (and (not (smie-rule-bolp))
493 (smie-rule-prev-p (if (equal token "if") "else" "=>"))
494 (smie-rule-parent)))
495 ((equal token "and")
496 ;; FIXME: maybe "and" (c|sh)ould be handled as an smie-separator.
497 (cond
498 ((smie-rule-parent-p "datatype") (if sml-rightalign-and 5 0))
499 ((smie-rule-parent-p "fun" "val") 0)))
500 ((equal token "d=")
501 (cond
502 ((smie-rule-parent-p "datatype") (if (smie-rule-bolp) 2))
503 ((smie-rule-parent-p "structure" "signature") 0)))
504 ;; Indent an expression starting with "local" as if it were starting
505 ;; with "fun".
506 ((equal token "local") (smie-indent-keyword "fun"))
507 ;; FIXME: type/val/fun/... are separators but "local" is not, even though
508 ;; it appears in the same list. Try to fix up the problem by hand.
509 ;; ((or (equal token "local")
510 ;; (equal (cdr (assoc token smie-grammar))
511 ;; (cdr (assoc "fun" smie-grammar))))
512 ;; (let ((parent (save-excursion (smie-backward-sexp))))
513 ;; (when (or (and (equal (nth 2 parent) "local")
514 ;; (null (car parent)))
515 ;; (progn
516 ;; (setq parent (save-excursion (smie-backward-sexp "fun")))
517 ;; (eq (car parent) (nth 1 (assoc "fun" smie-grammar)))))
518 ;; (goto-char (nth 1 parent))
519 ;; (cons 'column (smie-indent-virtual)))))
520 ))))
521
522 (defun sml-smie-definitional-equal-p ()
523 "Figure out which kind of \"=\" this is.
524 Assumes point is right before the = sign."
525 ;; The idea is to look backward for the first occurrence of a token that
526 ;; requires a definitional "=" and then see if there's such a definitional
527 ;; equal between that token and ourselves (in which case we're not
528 ;; a definitional = ourselves).
529 ;; The "search for =" is naive and will match "=>" and "<=", but it turns
530 ;; out to be OK in practice because such tokens very rarely (if ever) appear
531 ;; between the =-starter and the corresponding definitional equal.
532 ;; One known problem case is code like:
533 ;; "functor foo (structure s : S) where type t = s.t ="
534 ;; where the "type t = s.t" is mistaken for a type definition.
535 (let ((re (concat "\\(" sml-=-starter-re "\\)\\|=")))
536 (save-excursion
537 (and (re-search-backward re nil t)
538 (or (match-beginning 1)
539 ;; If we first hit a "=", then that = is probably definitional
540 ;; and we're an equality, but not necessarily. One known
541 ;; problem case is code like:
542 ;; "functor foo (structure s : S) where type t = s.t ="
543 ;; where the first = is more like an equality (tho it doesn't
544 ;; matter much) and the second is definitional.
545 ;;
546 ;; FIXME: The test below could be used to recognize that the
547 ;; second = is not a mere equality, but that's not enough to
548 ;; parse the construct properly: we'd need something
549 ;; like a third kind of = token for structure definitions, in
550 ;; order for the parser to be able to skip the "type t = s.t"
551 ;; as a sub-expression.
552 ;;
553 ;; (and (not (looking-at "=>"))
554 ;; (not (eq ?< (char-before))) ;Not a <=
555 ;; (re-search-backward re nil t)
556 ;; (match-beginning 1)
557 ;; (equal "type" (buffer-substring (- (match-end 1) 4)
558 ;; (match-end 1))))
559 )))))
560
561 (defun sml-smie-non-nested-of-p ()
562 ;; FIXME: Maybe datatype-|-p makes this nested-of business unnecessary.
563 "Figure out which kind of \"of\" this is.
564 Assumes point is right before the \"of\" symbol."
565 (save-excursion
566 (and (re-search-backward (concat "\\(" sml-non-nested-of-starter-re
567 "\\)\\|\\_<case\\_>") nil t)
568 (match-beginning 1))))
569
570 (defun sml-smie-datatype-|-p ()
571 "Figure out which kind of \"|\" this is.
572 Assumes point is right before the | symbol."
573 (save-excursion
574 (forward-char 1) ;Skip the |.
575 (let ((after-type-def
576 '("|" "of" "in" "datatype" "and" "exception" "abstype" "infix"
577 "infixr" "nonfix" "local" "val" "fun" "structure" "functor"
578 "signature")))
579 (or (member (sml-smie-forward-token-1) after-type-def) ;Skip the tag.
580 (member (sml-smie-forward-token-1) after-type-def)))))
581
582 (defun sml-smie-forward-token-1 ()
583 (forward-comment (point-max))
584 (buffer-substring-no-properties
585 (point)
586 (progn
587 (or (/= 0 (skip-syntax-forward "'w_"))
588 (skip-syntax-forward ".'"))
589 (point))))
590
591 (defun sml-smie-forward-token ()
592 (let ((sym (sml-smie-forward-token-1)))
593 (cond
594 ((equal "op" sym)
595 (concat "op " (sml-smie-forward-token-1)))
596 ((member sym '("|" "of" "="))
597 ;; The important lexer for indentation's performance is the backward
598 ;; lexer, so for the forward lexer we delegate to the backward one.
599 (save-excursion (sml-smie-backward-token)))
600 (t sym))))
601
602 (defun sml-smie-backward-token-1 ()
603 (forward-comment (- (point)))
604 (buffer-substring-no-properties
605 (point)
606 (progn
607 (or (/= 0 (skip-syntax-backward ".'"))
608 (skip-syntax-backward "'w_"))
609 (point))))
610
611 (defun sml-smie-backward-token ()
612 (let ((sym (sml-smie-backward-token-1)))
613 (unless (zerop (length sym))
614 ;; FIXME: what should we do if `sym' = "op" ?
615 (let ((point (point)))
616 (if (equal "op" (sml-smie-backward-token-1))
617 (concat "op " sym)
618 (goto-char point)
619 (cond
620 ((string= sym "=") (if (sml-smie-definitional-equal-p) "d=" "="))
621 ((string= sym "of") (if (sml-smie-non-nested-of-p) "=of" "of"))
622 ((string= sym "|") (if (sml-smie-datatype-|-p) "d|" "|"))
623 (t sym)))))))
624
625 ;;;;
626 ;;;; Imenu support
627 ;;;;
628
629 (defvar sml-imenu-regexp
630 (concat "^[ \t]*\\(let[ \t]+\\)?"
631 (regexp-opt (append sml-module-head-syms
632 '("and" "fun" "datatype" "abstype" "type")) t)
633 "\\_>"))
634
635 (defun sml-imenu-create-index ()
636 (let (alist)
637 (goto-char (point-max))
638 (while (re-search-backward sml-imenu-regexp nil t)
639 (save-excursion
640 (let ((kind (match-string 2))
641 (column (progn (goto-char (match-beginning 2)) (current-column)))
642 (location
643 (progn (goto-char (match-end 0))
644 (forward-comment (point-max))
645 (when (looking-at sml-tyvarseq-re)
646 (goto-char (match-end 0)))
647 (point)))
648 (name (sml-smie-forward-token)))
649 ;; Eliminate trivial renamings.
650 (when (or (not (member kind '("structure" "signature")))
651 (progn (search-forward "=")
652 (forward-comment (point-max))
653 (looking-at "sig\\|struct")))
654 (push (cons (concat (make-string (/ column 2) ?\ ) name) location)
655 alist)))))
656 alist))
657
658 ;;; Prog-Proc support. ;FIXME-copyright.
659
660 (defcustom sml-program-name "sml"
661 "Program to run as Standard ML read-eval-print loop."
662 :type 'string)
663
664 (defcustom sml-default-arg ""
665 "Default command line option to pass to `sml-program-name', if any."
666 :type 'string)
667
668 (defcustom sml-host-name ""
669 "Host on which to run `sml-program-name'."
670 :type 'string)
671
672 (defcustom sml-config-file "~/.smlproc.sml"
673 "File that should be fed to the ML process when started."
674 :type 'string)
675
676
677 (defcustom sml-prompt-regexp "^[-=>#] *"
678 "Regexp used to recognise prompts in the inferior ML process."
679 :type 'regexp)
680
681 ;; FIXME: Try to auto-detect the process and set those vars accordingly.
682
683 (defvar sml-use-command "use \"%s\""
684 "Template for loading a file into the inferior ML process.
685 Set to \"use \\\"%s\\\"\" for SML/NJ or Edinburgh ML;
686 set to \"PolyML.use \\\"%s\\\"\" for Poly/ML, etc.")
687
688 (defvar sml-cd-command "OS.FileSys.chDir \"%s\""
689 "Command template for changing working directories under ML.
690 Set this to nil if your compiler can't change directories.
691
692 The format specifier \"%s\" will be converted into the directory name
693 specified when running the command \\[sml-cd].")
694
695 (defvar sml-error-regexp-alist
696 `( ;; Poly/ML messages
697 ("^\\(Error\\|Warning:\\) in '\\(.+\\)', line \\([0-9]+\\)" 2 3)
698 ;; Moscow ML
699 ("^File \"\\([^\"]+\\)\", line \\([0-9]+\\)\\(-\\([0-9]+\\)\\)?, characters \\([0-9]+\\)-\\([0-9]+\\):" 1 2 5)
700 ;; SML/NJ: the file-pattern is anchored to avoid
701 ;; pathological behavior with very long lines.
702 ("^[-= ]*\\(.*[^\n)]\\)\\( (.*)\\)?:\\([0-9]+\\)\\.\\([0-9]+\\)\\(-\\([0-9]+\\)\\.\\([0-9]+\\)\\)? \\(Error\\|Warnin\\(g\\)\\): .*" 1
703 (3 . 6) (4 . 7) (9))
704 ;; SML/NJ's exceptions: see above.
705 ("^ +\\(raised at: \\)?\\(.+\\):\\([0-9]+\\)\\.\\([0-9]+\\)\\(-\\([0-9]+\\)\\.\\([0-9]+\\)\\)" 2
706 (3 . 6) (4 . 7)))
707 "Alist that specifies how to match errors in compiler output.
708 See `compilation-error-regexp-alist' for a description of the format.")
709
710 (defconst sml-pp-functions
711 (sml-prog-proc-make :name "SML"
712 :run (lambda () (call-interactively #'sml-run))
713 :load-cmd (lambda (file)
714 ;; `sml-use-command' was defined a long time
715 ;; ago not to include a final semi-colon.
716 (concat (format sml-use-command file) ";"))
717 :chdir-cmd (lambda (dir)
718 ;; `sml-cd-command' was defined a long time
719 ;; ago not to include a final semi-colon.
720 (concat (format sml-cd-command dir) ";"))))
721
722 ;; font-lock support
723 (defconst inferior-sml-font-lock-keywords
724 `(;; prompt and following interactive command
725 ;; FIXME: Actually, this should already be taken care of by comint.
726 (,(concat "\\(" sml-prompt-regexp "\\)\\(.*\\)")
727 (1 font-lock-prompt-face)
728 (2 font-lock-command-face keep))
729 ;; CM's messages
730 ("^\\[\\(.*GC #.*\n\\)*.*\\]" . font-lock-comment-face)
731 ;; SML/NJ's irritating GC messages
732 ("^GC #.*" . font-lock-comment-face))
733 "Font-locking specification for inferior SML mode.")
734
735 (defface font-lock-prompt-face
736 '((t (:bold t)))
737 "Font Lock mode face used to highlight prompts."
738 :group 'font-lock-highlighting-faces)
739 (defvar font-lock-prompt-face 'font-lock-prompt-face
740 "Face name to use for prompts.")
741
742 (defface font-lock-command-face
743 '((t (:bold t)))
744 "Font Lock mode face used to highlight interactive commands."
745 :group 'font-lock-highlighting-faces)
746 (defvar font-lock-command-face 'font-lock-command-face
747 "Face name to use for interactive commands.")
748
749 (defconst inferior-sml-font-lock-defaults
750 '(inferior-sml-font-lock-keywords nil nil nil nil))
751
752 (defun sml--read-run-cmd ()
753 (list
754 (read-string "ML command: " sml-program-name)
755 (if (or current-prefix-arg (> (length sml-default-arg) 0))
756 (read-string "Any args: " sml-default-arg)
757 sml-default-arg)
758 (if (or current-prefix-arg (> (length sml-host-name) 0))
759 (read-string "On host: " sml-host-name)
760 sml-host-name)))
761
762 (defun sml-run (cmd arg &optional host)
763 "Run the program CMD with given arguments ARG.
764 The command is run in buffer *CMD* using mode `inferior-sml-mode'.
765 If the buffer already exists and has a running process, then
766 just go to this buffer.
767
768 If a prefix argument is used, the user is also prompted for a HOST
769 on which to run CMD using `remote-shell-program'.
770
771 \(Type \\[describe-mode] in the process's buffer for a list of commands.)"
772 (interactive (sml--read-run-cmd))
773 (let* ((pname (file-name-nondirectory cmd))
774 (args (split-string arg))
775 (file (when (and sml-config-file (file-exists-p sml-config-file))
776 sml-config-file)))
777 ;; and this -- to keep these as defaults even if
778 ;; they're set in the mode hooks.
779 (setq sml-program-name cmd)
780 (setq sml-default-arg arg)
781 (setq sml-host-name host)
782 ;; For remote execution, use `remote-shell-program'
783 (when (> (length host) 0)
784 (setq args (list* host "cd" default-directory ";" cmd args))
785 (setq cmd remote-shell-program))
786 ;; Go for it.
787 (save-current-buffer
788 (let ((exec-path (if (and (file-name-directory cmd)
789 (not (file-name-absolute-p cmd)))
790 ;; If the command has slashes, make sure we
791 ;; first look relative to the current directory.
792 ;; Emacs-21 does it for us, but not Emacs-20.
793 (cons default-directory exec-path) exec-path)))
794 (pop-to-buffer (apply 'make-comint pname cmd file args)))
795
796 (inferior-sml-mode)
797 (goto-char (point-max))
798 (current-buffer))))
799
800 (defvar inferior-sml-mode-map
801 (let ((map (make-sparse-keymap)))
802 (set-keymap-parent map comint-mode-map)
803 (define-key map "\C-c\C-s" 'run-sml)
804 (define-key map "\C-c\C-l" 'sml-load-file)
805 (define-key map "\t" 'completion-at-point)
806 map)
807 "Keymap for inferior-sml mode")
808
809
810 (declare-function smerge-refine-subst "smerge-mode"
811 (beg1 end1 beg2 end2 props-c))
812
813 (defun inferior-sml-next-error-hook ()
814 ;; Try to recognize SML/NJ type error message and to highlight finely the
815 ;; difference between the two types (in case they're large, it's not
816 ;; always obvious to spot it).
817 ;;
818 ;; Sample messages:
819 ;;
820 ;; Data.sml:31.9-33.33 Error: right-hand-side of clause doesn't agree with function result type [tycon mismatch]
821 ;; expression: Hstring
822 ;; result type: Hstring * int
823 ;; in declaration:
824 ;; des2hs = (fn SYM_ID hs => hs
825 ;; | SYM_OP hs => hs
826 ;; | SYM_CHR hs => hs)
827 ;; Data.sml:35.44-35.63 Error: operator and operand don't agree [tycon mismatch]
828 ;; operator domain: Hstring * Hstring
829 ;; operand: (Hstring * int) * (Hstring * int)
830 ;; in expression:
831 ;; HSTRING.ieq (h1,h2)
832 ;; vparse.sml:1861.6-1922.14 Error: case object and rules don't agree [tycon mismatch]
833 ;; rule domain: STConstraints list list option
834 ;; object: STConstraints list option
835 ;; in expression:
836 (save-current-buffer
837 (when (and (derived-mode-p 'sml-mode 'inferior-sml-mode)
838 (boundp 'next-error-last-buffer)
839 (bufferp next-error-last-buffer)
840 (set-buffer next-error-last-buffer)
841 (derived-mode-p 'inferior-sml-mode)
842 ;; The position of `point' is not guaranteed :-(
843 (looking-at (concat ".*\\[tycon mismatch\\]\n"
844 " \\(operator domain\\|expression\\|rule domain\\): +")))
845 (require 'smerge-mode)
846 (save-excursion
847 (let ((b1 (match-end 0))
848 e1 b2 e2)
849 (when (re-search-forward "\n in \\(expression\\|declaration\\):\n"
850 nil t)
851 (setq e2 (match-beginning 0))
852 (when (re-search-backward
853 "\n \\(operand\\|result type\\|object\\): +"
854 b1 t)
855 (setq e1 (match-beginning 0))
856 (setq b2 (match-end 0))
857 (smerge-refine-subst b1 e1 b2 e2
858 '((face . smerge-refined-change))))))))))
859
860 (define-derived-mode inferior-sml-mode sml-prog-proc-comint-mode "Inferior-SML"
861 "Major mode for interacting with an inferior ML process.
862
863 The following commands are available:
864 \\{inferior-sml-mode-map}
865
866 An ML process can be fired up (again) with \\[sml].
867
868 Customisation: Entry to this mode runs the hooks on `comint-mode-hook'
869 and `inferior-sml-mode-hook' (in that order).
870
871 Variables controlling behaviour of this mode are
872
873 `sml-program-name' (default \"sml\")
874 Program to run as ML.
875
876 `sml-use-command' (default \"use \\\"%s\\\"\")
877 Template for loading a file into the inferior ML process.
878
879 `sml-cd-command' (default \"System.Directory.cd \\\"%s\\\"\")
880 ML command for changing directories in ML process (if possible).
881
882 `sml-prompt-regexp' (default \"^[\\-=] *\")
883 Regexp used to recognise prompts in the inferior ML process.
884
885 You can send text to the inferior ML process from other buffers containing
886 ML source.
887 `switch-to-sml' switches the current buffer to the ML process buffer.
888 `sml-send-function' sends the current *paragraph* to the ML process.
889 `sml-send-region' sends the current region to the ML process.
890
891 Prefixing the sml-send-<whatever> commands with \\[universal-argument]
892 causes a switch to the ML process buffer after sending the text.
893
894 For information on running multiple processes in multiple buffers, see
895 documentation for variable `sml-buffer'.
896
897 Commands:
898 RET after the end of the process' output sends the text from the
899 end of process to point.
900 RET before the end of the process' output copies the current line
901 to the end of the process' output, and sends it.
902 DEL converts tabs to spaces as it moves back.
903 TAB file name completion, as in shell-mode, etc.."
904 (setq comint-prompt-regexp sml-prompt-regexp)
905 (sml-mode-variables)
906
907 ;; We have to install it globally, 'cause it's run in the *source* buffer :-(
908 (add-hook 'next-error-hook 'inferior-sml-next-error-hook)
909
910 ;; Make TAB add a " rather than a space at the end of a file name.
911 (set (make-local-variable 'comint-completion-addsuffix) '(?/ . ?\"))
912
913 (set (make-local-variable 'font-lock-defaults)
914 inferior-sml-font-lock-defaults)
915
916 ;; Compilation support (used for `next-error').
917 (set (make-local-variable 'compilation-error-regexp-alist)
918 sml-error-regexp-alist)
919 ;; FIXME: move it to sml-mode?
920 (set (make-local-variable 'compilation-error-screen-columns) nil)
921
922 (setq mode-line-process '(": %s")))
923
924 (defcustom sml-compile-command "CM.make()"
925 "The command used by default by `sml-compile'.
926 See also `sml-compile-commands-alist'.")
927
928 (defcustom sml-compile-commands-alist
929 '(("CMB.make()" . "all-files.cm")
930 ("CMB.make()" . "pathconfig")
931 ("CM.make()" . "sources.cm")
932 ("use \"load-all\"" . "load-all"))
933 "Commands used by default by `sml-compile'.
934 Each command is associated with its \"main\" file.
935 It is perfectly OK to associate several files with a command or several
936 commands with the same file.")
937
938 (defun sml-compile (command &optional and-go)
939 "Pass a COMMAND to the SML process to compile the current program.
940
941 You can then use the command \\[next-error] to find the next error message
942 and move to the source code that caused it.
943
944 Interactively, prompts for the command if `compilation-read-command' is
945 non-nil. With prefix arg, always prompts.
946
947 Prefix arg AND-GO also means to `switch-to-sml' afterwards."
948 (interactive
949 (let* ((dir default-directory)
950 (cmd "cd \"."))
951 ;; Look for files to determine the default command.
952 (while (and (stringp dir)
953 (dolist (cf sml-compile-commands-alist 1)
954 (when (file-exists-p (expand-file-name (cdr cf) dir))
955 (setq cmd (concat cmd "\"; " (car cf))) (return nil))))
956 (let ((newdir (file-name-directory (directory-file-name dir))))
957 (setq dir (unless (equal newdir dir) newdir))
958 (setq cmd (concat cmd "/.."))))
959 (setq cmd
960 (cond
961 ((local-variable-p 'sml-compile-command) sml-compile-command)
962 ((string-match "^\\s-*cd\\s-+\"\\.\"\\s-*;\\s-*" cmd)
963 (substring cmd (match-end 0)))
964 ((string-match "^\\s-*cd\\s-+\"\\(\\./\\)" cmd)
965 (replace-match "" t t cmd 1))
966 ((string-match ";" cmd) cmd)
967 (t sml-compile-command)))
968 ;; code taken from compile.el
969 (if (or compilation-read-command current-prefix-arg)
970 (list (read-from-minibuffer "Compile command: "
971 cmd nil nil '(compile-history . 1)))
972 (list cmd))))
973 ;; ;; now look for command's file to determine the directory
974 ;; (setq dir default-directory)
975 ;; (while (and (stringp dir)
976 ;; (dolist (cf sml-compile-commands-alist t)
977 ;; (when (and (equal cmd (car cf))
978 ;; (file-exists-p (expand-file-name (cdr cf) dir)))
979 ;; (return nil))))
980 ;; (let ((newdir (file-name-directory (directory-file-name dir))))
981 ;; (setq dir (unless (equal newdir dir) newdir))))
982 ;; (setq dir (or dir default-directory))
983 ;; (list cmd dir)))
984 (set (make-local-variable 'sml-compile-command) command)
985 (save-some-buffers (not compilation-ask-about-save) nil)
986 (let ((dir default-directory))
987 (when (string-match "^\\s-*cd\\s-+\"\\([^\"]+\\)\"\\s-*;" command)
988 (setq dir (match-string 1 command))
989 (setq command (replace-match "" t t command)))
990 (setq dir (expand-file-name dir))
991 (with-current-buffer (sml-proc-buffer)
992 (setq default-directory dir)
993 (sml-send-string (concat (format sml-cd-command dir) "; " command)
994 t and-go))))
995
996 ;;; MORE CODE FOR SML-MODE
997
998 ;;;###autoload
999 (add-to-list 'auto-mode-alist '("\\.s\\(ml\\|ig\\)\\'" . sml-mode))
1000
1001 (defvar comment-quote-nested)
1002 (defvar electric-indent-chars)
1003 (defvar electric-layout-rules)
1004
1005 ;;;###autoload
1006 (define-derived-mode sml-mode sml-prog-proc-mode "SML"
1007 "\\<sml-mode-map>Major mode for editing Standard ML code.
1008 This mode runs `sml-mode-hook' just before exiting.
1009 See also (info \"(sml-mode)Top\").
1010 \\{sml-mode-map}"
1011 (set (make-local-variable 'sml-prog-proc-functions) sml-pp-functions)
1012 (set (make-local-variable 'font-lock-defaults) sml-font-lock-defaults)
1013 (set (make-local-variable 'outline-regexp) sml-outline-regexp)
1014 (set (make-local-variable 'imenu-create-index-function)
1015 'sml-imenu-create-index)
1016 (set (make-local-variable 'add-log-current-defun-function)
1017 'sml-current-fun-name)
1018 ;; Treat paragraph-separators in comments as paragraph-separators.
1019 (set (make-local-variable 'paragraph-separate)
1020 (concat "\\([ \t]*\\*)?\\)?\\(" paragraph-separate "\\)"))
1021 (set (make-local-variable 'require-final-newline) t)
1022 (set (make-local-variable 'electric-indent-chars)
1023 (cons ?\; (if (boundp 'electric-indent-chars)
1024 electric-indent-chars '(?\n))))
1025 (set (make-local-variable 'electric-layout-rules)
1026 `((?\; . ,(lambda ()
1027 (save-excursion
1028 (skip-chars-backward " \t;")
1029 (unless (or (bolp)
1030 (progn (skip-chars-forward " \t;")
1031 (eolp)))
1032 'after))))))
1033 (sml-mode-variables))
1034
1035 (defun sml-mode-variables ()
1036 (set-syntax-table sml-mode-syntax-table)
1037 (setq local-abbrev-table sml-mode-abbrev-table)
1038 ;; Setup indentation and sexp-navigation.
1039 (smie-setup sml-smie-grammar #'sml-smie-rules
1040 :backward-token #'sml-smie-backward-token
1041 :forward-token #'sml-smie-forward-token)
1042 (set (make-local-variable 'parse-sexp-ignore-comments) t)
1043 (set (make-local-variable 'comment-start) "(* ")
1044 (set (make-local-variable 'comment-end) " *)")
1045 (set (make-local-variable 'comment-start-skip) "(\\*+\\s-*")
1046 (set (make-local-variable 'comment-end-skip) "\\s-*\\*+)")
1047 ;; No need to quote nested comments markers.
1048 (set (make-local-variable 'comment-quote-nested) nil))
1049
1050 (defun sml-funname-of-and ()
1051 "Name of the function this `and' defines, or nil if not a function.
1052 Point has to be right after the `and' symbol and is not preserved."
1053 (forward-comment (point-max))
1054 (if (looking-at sml-tyvarseq-re) (goto-char (match-end 0)))
1055 (let ((sym (sml-smie-forward-token)))
1056 (forward-comment (point-max))
1057 (unless (or (member sym '(nil "d="))
1058 (member (sml-smie-forward-token) '("d=")))
1059 sym)))
1060
1061 (defun sml-find-forward (re)
1062 (while (progn (forward-comment (point-max))
1063 (not (looking-at re)))
1064 (or (ignore-errors (forward-sexp 1) t) (forward-char 1))))
1065
1066 (defun sml-electric-pipe () ;FIXME: Use post-self-insert-hook?
1067 "Insert a \"|\".
1068 Depending on the context insert the name of function, a \"=>\" etc."
1069 ;; FIXME: Make it a skeleton.
1070 (interactive)
1071 (unless (save-excursion (skip-chars-backward "\t ") (bolp)) (insert "\n"))
1072 (insert "| ")
1073 (let ((text
1074 (save-excursion
1075 (backward-char 2) ;back over the just inserted "| "
1076 (let ((sym (sml-find-matching-starter sml-pipeheads
1077 ;; (sml-op-prec "|" 'back)
1078 )))
1079 (sml-smie-forward-token)
1080 (forward-comment (point-max))
1081 (cond
1082 ((string= sym "|")
1083 (let ((f (sml-smie-forward-token)))
1084 (sml-find-forward "\\(=>\\|=\\||\\)\\S.")
1085 (cond
1086 ((looking-at "|") "") ;probably a datatype
1087 ((looking-at "=>") " => ") ;`case', or `fn' or `handle'
1088 ((looking-at "=") (concat f " = "))))) ;a function
1089 ((string= sym "and")
1090 ;; could be a datatype or a function
1091 (setq sym (sml-funname-of-and))
1092 (if sym (concat sym " = ") ""))
1093 ;; trivial cases
1094 ((string= sym "fun")
1095 (while (and (setq sym (sml-smie-forward-token))
1096 (string-match "^'" sym))
1097 (forward-comment (point-max)))
1098 (concat sym " = "))
1099 ((member sym '("case" "handle" "fn" "of")) " => ")
1100 ;;((member sym '("abstype" "datatype")) "")
1101 (t ""))))))
1102
1103 (insert text)
1104 (indent-according-to-mode)
1105 (beginning-of-line)
1106 (skip-chars-forward "\t |")
1107 (skip-syntax-forward "w_")
1108 (skip-chars-forward "\t ")
1109 (when (eq ?= (char-after)) (backward-char))))
1110
1111 ;;; Misc
1112
1113 (defun sml-mark-function ()
1114 "Mark the surrounding function. Or try to at least."
1115 (interactive)
1116 ;; FIXME: Provide beginning-of-defun-function so mark-defun "just works".
1117 (let ((start (point)))
1118 (sml-beginning-of-defun)
1119 (let ((beg (point)))
1120 (smie-forward-sexp 'halfsexp)
1121 (if (or (< start beg) (> start (point)))
1122 (progn
1123 (goto-char start)
1124 (mark-paragraph))
1125 (push-mark nil t t)
1126 (goto-char beg)))))
1127
1128 (defun sml-back-to-outer-indent () ;FIXME-copyright.
1129 "Unindents to the next outer level of indentation."
1130 (interactive)
1131 (save-excursion
1132 (beginning-of-line)
1133 (skip-chars-forward "\t ")
1134 (let ((start-column (current-column))
1135 (indent (current-column)))
1136 (if (> start-column 0)
1137 (progn
1138 (save-excursion
1139 (while (>= indent start-column)
1140 (setq indent (if (re-search-backward "^[^\n]" nil t)
1141 (current-indentation)
1142 0))))
1143 (backward-delete-char-untabify (- start-column indent)))))))
1144
1145 (defun sml-find-matching-starter (syms)
1146 (let ((halfsexp nil)
1147 tok)
1148 ;;(sml-smie-forward-token)
1149 (while (not (or (bobp)
1150 (member (nth 2 (setq tok (smie-backward-sexp halfsexp)))
1151 syms)))
1152 (cond
1153 ((null (car tok)) nil)
1154 ((numberp (car tok)) (setq halfsexp 'half))
1155 (t (goto-char (cadr tok)))))
1156 (if (nth 2 tok) (goto-char (cadr tok)))
1157 (nth 2 tok)))
1158
1159 (defun sml-skip-siblings ()
1160 (let (tok)
1161 (while (and (not (bobp))
1162 (progn (setq tok (smie-backward-sexp 'half))
1163 (cond
1164 ((null (car tok)) t)
1165 ((numberp (car tok)) t)
1166 (t nil)))))
1167 (if (nth 2 tok) (goto-char (cadr tok)))
1168 (nth 2 tok)))
1169
1170 (defun sml-beginning-of-defun ()
1171 (let ((sym (sml-find-matching-starter sml-starters-syms)))
1172 (if (member sym '("fun" "and" "functor" "signature" "structure"
1173 "abstraction" "datatype" "abstype"))
1174 (save-excursion (sml-smie-forward-token) (forward-comment (point-max))
1175 (sml-smie-forward-token))
1176 ;; We're inside a "non function declaration": let's skip all other
1177 ;; declarations that we find at the same level and try again.
1178 (sml-skip-siblings)
1179 ;; Obviously, let's not try again if we're at bobp.
1180 (unless (bobp) (sml-beginning-of-defun)))))
1181
1182 (defcustom sml-max-name-components 3
1183 "Maximum number of components to use for the current function name."
1184 :type 'integer)
1185
1186 (defun sml-current-fun-name ()
1187 (save-excursion
1188 (let ((count sml-max-name-components)
1189 fullname name)
1190 (end-of-line)
1191 (while (and (> count 0)
1192 (setq name (sml-beginning-of-defun)))
1193 (decf count)
1194 (setq fullname (if fullname (concat name "." fullname) name))
1195 ;; Skip all other declarations that we find at the same level.
1196 (sml-skip-siblings))
1197 fullname)))
1198
1199
1200 ;;; INSERTING PROFORMAS (COMMON SML-FORMS)
1201
1202 (defvar sml-forms-alist nil
1203 "Alist of code templates.
1204 You can extend this alist to your heart's content. For each additional
1205 template NAME in the list, declare a keyboard macro or function (or
1206 interactive command) called 'sml-form-NAME'.
1207 If 'sml-form-NAME' is a function it takes no arguments and should
1208 insert the template at point\; if this is a command it may accept any
1209 sensible interactive call arguments\; keyboard macros can't take
1210 arguments at all. Apropos keyboard macros, see `name-last-kbd-macro'
1211 and `sml-addto-forms-alist'.
1212 `sml-forms-alist' understands let, local, case, abstype, datatype,
1213 signature, structure, and functor by default.")
1214
1215 (defmacro sml-def-skeleton (name interactor &rest elements)
1216 (let ((fsym (intern (concat "sml-form-" name))))
1217 `(progn
1218 (add-to-list 'sml-forms-alist ',(cons name fsym))
1219 (define-abbrev sml-mode-abbrev-table ,name "" ',fsym nil 'system)
1220 (let ((abbrev (abbrev-symbol ,name sml-mode-abbrev-table)))
1221 (abbrev-put abbrev :case-fixed t)
1222 (abbrev-put abbrev :enable-function
1223 (lambda () (not (nth 8 (syntax-ppss))))))
1224 (define-skeleton ,fsym
1225 ,(format "SML-mode skeleton for `%s..' expressions" name)
1226 ,interactor
1227 ,(concat name " ") >
1228 ,@elements))))
1229 (put 'sml-def-skeleton 'lisp-indent-function 2)
1230
1231 (sml-def-skeleton "let" nil
1232 @ "\nin " > _ "\nend" >)
1233
1234 (sml-def-skeleton "if" nil
1235 @ " then " > _ "\nelse " > _)
1236
1237 (sml-def-skeleton "local" nil
1238 @ "\nin" > _ "\nend" >)
1239
1240 (sml-def-skeleton "case" "Case expr: "
1241 str "\nof " > _ " => ")
1242
1243 (sml-def-skeleton "signature" "Signature name: "
1244 str " =\nsig" > "\n" > _ "\nend" >)
1245
1246 (sml-def-skeleton "structure" "Structure name: "
1247 str " =\nstruct" > "\n" > _ "\nend" >)
1248
1249 (sml-def-skeleton "functor" "Functor name: "
1250 str " () : =\nstruct" > "\n" > _ "\nend" >)
1251
1252 (sml-def-skeleton "datatype" "Datatype name and type params: "
1253 str " =" \n)
1254
1255 (sml-def-skeleton "abstype" "Abstype name and type params: "
1256 str " =" \n _ "\nwith" > "\nend" >)
1257
1258 ;;
1259
1260 (sml-def-skeleton "struct" nil
1261 _ "\nend" >)
1262
1263 (sml-def-skeleton "sig" nil
1264 _ "\nend" >)
1265
1266 (sml-def-skeleton "val" nil
1267 @ " = " > _)
1268
1269 (sml-def-skeleton "fn" nil
1270 @ " =>" > _)
1271
1272 (sml-def-skeleton "fun" nil
1273 @ " =" > _)
1274
1275 ;;
1276
1277 (defun sml-forms-menu (_menu)
1278 (mapcar (lambda (x) (vector (car x) (cdr x) t))
1279 sml-forms-alist))
1280
1281 (defvar sml-last-form "let")
1282
1283 (defun sml-electric-space ()
1284 "Expand a symbol into an SML form, or just insert a space.
1285 If the point directly precedes a symbol for which an SML form exists,
1286 the corresponding form is inserted."
1287 (interactive)
1288 (let ((abbrev-mode (not abbrev-mode))
1289 (last-command-event ?\ )
1290 ;; Bind `this-command' to fool skeleton's special abbrev handling.
1291 (this-command 'self-insert-command))
1292 (call-interactively 'self-insert-command)))
1293
1294 (defun sml-insert-form (name newline) ;FIXME-copyright.
1295 "Interactive short-cut to insert the NAME common ML form.
1296 If a prefix argument is given insert a NEWLINE and indent first, or
1297 just move to the proper indentation if the line is blank\; otherwise
1298 insert at point (which forces indentation to current column).
1299
1300 The default form to insert is 'whatever you inserted last time'
1301 \(just hit return when prompted\)\; otherwise the command reads with
1302 completion from `sml-forms-alist'."
1303 (interactive
1304 (list (completing-read
1305 (format "Form to insert (default %s): " sml-last-form)
1306 sml-forms-alist nil t nil nil sml-forms-alist)
1307 current-prefix-arg))
1308 (setq sml-last-form name)
1309 (unless (or (not newline)
1310 (save-excursion (beginning-of-line) (looking-at "\\s-*$")))
1311 (insert "\n"))
1312 (when (memq (char-syntax (preceding-char)) '(?_ ?w)) (insert " "))
1313 (let ((f (cdr (assoc name sml-forms-alist))))
1314 (cond
1315 ((commandp f) (command-execute f))
1316 (f (funcall f))
1317 (t (error "Undefined SML form: %s" name)))))
1318
1319 ;; See also macros.el in emacs lisp dir.
1320
1321 (defun sml-addto-forms-alist (name) ;FIXME-copyright.
1322 "Assign a name to the last keyboard macro defined.
1323 Argument NAME is transmogrified to sml-form-NAME which is the symbol
1324 actually defined.
1325
1326 The symbol's function definition becomes the keyboard macro string.
1327
1328 If that works, NAME is added to `sml-forms-alist' so you'll be able to
1329 reinvoke the macro through \\[sml-insert-form]. You might want to save
1330 the macro to use in a later editing session -- see `insert-kbd-macro'
1331 and add these macros to your .emacs file.
1332
1333 See also `edit-kbd-macro' which is bound to \\[edit-kbd-macro]."
1334 (interactive "sName for last kbd macro (\"sml-form-\" will be added): ")
1335 (when (string= name "") (error "No command name given"))
1336 (let ((fsym (intern (concat "sml-form-" name))))
1337 (name-last-kbd-macro fsym)
1338 (message "Macro bound to %s" fsym)
1339 (add-to-list 'sml-forms-alist (cons name fsym))))
1340
1341 ;;;
1342 ;;; MLton support
1343 ;;;
1344
1345 (defvar sml-mlton-command "mlton"
1346 "Command to run MLton. Can include arguments.")
1347
1348 (defvar sml-mlton-mainfile nil)
1349
1350 (defconst sml-mlton-error-regexp-alist
1351 ;; I wish they just changed MLton to use one of the standard
1352 ;; error formats.
1353 `(("^\\(?:Error\\|\\(Warning\\)\\): \\(.+\\) \\([0-9]+\\)\\.\\([0-9]+\\)\\.$"
1354 2 3 4
1355 ;; If subgroup 1 matched, then it's a warning, otherwise it's an error.
1356 (1))))
1357
1358 (defvar compilation-error-regexp-alist)
1359 (eval-after-load "compile"
1360 '(dolist (x sml-mlton-error-regexp-alist)
1361 (add-to-list 'compilation-error-regexp-alist x)))
1362
1363 (defun sml-mlton-typecheck (mainfile)
1364 "typecheck using MLton."
1365 (interactive
1366 (list (if (and sml-mlton-mainfile (not current-prefix-arg))
1367 sml-mlton-mainfile
1368 (read-file-name "Main file: "))))
1369 (setq sml-mlton-mainfile mainfile)
1370 (save-some-buffers)
1371 (require 'compile)
1372 (dolist (x sml-mlton-error-regexp-alist)
1373 (add-to-list 'compilation-error-regexp-alist x))
1374 (with-current-buffer (find-file-noselect mainfile)
1375 (compile (concat sml-mlton-command
1376 " -stop tc " ;Stop right after type checking.
1377 (shell-quote-argument
1378 (file-relative-name buffer-file-name))))))
1379
1380 ;;;
1381 ;;; MLton's def-use info.
1382 ;;;
1383
1384 (defvar sml-defuse-file nil)
1385
1386 (defun sml-defuse-file ()
1387 (or sml-defuse-file (sml-defuse-set-file)))
1388
1389 (defun sml-defuse-set-file ()
1390 "Specify the def-use file to use."
1391 (interactive)
1392 (setq sml-defuse-file (read-file-name "Def-use file: ")))
1393
1394 (defun sml-defuse-symdata-at-point ()
1395 (save-excursion
1396 (sml-smie-forward-token)
1397 (let ((symname (sml-smie-backward-token)))
1398 (if (equal symname "op")
1399 (save-excursion (setq symname (sml-smie-forward-token))))
1400 (when (string-match "op " symname)
1401 (setq symname (substring symname (match-end 0)))
1402 (forward-word)
1403 (forward-comment (point-max)))
1404 (list symname
1405 ;; Def-use files seem to count chars, not columns.
1406 ;; We hope here that they don't actually count bytes.
1407 ;; Also they seem to start counting at 1.
1408 (1+ (- (point) (progn (beginning-of-line) (point))))
1409 (save-restriction
1410 (widen) (1+ (count-lines (point-min) (point))))
1411 buffer-file-name))))
1412
1413 (defconst sml-defuse-def-regexp
1414 "^[[:alpha:]]+ \\([^ \n]+\\) \\(.+\\) \\([0-9]+\\)\\.\\([0-9]+\\)$")
1415 (defconst sml-defuse-use-regexp-format "^ %s %d\\.%d $")
1416
1417 (defun sml-defuse-jump-to-def ()
1418 "Jump to the definition corresponding to the symbol at point."
1419 (interactive)
1420 (let ((symdata (sml-defuse-symdata-at-point)))
1421 (if (null (car symdata))
1422 (error "Not on a symbol")
1423 (with-current-buffer (find-file-noselect (sml-defuse-file))
1424 (goto-char (point-min))
1425 (unless (re-search-forward
1426 (format sml-defuse-use-regexp-format
1427 (concat "\\(?:"
1428 ;; May be an absolute file name.
1429 (regexp-quote (nth 3 symdata))
1430 "\\|"
1431 ;; Or a relative file name.
1432 (regexp-quote (file-relative-name
1433 (nth 3 symdata)))
1434 "\\)")
1435 (nth 2 symdata)
1436 (nth 1 symdata))
1437 nil t)
1438 ;; FIXME: This is typically due to editing: any minor editing will
1439 ;; mess everything up. We should try to fail more gracefully.
1440 (error "Def-use info not found"))
1441 (unless (re-search-backward sml-defuse-def-regexp nil t)
1442 ;; This indicates a bug in this code.
1443 (error "Internal failure while looking up def-use"))
1444 (unless (equal (match-string 1) (nth 0 symdata))
1445 ;; FIXME: This again is most likely due to editing.
1446 (error "Incoherence in the def-use info found"))
1447 (let ((line (string-to-number (match-string 3)))
1448 (char (string-to-number (match-string 4))))
1449 (pop-to-buffer (find-file-noselect (match-string 2)))
1450 (goto-char (point-min))
1451 (forward-line (1- line))
1452 (forward-char (1- char)))))))
1453
1454 ;;;
1455 ;;; SML/NJ's Compilation Manager support
1456 ;;;
1457
1458 (defvar sml-cm-mode-syntax-table sml-mode-syntax-table)
1459 (defvar sml-cm-font-lock-keywords
1460 `(,(concat "\\_<" (regexp-opt '("library" "group" "is" "structure"
1461 "functor" "signature" "funsig") t)
1462 "\\_>")))
1463 ;;;###autoload
1464 (add-to-list 'completion-ignored-extensions ".cm/")
1465 ;; This was used with the old compilation manager.
1466 (add-to-list 'completion-ignored-extensions "CM/")
1467 ;;;###autoload
1468 (add-to-list 'auto-mode-alist '("\\.cm\\'" . sml-cm-mode))
1469 ;;;###autoload
1470 (define-derived-mode sml-cm-mode fundamental-mode "SML-CM"
1471 "Major mode for SML/NJ's Compilation Manager configuration files."
1472 (local-set-key "\C-c\C-c" 'sml-compile)
1473 (set (make-local-variable 'font-lock-defaults)
1474 '(sml-cm-font-lock-keywords nil t nil nil)))
1475
1476 ;;;
1477 ;;; ML-Lex support
1478 ;;;
1479
1480 (defvar sml-lex-font-lock-keywords
1481 (append
1482 `((,(concat "^%" sml-id-re) . font-lock-builtin-face)
1483 ("^%%" . font-lock-module-def-face))
1484 sml-font-lock-keywords))
1485 (defconst sml-lex-font-lock-defaults
1486 (cons 'sml-lex-font-lock-keywords (cdr sml-font-lock-defaults)))
1487
1488 ;;;###autoload
1489 (define-derived-mode sml-lex-mode sml-mode "SML-Lex"
1490 "Major Mode for editing ML-Lex files."
1491 (set (make-local-variable 'font-lock-defaults) sml-lex-font-lock-defaults))
1492
1493 ;;;
1494 ;;; ML-Yacc support
1495 ;;;
1496
1497 (defface sml-yacc-bnf-face
1498 '((t (:foreground "darkgreen")))
1499 "Face used to highlight (non)terminals in `sml-yacc-mode'.")
1500 (defvar sml-yacc-bnf-face 'sml-yacc-bnf-face)
1501
1502 (defcustom sml-yacc-indent-action 16
1503 "Indentation column of the opening paren of actions."
1504 :type 'integer)
1505
1506 (defcustom sml-yacc-indent-pipe nil
1507 "Indentation column of the pipe char in the BNF.
1508 If nil, align it with `:' or with previous cases."
1509 :type 'integer)
1510
1511 (defcustom sml-yacc-indent-term nil
1512 "Indentation column of the (non)term part.
1513 If nil, align it with previous cases."
1514 :type 'integer)
1515
1516 (defvar sml-yacc-font-lock-keywords
1517 (cons `((concat "^\\(" sml-id-re "\\s-*:\\|\\s-*|\\)\\(\\s-*" sml-id-re
1518 "\\)*\\s-*\\(\\(%" sml-id-re "\\)\\s-+" sml-id-re "\\|\\)")
1519 (0 (save-excursion
1520 (save-match-data
1521 (goto-char (match-beginning 0))
1522 (unless (or (re-search-forward "\\_<of\\_>"
1523 (match-end 0) 'move)
1524 (progn (forward-comment (point-max))
1525 (not (looking-at "("))))
1526 sml-yacc-bnf-face))))
1527 (4 font-lock-builtin-face t t))
1528 sml-lex-font-lock-keywords))
1529 (defconst sml-yacc-font-lock-defaults
1530 (cons 'sml-yacc-font-lock-keywords (cdr sml-font-lock-defaults)))
1531
1532 (defun sml-yacc-indent-line ()
1533 "Indent current line of ML-Yacc code."
1534 (let ((savep (> (current-column) (current-indentation)))
1535 (indent (max (or (ignore-errors (sml-yacc-indentation)) 0) 0)))
1536 (if savep
1537 (save-excursion (indent-line-to indent))
1538 (indent-line-to indent))))
1539
1540 (defun sml-yacc-indentation ()
1541 (save-excursion
1542 (back-to-indentation)
1543 (or (and (looking-at (eval-when-compile
1544 (concat "%\\|" sml-id-re "\\s-*:")))
1545 0)
1546 (when (save-excursion
1547 (condition-case nil (progn (up-list -1) nil) (scan-error t)))
1548 ;; We're outside an action.
1549 (cond
1550 ;; Special handling of indentation inside %term and %nonterm
1551 ((save-excursion
1552 (and (re-search-backward "^%\\(\\sw+\\)" nil t)
1553 (member (match-string 1) '("term" "nonterm"))))
1554 (if (numberp sml-yacc-indent-term) sml-yacc-indent-term
1555 (let ((offset (if (looking-at "|") -2 0)))
1556 (forward-line -1)
1557 (looking-at "\\s-*\\(%\\sw*\\||\\)?\\s-*")
1558 (goto-char (match-end 0))
1559 (+ offset (current-column)))))
1560 ((looking-at "(") sml-yacc-indent-action)
1561 ((looking-at "|")
1562 (if (numberp sml-yacc-indent-pipe) sml-yacc-indent-pipe
1563 (backward-sexp 1)
1564 (while (progn (forward-comment (- (point)))
1565 (/= 0 (skip-syntax-backward "w_"))))
1566 (forward-comment (- (point)))
1567 (if (not (looking-at "\\s-$"))
1568 (1- (current-column))
1569 (skip-syntax-forward " ")
1570 (- (current-column) 2))))))
1571 ;; default to SML rules
1572 (smie-indent-calculate))))
1573
1574 ;;;###autoload
1575 (add-to-list 'auto-mode-alist '("\\.grm\\'" . sml-yacc-mode))
1576 ;;;###autoload
1577 (define-derived-mode sml-yacc-mode sml-mode "SML-Yacc"
1578 "Major Mode for editing ML-Yacc files."
1579 (set (make-local-variable 'indent-line-function) 'sml-yacc-indent-line)
1580 (set (make-local-variable 'font-lock-defaults) sml-yacc-font-lock-defaults))
1581
1582 \f
1583 (provide 'sml-mode)
1584
1585 ;;; sml-mode.el ends here