Interpreter for a simple SML-inspired functional language.
let partition = fn p l ->
let part = fn yes no l -> case l of
| [] -> (rev yes, rev no)
| x :: l -> if p x then part (x :: yes) no l else part yes (x :: no) l
in
part [] [] l
Make sure you have GHC version 9.0.2 or lower. Code generated by BNFC does not compile on new versions (at least for me).
Cabal is used to build the project:
cabal update # Required if cabal package list was never fetched
cabal run
There is also a Makefile
that is a shortcut for the above, and also copies the executable to ./interpreter
:
make
./interpreter ./good/01-02-hello-world.ml
If you want to pass arguments to the interpreter, you need to use the following version of cabal run
:
cabal run jppml -- ./good/01-02-hello-world.ml
cd src/
bnfc -m --functor Syntax.cf
make
This does not run examples from the bad/
directory. They shouldn't be doing anything more thorough than the tests in test/
though.
cabal test
Use ./interpreter
without arguments to enter REPL (recommended, prints things in color and can display information about types). Or ./interpreter <file>
to interpret code from a file.
Important: the interpreter will only work in the project's main directory. It needs to load built-in values and always assumes they are in ./src/Core.ml
.
The preprocessor aggresively desugars language constructs into function applications, for example even if-then-else is replaced with __if
of type bool -> (unit -> 'a) -> (unit -> 'a) -> 'a
. The set of built-in functions not implemented in JPPML itself is minimal.
Source code uses identifiers prefixed with "__" for its reserved values, especially for functions that correspond to built-in operators. They can be defined only in let declarations/expressions and can't be redefined later. Type names and constructor names can't be redefined either.
The typechecker checks for missing symbols, illegal bindings, and does SML-style type reconstruction. You can see types of values in the interactive interpreter. The typechecker tries its best to report useful errors, with position of the error included in error messages.
The preprocessor and the typechecker allow interpreter to be relatively small and do relatively little. Values are stored indirectly, with type Ptr
used as location designator and Val
as actual value.
- Syntax.cf and auto-generated AbsSyntax.hs, LexSyntax.hs, LexSyntax.x, ParSyntax.hs, ParSyntax.y, PrintSyntax.hs, SkelSyntax.hs – syntax of the language and lexer, parser, utilities auto-generated by BNFC
- Preprocess.hs – preprocessor. Simplifies AST, desugars code
- Typecheck.hs – type checker
- Infer.hs – type inference for the type checker, does type inference
- Eval.hs – interpreter
- app/Main.hs – entry point for the executable
- Core.hs – built-in values for the interpreter
- Core.ml – standard library. Includes functions that the interpreter depends on
- List.ml – standard
List
module. A great example of JPPML code - good/, bad/ – folders with examples of correct and incorrect code. Correct code doesn't crash so examples of raising errors are in
bad/
. - test/Main.hs and others in test/ – files with many small tests, and source code for the program to run all tests