The programming environment we will be using for the course will be the Standard ML of New Jersey system (or SML/NJ). This guide provides instructions for how to set up your zoo account to use SML/NJ. This is not intended as a guide to programming in Standard ML; for that purpose you are referred to the course textbook.
You can find a Windows version of SML at: SML/NJ Download.
The most convenient way to run SML/NJ is using the SML mode for Emacs-19 (or later). To set up your Emacs to use this mode, add the following lines to the end of your .emacs file:
(setq load-path (cons "/c/cs201/lib/elisp" load-path)) (autoload 'sml-mode "sml-mode" "Major mode for editing SML." t) (setq auto-mode-alist (cons '("\.sml$" . sml-mode) (cons '("\.sig$" . sml-mode) (cons '("\\.fun$" . sml-mode) auto-mode-alist)))) (load-library "sml-font") (defun turn-on-font-lock () (font-lock-mode t))
These lines can also be found in the file lib/elisp/emacs.additions in the course directory.
Note that the SML mode works only for Emacs versions 19 and later.
With your .emacs file set up as above, you may enter the SML mode either by loading any file with a .sml or .sig extension, or by typing M-x sml-mode. The SML mode provides indentation and other facility that may be helpful in editing SML source code.
Once in the SML mode, you can start SML/NJ by typing M-x sml, or by selecting SML/Process/Start default ML compiler from the menu. This will start SML/NJ running in the background. You can then bring up an SML/NJ window by typing C-c C-s, or by selecting SML/Process/switch to ML buffer from the menu. On some machines SML/NJ may be slow to load; if the window comes up empty, be patient.
If you prefer not to run SML/NJ through Emacs, you may also run it from the Unix prompt sml.
Once SML/NJ has started, it prints the current version number and then prompts for user input. The prompt is a single dash. At the prompt you may type an SML top-level declaration or an SML expression. When you enter a declaration, SML/NJ evaluates the declaration, prints its result value and type, and prompts for more input. For example (user input in bold):
- val a = 2 + 3; val a = 5 : int -
This declaration sets a equal to the value of 2 + 3, that is, 5. The semicolon at the end of the input line tells the SML parser that its input is complete. By leaving off the semicolon you may enter multi-line declarations or expressions. Each line after the first is prompted by an equals. For example, we may declare a two-line increment function by:
- fun inc x = = x + 1; val inc = fn : int -> int -
In each of the above examples, the identifier being declared (a or inc) is available in code that follows. When you enter an expression at the user prompt, SML/NJ treats that expression as a declaration for the identifier it. For example:
- inc a; val it = 6 : int - it * 3; val it = 18 : int -
The interactive loop, as the above is called, is a convenient way to evaluate small expressions and declarations, but it is not practical for larger pieces of code. For larger pieces of code we want to load them directly into SML/NJ from a file. This is done by running the use command. SML/NJ will then process the file as if it had been entered into the interactive loop (except that no semicolon is needed at the end of the file). For example, suppose the file myprog.sml contained the code:
val b = 3 + 4 val c = 6 * b
Then using that file will cause SML/NJ to respond:
- use "myprog.sml"; [opening myprog.sml] val b = 7 : int val c = 42 : int val it = () : unit -
The final binding to it happens because SML/NJ views use "myprog.sml" as an expression and, as discussed above, treats that expression as a declaration for the identifier it. The value of this expression is (), which is pronounced ``unit.''
When SML/NJ prints a data structure, it prints that data structure only to a certain depth. Beneath that depth it prints a # instead. This is generally a good thing, since data structures can be very large (and even cyclic). However, the default depth to which SML/NJ prints data structures is 5, which is usually not enough. You can adjust the depth to which it prints data structures by entering, for example,
- Compiler.Control.Print.printDepth := 10;
to set the depth to 10. SML/NJ also abbreviates list and strings over a certain length. You can set the length at which this happens by setting Compiler.Control.Print.printLength and Compiler.Control.Print.stringDepth, in a manner analogous to the above.
When the argument to use is not a full path name, SML/NJ looks for source files in the working directory. You can set SML/NJ's working directory by entering, for example,
- OS.FileSys.chDir "newdir";
to change the working directory to ``newdir.'' Windows users should note that the SML syntax for strings doubles backslashes. You can also find out what the working directory is with the command:
- OS.FileSys.getDir ();
When running SML under Emacs, you can have Emacs issue use commands to SML/NJ instead of entering them yourselves. The command C-c C-b sends the current buffer to SML/NJ and C-c C-r sends the current Emacs region. You can also have Emacs issue directory change commands with M-x sml-cd.
One of the most useful things Emacs can do for you is to parse error messages and locate their source. After compiling a file, enter C-c ` and Emacs will locate and highlight your first error, as reported by SML/NJ. You may then locate successive errors by repeating the command. Note that if you enter a use command directly into SML/NJ, Emacs will not know of it, and will continue parsing errors from the previous compile. You can get Emacs back into synch by directing it to ignore all remaining error using the M-x sml-skip-errors command.
You may also wish to experiment with the M-| command, which begins a new branch of a case statement or function definition.
A summary of these commands appears below:
key sequence | command name |
C-c C-b | sml-send-buffer |
C-c C-r | sml-send-region |
M-x sml-cd | sml-cd |
C-c ` | sml-next-error |
M-x sml-skip-errors | sml-skip-errors |
M-| | sml-electric-pipe |