;;; sml-proc.el. Comint based interaction mode for Standard ML. ;; Copyright (C) 1989, Lars Bo Nielsen, 1994,1997 Matthew J. Morley ;; $Revision: 3.16 $ ;; $Date: 1998/03/13 15:38:32 $ ;; ==================================================================== ;; This file is not part of GNU Emacs, but it is distributed under the ;; same conditions. ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 2, or (at ;; your option) any later version. ;; This program is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, 675 Mass Ave, Cambridge, MA 0139, USA. ;; (See sml-mode.el for HISTORY.) ;; ==================================================================== ;; [MJM 10/94] Separating this from sml-mode means sml-mode will run ;; under 18.59 (or anywhere without comint, if there are such places). ;; See sml-mode.el for further information. ;;; DESCRIPTION ;; Inferior-sml-mode is for interacting with an ML process run under ;; emacs. This uses the comint package so you get history, expansion, ;; backup and all the other benefits of comint. Interaction is ;; achieved by M-x sml which starts a sub-process under emacs. You may ;; need to set this up for autoloading in your .emacs: ;; (autoload 'sml "sml-proc" "Run an inferior ML process." t) ;; Exactly what process is governed by the variable sml-program-name ;; -- just "sml" by default. If you give a prefix argument (C-u M-x ;; sml) you will be prompted for a different program to execute from ;; the default -- if you just hit RETURN you get the default anyway -- ;; along with the option to specify any command line arguments. Once ;; you select the ML program name in this manner, it remains the ;; default (unless you set in a hook, or otherwise). ;; NOTE: inferior-sml-mode-hook is run AFTER the ML program has been ;; launched. inferior-sml-load-hook is run only when sml-proc.el is ;; loaded into Emacs. ;; When running an ML process some further key-bindings are effective ;; in sml-mode buffer(s). C-c C-s (switch-to-sml) will split the ;; screen into two windows if necessary and place you in the ML ;; process buffer. In the interaction buffer, C-c C-s is bound to the ;; `sml' command by default (in case you need to restart). ;; C-c C-l (sml-load-file) will load an SML source file into the ;; inferior process, C-c C-r (sml-send-region) will send the current ;; region of text to the ML process, etc. Given a prefix argument to ;; these commands will switch you from the SML buffer to the ML ;; process buffer as well as sending the text. If you get errors ;; reported by the compiler, C-c ` (sml-next-error) will step through ;; the errors with you. ;; NOTE. There is only limited support for this as it obviously ;; depends on the compiler's error messages being recognised by the ;; mode. Error reporting is currently only geared up for SML/NJ, ;; Moscow ML, and Poly/ML (see file sml-{mosml,poly-ml}.el). Look at ;; the documentation for sml-error-parser and sml-next-error -- you ;; may only need to modify the former to recover this feature for some ;; other ML systems, along with sml-error-regexp. ;; While small pieces of text can be fed quite happily into the ML ;; process directly, lager pieces should (probably) be sent via a ;; temporary file making use of the compiler's "use" command. ;; CURRENT RATIONALE: you get sense out of the error messages if ;; there's a real file associated with a block of code, and XEmacs is ;; less likely to hang. These are likely to change. ;; For more information see the variable sml-temp-threshold. You ;; should set the variable sml-use-command appropriately for your ML ;; compiler. By default things are set up to work for the SML/NJ ;; compiler. ;;; FOR YOUR .EMACS ;; Here are some ideas for inferior-sml-*-hooks: ;; (setq inferior-sml-load-hook ;; '(lambda() "Set global defaults for inferior-sml-mode" ;; (define-key inferior-sml-mode-map "\C-cd" 'sml-cd) ;; (define-key sml-mode-map "\C-cd" 'sml-cd) ;; (define-key sml-mode-map "\C-c\C-f" 'sml-send-function) ;; (setq sml-temp-threshold 0))) ; safe: always use tmp file ;; (setq inferior-sml-mode-hook ;; '(lambda() "Inferior SML mode defaults" ;; (setq comint-scroll-show-maximum-output t ;; comint-scroll-to-bottom-on-output t ;; comint-input-autoexpand nil))) ;; =================================================================== ;;; INFERIOR ML MODE VARIABLES (require 'sml-mode) (require 'comint) (provide 'sml-proc) (defvar sml-program-name "sml" "*Program to run as ML.") (defvar sml-default-arg "" "*Default command line option to pass, if any.") (defvar sml-display-frame-alist '((height . 24) (width . 80) (menu-bar-lines . 0)) "*Alist of frame parameters used in creating dedicated ML interaction frames. These supersede the values given in `default-frame-alist'. You might like a larger screen \(setcdr \(assoc 'height sml-display-frame-alist\) 40\) or you might like a small font \(setq sml-display-frame-alist \(cons '\(font . \"7x14\"\) sml-display-frame-alist\)\) in your `inferior-sml-load-hook', say. The parameters '\(\(unsplittable . t\) \(icon-name . \"*sml*\"\)\) are always added to sml-display-frame-alist by default, though the value of icon-name is actually culled from `sml-program-name'. See also the documentation for `modify-frame-parameters'.") (defvar sml-dedicated-frame (if window-system t nil) "*If non-nil, interaction buffers display in their own frame. Default is equivalent to variable `window-system'. If you reset this variable after starting the compiler, you might have to reset the window-dedicated property of the window displaying the interaction buffer. See `set-window-dedicated-p'.") ;;(defvar sml-raise-on-error nil ;; "*When non-nil, `sml-next-error' will raise the ML process's frame.") (defvar sml-temp-threshold 0 "*Controls when emacs uses temporary files to communicate with ML. If not a number (e.g., NIL), then emacs always sends text directly to the subprocess. If an integer N, then emacs uses a temporary file whenever the text is longer than N chars. `sml-temp-file' contains the name of the temporary file for communicating. See variable `sml-use-command' and function `sml-send-region'. Sending regions directly through the pty (not using temp files) doesn't work very well -- e.g., SML/NJ nor Poly/ML incorrectly report the line # of errors occurring in std_in.") (defvar sml-temp-file (make-temp-name "/tmp/ml") "*Temp file that emacs uses to communicate with the ML process. See `sml-temp-threshold'. Defaults to \(make-temp-name \"/tmp/ml\"\)") (defvar inferior-sml-mode-hook nil "*This hook is run when the inferior ML process is started. All buffer local customisations for the interaction buffers go here.") (defvar inferior-sml-load-hook nil "*Hook run when inferior-sml-mode (sml-proc.el) is loaded into Emacs. This is a good place to put your preferred key bindings.") (defvar sml-buffer nil "*The current ML process buffer. MULTIPLE PROCESS SUPPORT (Whoever wants multi-process support anyway?) ===================================================================== sml-mode supports, in a fairly simple fashion, running multiple ML processes. To run multiple ML processes, you start the first up with \\[sml]. It will be in a buffer named *sml*. Rename this buffer with \\[rename-buffer]. You may now start up a new process with another \\[sml]. It will be in a new buffer, named *sml*. You can switch between the different process buffers with \\[switch-to-buffer]. NB *sml* is just the default name for the buffer. It actually gets it's name from the value of `sml-program-name' -- *poly*, *smld*,... If you have more than one ML process around, commands that send text from source buffers to ML processes -- like `sml-send-function' or `sml-send-region' -- have to choose a process to send it to. This is determined by the global variable `sml-buffer'. Suppose you have three inferior ML's running: Buffer Process sml # mosml # *sml* #> If you do a \\[sml-send-function] command on some ML source code, what process do you send it to? - If you're in a process buffer (sml, mosml, or *sml*), you send it to that process (usually makes sense only to `sml-load-file'). - If you're in some other buffer (e.g., a source file), you send it to the process attached to buffer `sml-buffer'. This process selection is performed by function `sml-proc' which looks at the value of `sml-buffer' -- which must be a lisp buffer object, or a string \(or nil\). Whenever \\[sml] fires up a new process, it resets `sml-buffer' to be the new process's buffer. If you only run one process, this will do the right thing. If you run multiple processes, you can change `sml-buffer' to another process buffer with \\[set-variable], or use the command \\[sml-buffer] in the interaction buffer of choice.") ;;; ALL STUFF THAT DEFAULTS TO THE SML/NJ COMPILER (110) (defvar sml-use-command "use \"%s\"" "*Template for loading a file into the inferior ML process. Set to \"use \\\"%s\\\"\" for SML/NJ or Edinburgh ML; set to \"PolyML.use \\\"%s\\\"\" for Poly/ML, etc.") (defvar sml-cd-command "OS.FileSys.chDir \"%s\"" "*Command template for changing working directories under ML. Set this to nil if your compiler can't change directories. The format specifier \"%s\" will be converted into the directory name specified when running the command \\[sml-cd].") (defvar sml-prompt-regexp "^[\-=] *" "*Regexp used to recognise prompts in the inferior ML process.") (defvar sml-error-parser 'sml-smlnj-error-parser "*This function parses an error message into a 3-5 element list: \(file start-line start-col end-line-col err-msg\). The first three components are required by `sml-next-error', but the other two are optional. If the file associated with the input is the standard input stream, this function should probably return \(\"std_in\" start-line start-col\). This function will be called in a context in which the match data \(see `match-data'\) are current for `sml-error-regexp'. The mode sets the default value to the function `sml-smlnj-error-parser'. In a step towards greater sml-mode modularity END-LINE-COL can be either - the symbol nil \(in which case it is ignored\) or - an Emacs Lisp expression that when `eval'd at \(start-line,start-col\) will move point to the end of the errorful text in the file. Note that the compiler should return the full path name of the errorful file, and that this might require you to fiddle with the compiler's prettyprinting switches.") ;; std_in:2.1-4.3 Error: operator and operand don't agree (tycon mismatch) ;; std_in:2.1 Error: operator and operand don't agree (tycon mismatch) (defconst sml-smlnj-error-regexp (concat "^[-= ]*\\(.+\\):" ;file name "\\([0-9]+\\)\\.\\([0-9]+\\)" ;start line.column "\\(-\\([0-9]+\\)\\.\\([0-9]+\\)\\)?" ;end line.colum ".+\\(\\(Error\\|Warning\\): .*\\)") ;the message "Default regexp matching SML/NJ error and warning messages. There should be no need to customise this, though you might decide that you aren't interested in Warnings -- my advice would be to modify `sml-error-regexp' explicitly to do that though. If you do customise `sml-smlnj-error-regexp' you may need to modify the function `sml-smlnj-error-parser' (qv).") (defvar sml-error-regexp sml-smlnj-error-regexp "*Regexp for matching \(the start of\) an error message.") (defun sml-smlnj-error-parser (pt) "This parses the SML/NJ error message at PT into a 5 element list \(file start-line start-col end-of-err msg\) where FILE is the file in which the error occurs\; START-LINE is the line number in the file where the error occurs\; START-COL is the character position on that line where the error occurs. If present, the fourth return value is a simple Emacs Lisp expression that will move point to the end of the errorful text, assuming that point is at \(start-line,start-col\) to begin with\; and MSG is the text of the error message given by the compiler." ;; This function uses `sml-smlnj-error-regexp' to do the parsing, and ;; assumes that regexp groups 1, 2, and 3 correspond to the first three ;; elements of the list returned\; and groups 5, 6 and 7 correspond to the ;; optional elements in that order. (save-excursion (goto-char pt) (if (not (looking-at sml-smlnj-error-regexp)) ;; the user loses big time. (list nil nil nil) (let ((file (match-string 1)) ; the file (slin (string-to-int (match-string 2))) ; the start line (scol (string-to-int (match-string 3))) ; the start col (msg (if (match-beginning 7) (match-string 7)))) ;; another loss: buggy sml/nj's produce nonsense like file:0.0 Error (if (zerop slin) (list file nil scol) ;; ok, was a range of characters mentioned? (if (match-beginning 4) ;; assume m-b 4 implies m-b 5 and m-b 6 (sml-smlnj-error-regexp) (let* ((elin (string-to-int (match-string 5))) ; end line (ecol (string-to-int (match-string 6))) ; end col (jump (if (= elin slin) ;; move forward on the same line `(forward-char ,(1+ (- ecol scol))) ;; otherwise move down, and over to ecol `(progn (forward-line ,(- elin slin)) (forward-char ,ecol))))) ;; nconc glues lists together. jump & msg aren't lists (nconc (list file slin scol) (list jump) (list msg))) (nconc (list file slin scol) (list nil) (list msg)))))))) (defun sml-smlnj (pfx) "Set up and run Standard ML of New Jersey. Prefix argument means accept the defaults below. Note: defaults set here will be clobbered if you setq them in the inferior-sml-mode-hook. sml-program-name