The program “smlnj-script” is the SML/NJ script intepreter, which provides a way to to run a Standard ML (SML) program as a script, and also provides a few additional features useful for scripts. The interpreter smlnj-script is based on the SML/NJ implementation of SML. The interpreter is, in essence, the SML/NJ compiler (including the SML/NJ run-time machinery) with a few additional utility functions defined and a specialized startup routine.
An SML/NJ script is a program that can make use of these capabilities:
SML is a procedural language with extremely strong support for higher-order functions and abstraction.
In syntax and semantics, SML stays fairly close to the λ-calculus. For example, just like in the λ-calculus, each function in SML has exactly 1 argument, so if you want to pass more than one thing you need to pack them all together into a data structure.
SML is a strongly typed language. Fortunately, most type annotations are optional and will be automatically calculated by the SML implementation. Also, SML/NJ scripts only type check (and parse and compile) each top-level form as it is encountered. So you can at run-time evaluate a string (using SmlnjScriptUtils.useString) or file (using “use”) to supply definitions that will allow later top-level forms to successfully be type checked.
You can learn about SML at locations like:
http://en.wikipedia.org/wiki/Standard_ML
The following texts might be useful to you for learning SML:
The Standard ML Basis Library is documented at this location:
Most SML implementations (including of course SML/NJ) include “the SML/NJ library” which provides a number of additional useful features not included in the standard basis. The SML/NJ library is partially documented at this location: Unfortunately, there are some features in the SML/NJ library that are only currently documented in the source code. The important files to read are the ones containing the definitions of signatures, which are generally well commented. It is worthwhile to fetch and unpack the source code for the SML/NJ library to get these signature files. For the latest working version of SML/NJ, the SML/NJ library source code can be found at this location: You unpack this file with a command like this:
tar xfz smlnj-lib.tgz
The hallmark of a script on a POSIX (a.k.a. UNIX) system (like any computer running Linux) is that it is a file which has “execute” permission that begins with a line of this form:
#!PATH-TO-INTERPRETER OPTIONAL-ARGUMENT
The interpreter that an SML/NJ script uses is the program called “smlnj-script”.
In the examples that follow, this text will assume there is a copy of smlnj-script at this file system location (which is true for the HWU (Heriot-Watt University) CS (Computer Science) department's UNIX file systems):
/u1/staff/jbw/bin/smlnj-script
The ways you can get smlnj-script to be used for an SML/NJ script include the following:
#!/usr/bin/env smlnj-script
and ensure your PATH environment variable includes a directory containing smlnj-script, for example you could do something like this:
PATH="$PATH:/u1/staff/jbw/bin/"
This method uses /usr/bin/env as the interpreter. All the “env” program does in this case is search for “smlnj-script” in the PATH and invoke it with the correct arguments.
After doing the above, the script can simply be executed like any other program.
#!/u1/staff/jbw/bin/smlnj-script
After doing the above, the script can simply be executed like any other program.
/u1/staff/jbw/bin/smlnj-script Xyzzy Plugh Hello Sailor
chmod a+x Xyzzy
SML/NJ (and also the SML/NJ script interpreter “smlnj-script”) provides an interactive “read-eval-print loop” (REPL) which reads individual SML top-level declarations typed by the programmer, then parses, type-checks, compiles, and executes them, and then prints the results. You can get access to the REPL by just running this command:
smlnj-script
You can also insert an invocation of U.interact in the middle of an SML/NJ script, and it will invoke the REPL to let you inspect top-level definitions. Do so with a line like this:
val () = SmlnjScriptUtils.interact ();
When you want to continue, just press Control-D (or whatever character is assigned to the “simulate end-of-file” functionality in your terminal window) or evaluate (SmlnjScriptUtils.continue ()) by typing a line like this at the REPL prompt:
>- SmlnjScriptUtils.continue ();
Unfortunately, definitions that are not at top-level will not be visible to the REPL. If you want to inspect values in the middle of a loop, save the values in some ref cells that are visible in the top-level environment before calling SmlnjScriptUtils.interact, like this:
val myRefCell = ref (...); ... fun runsForever () = (... myRefCell := (...) ... SmlnjScriptUtils.interact () ...)
If you find the results printed by the REPL are being truncated (for example, you may see “#” instead of some complicated data), you can get the entire data structure printed by first invoking this (from within your program or from the REPL input prompt):
SmlnjScriptUtils.raisePrintingLimitsToMax ()
Type error messages generated by SML/NJ can sometimes be quite confusing. If you are having trouble with debugging SML type errors, you may want to use the Skalpel type error slicer for SML which can be found at this location:
There is a web interface into which you can type your erroneous source code, or you can download and install the software for yourself for use in a source code editor. You will get what we think is a much more understandable explanation of how to fix your type error. If you are a student or staff member at HWU, feel free to ask us (currently that is Joe Wells or John Pirie) in person for help using the type error slicer.Extending the SML language, the SML Basis Library, and the SML/NJ library, the smlnj-script interpreter provides some additional functions, mostly as part of the SmlnjScriptUtils structure. This structure is also given the short name “U”, and a few of its members are copied into the top-level environment. In addition, there is a new top-level overloaded operator toString, which also has the short name “%”, and which can be extended to handle some user-defined types. Furthermore, SML/NJ's quasiquote feature is turned on by default for use with the new “q” and “qq” Perl-style quoting operators.
Here is a listing of some of the new features:
val silenceCompiler : unit -> unit = SmlnjScriptUtils.silenceCompiler
The silenceCompiler function turns off compiler messages such as the printing of the name, type, and value of all identifiers that get added to the top-level environment and also autoloading messages. Use it once you are happy your script is working.
Sadly, this pretty much completely silences the compiler and hides type error messages. Unfortunately there is no way to turn off the useless messages while keeping the error messages you want to see. Maybe this will be fixed in a future version of SML/NJ.
val % : 'a -> string = ... val toString : 'a -> string = ...
The % and toString operators are both overloaded operators that know how to convert many types into strings. They both do the same thing. They are equivalent to Int.toString, Bool.toString, Real.toString, etc., whichever is useful on the argument. You can add support for more types (provided the type is “atomic”) with SmlnjScriptUtils.extendToString.
structure SmlnjScriptUtils = ... structure U = SmlnjScriptUtils
The SmlnjScriptUtils structure contains a lot of stuff. U is just a short name for SmlnjScriptUtils. Here are some of the highlights:
val raisePrintingLimitsToMax : unit -> unit = ...
Does what it says. Useful when debugging and the default limits cause too much of your data structures to be truncated when printed by the compiler.
val interact : unit -> unit = ... val continue : unit -> unit = ...
The function interact provides an interactive “read-eval-print loop” (REPL), and the function continue resumes execution by returning from a call to interact. See above for more details.
val extendToString : string -> unit = ...
The function extendToString extends the top-level overloaded operators toString and % with an additional case. Supply the name of a function of type T -> string where T is an “atomic” type. The type T is atomic when it is a type name without arguments and there is no definition available in scope that allows the compiler to deduce it is equal to a non-atomic type.
(The restriction to atomic types exists as far as I can tell for no good reason whatsoever. For unknown reasons whoever implemented SML/NJ's type inference machinery thought this was a good idea. Maybe the restriction will be fixed in a future version of SML/NJ.)
val evalString : string -> unit = ... val useString : string -> unit = ...
Both evalString and useString enter into a loop of parsing the string to find a prefix that is a top-level SML form, type checking and compiling this form, and executing the resulting machine code, and then repeating with the rest of the string. The difference is that evalString throws any new top-level definitions away when it is done and returns (only their side-effects persist) while useString extends the top-level environment with the new definitions when it returns. In both cases new top-level definitions are available for remaining forms processed during the call to evalString or useString.
structure StringSet : ORD_SET = ... structure StringMap : ORD_MAP = ...
The structures StringSet and StringMap provide implementations of sets and finite maps (finite maps are sometimes called “dictionaries”) for the type “string”. The operations available are defined in the ORD_SET and ORD_MAP signatures, which are part of the SML/NJ library.
val q = SmlnjScriptUtils.q val qq = SmlnjScriptUtils.qq
The functions q and qq are abbreviations for things in SmlnjScriptUtils, but you definitely want to use these short names rather than the versions in SmlnjScriptUtils, because the entire point is that they give you a compact syntax for writing strings with stuff spliced into a string template. These operators are intended to be used with SML/NJ's quasiquote syntax to make quoting operators. (The quasiquote syntax is also supported by Moscow ML and the ML Kit, but is not supported by some other major SML implementations like MLton and Poly/ML.)
To illustrate, here are some expressions that evaluate to true:
let val x = 7 in q`The value of x is ^(% x)!` = "The value of x is 7!" end
let val y = "Jack" in qq`My name is ^y.\n` = "My name is Jack.\n" end
The difference between q and qq is that qq interprets SML-style escape syntax in the literal text while q does not. So for example, this evaluates to true:
q`This is not a newline: \n` = "This is not a newline: \\n"
The names q and qq are loosely inspired by Perl's q and qq operators.
In case you are curious, the source code of smlnj-script is currently available in this directory on the HWU CS department file systems:
/u1/staff/jbw/smlnj-script-git/smlnj-script/
I recommend against making your own copy of smlnj-script unless it is necessary, because it is quite big, as it includes a full copy of the SML/NJ compiler.
If you have a HWU CS computer account, you don't need a copy of smlnj-script for your own computer because you can log in to the HWU CS department computers from home with SSH, for example you could log in with a command like this:
ssh linux03.macs.hw.ac.uk
If you want to get the smlnj-script program for your own computer, here is the easiest procedure:
cd "$HOME" git clone ssh://linux02.macs.hw.ac.uk/u1/staff/jbw/smlnj-script-git/smlnj-script cd smlnj-script make
At this point, you should have a binary at $HOME/smlnj-script/smlnj-script. You can then test it as follows:
PATH="$HOME/smlnj-script:$PATH" cd examples ./hello-world
There are various problems that you might encounter. You will need a UNIX machine (any recent Linux will do). The most likely problem is that your installation of SML/NJ (you need SML/NJ installed first) might be missing the heap2exec program or support for the NLFFI foreign function interface. For example, the Ubuntu “smlnj” packages generally have this problem. If so, you will need to reinstall SML/NJ from source with the right options set. Follow the instructions on the SML/NJ web site and make sure the following lines are uncommented in the file config/targets before building SML/NJ:
request ckit ... request ml-nlffi-lib ... request ml-nlffigen ... request heap2asm
Date: 2012-02-15 22:40:56