Skip to content

br0ns/PreML

Repository files navigation

PreML

PreML is a preprocessor for Standard ML. It’s aim is to extent the language with some useful syntactic sugar.

It is not the aim of PreML to turn SML into a new language. Thus I try to keep the rewritings simple and easy to understand.

PreML will try to preserve your code as good as it can; Error messages from your compiler will always refer to the correct line, and code parts that doesn’t use PreML’s features will not be changed.

Installation

Right now PreML only compiles under MLTon. If you have MLTon installed you can build PreML simply by simply typing

make

After which PreML can be found in the bin folder.

There’s also an install goal which installs the binaries in /usr/local/bin, so you probably need to be root to do that.

Using Smackage

PreML also installs through Smackage. I think it’s a really cool thing the Smacakge guys have going so I suggest you use this method.

Assuming you have a working installation of Smackage (and if you don’t: go to https://github.com/standardml/smackage to see how to get it) installation should be as easy as

smackage source preml git git:https://github.com/mortenbp/PreML.git
smackage refresh
smackage get preml
smackage make preml
smackage make preml smackage-install

This was tested with Smackage v0.6.0.

Usage

After installation two programs are available: preml which is the preprocessor and premlton which is just a shellscript wrapper for MLTon. Type

preml --help

to get a help message.

The MLTon wrapper premlton makes the use of PreML more or less transparent to the user:

premlton MyProject.mlb

will preprocess MyProject.mlb, produce an executable MyProject from the result, and clean up the preprocessed files.

Features

PreML understands nine kinds of syntactic sugar as of this writing (v1.4.1).

Examples of most forms can be found in the examples folder.

do syntax

A syntax very much like Haskells do syntax is supported. A do block begins with either just do or do with X where X is some structure implementing >>= and return, and ends with end.

Each line in a do block except the first and the last must begin with a semi colon.

An example:

do with List
 ; x <- [1, 2, 3]
 ; y <- [4, 5, 6]
 ; return (x + y)
end

open the result of a functor application

Instead of having to bind the result of a functor application to a structure and then opening it one can simply write

open F(X)

where F is a functor name and X can be anything usually allowed as a functor argument.

Be aware that PreML simply invents a new structure name, binds it to the result and the opens it. Thus this syntax is not allowed in let-blocks.

Extend a structure through a functor

A use of functors is to derive functionality based on an interface. Structures implementing that interfaces (a compare function for instance) can then get the derived functionality through the functor for free.

An example functor:

functor Range (eqtype t
               val next : t -> t) =
struct
fun range (a, b) = if a = b
                   then [a]
                   else a :: range (next a, b)
end

and some structure implementing the interface

structure MyInt =
struct
open Int
eqtype t = int
fun next n = n + 1
end

structure MyChar =
struct
open Char
eqtype t = char
fun next c = chr (Rod c + 1)
end

Existing structures

Now we have two structures implementing the interface of our functor. To extend those structure we can write

extend MyInt as (Range)
extend MyChar as (Range)

Now the structures have both the next and the range function (and all the other functions pulled in from Int and Char).

New structures

Another possibility is to extend the structures as we’re defining them.

Then the definition of MyInt is

structure MyInt =
struct (Range)
open Int
eqtype t = int
fun next n = n + 1
end

It is possible to extend structures through more than one functor at a time. Simply put a list of functors in the parenthesis:

extend Foo as (Bar, Baz)
structure Foo =
struct (Bar, Baz)
...
end

The Baz functor will then be called with union of the original structure and the output from Bar.

Annotate Fail exceptions with a file position

Instead of

raise Fail "foo bar baz"

one can write

raise FailWithPosition "foo bar baz"

The result is that the position of the error message (which is not necessarily the same as where the exception is raised) will be prepended to it.

The resulting error message will look like this:

! Uncaught exception:
! Fail  "/tmp/sml3238ZQE(26:24): foo bar baz"

which says that the exception is declared on line 26 in file /tmp/sml3238ZQE.

Include files

The keyword include has been overloaded, such that if what follows is enclosed in quotation marks it will be treated as a (relative) file path and included verbatim. More than one file can be included at a time.

If the word singleline (no quotation marks) follows immediately after include the included file(s) will be placed on a single line in order to preserve error message positions.

It goes without saying that debugging can be very hard in the event that the included file(s) is responsible for the error.

Filtered open

Say one needs values foo, bar and baz from structure Qux. One can simply write

open (foo, bar, baz) Qux

Note that this only works for values. PreML does not do type checking so it can’t know if bar is a value, type, exception or datatype.

List comprehensions

PreML supports Haskell style list comprehensions.

Some examples:

val xs = MyInt.range(~5, 5)
val foo = [x | x <- xs, x > 0]
val bar = [x * y | x <- xs, y <- xs, y < x]

Partially applied tuple constructors

Again inspired by Haskell tuples need not be fully applied.

Some examples:

val a = (,42) 42
val b = (42,) 42
val c = (,) 42 42
val d = (42,,42) 42
fun e x = (,x,)
val f = e 41 42 43

val xs = map (42,) [1,2,3]

Emacs configuration

Included with PreML is the file sml-defs.el which modifies Emacs’ sml-mode to work with the do notation. On my system the file resides in

/usr/share/emacs/site-lisp/sml-mode

When using sml-mode in Emacs you can have your interactive interpreter preprocess your buffer before running it by putting the following in your .emacs

(setq sml-use-command
      (concat
       "local "
       "val filei = \"%s\" "
       "val fileo = filei ^ \".preml\" "
       "val _ = OS.Process.system (\"preml \\\"\" ^ filei ^ \"\\\"\") "
       "val _ = use fileo "
       "val _ = OS.FileSys.remove fileo "
       "in end")
      )