Experiments with An Incremental Approach to Compiler Construction on the JVM.
This is a hobby project. Expect lots of bugs and don't expect to be able to use this for anything useful.
This project is built with mill version 0.8.0 and requires Java 8 or above to be installed.
To build the project:
mill _.compile
To run the test suite:
mill _.test
To build an executable at the root of the repo:
mill main.generateRunScript
You should be able to use this executable to compile your own programs.
See the help text for details of the command line interface:
$ ./inc --help
inc 0.1.0-SNAPSHOT
Usage: inc [options] <file>
--help
--version
-cp, --classpath <value>
The classpath to use when compiling. Defaults to the current directory.
-d, --destination <value>
The destination directory for compilation output. Defaults to the current directory.
--print-parser Print syntax trees after the parsing phase
--print-resolver Print syntax trees after the name resolution phase
--print-typer Print syntax trees after the type inference phase
--print-codegen Print the java bytecode generated by the code generation phase
--print-timings Print the time taken in each phase
--verify-codegen Run a verifier on the code produced in codegen
--exit-on-error <value> Exit the Java VM with exit code 1 if an error occurs
--stop-before <value> Stop before the named phase. Currently defined phases: parser, resolver, typer, codegen
<file> The source file to compile
module Test {}
module Test/Let {
let litInt = 42
let litLong = 42L
let litFloat = 42.0F
let litDouble = 42.0D
let litBool = true
let litChar = 'a'
let litString = "foo"
let litUnit = ()
}
module Test/If {
let choose = if true then 1 else 0
}
module Test/Func {
let id = a -> a
let compose = f -> g -> a -> f(g(a))
}
module Test/Id {
import Test/Func
// Imports are qualified by default
let app = Func.id(1)
}
module Test/Compose {
import Test/Func.{ compose, id }
// Individual symbols can be imported unqualified
let pointless = compose(id)(id)(1)
}
module Test/Data {
data Either[A, B] {
case Left(a: A)
case Right(b: B)
}
data Fix[F] {
case Unfix(unfix: F[Fix[F]])
}
}
module Test/Data {
data Option[A] {
case Some(a: A)
case None()
}
data List[A] {
case Cons(head: A, tail: List[A])
case Nil()
}
let last = list -> match list with {
case Cons { head, tail: Nil {} } -> Some(head)
case Cons { tail } -> last(tail)
case Nil {} -> None()
}
}
The dependency structure of the modules is described in the following diagram:
The relationship of the modules during compilation is as follows:
This project has a small benchmark suite which runs against every commit.
It consists of a collection of source files whose compilation time is benchmarked using the command-line benchmark utility hyperfine.
The results are charted here.
Bare metal benchmarking agents are too expensive for the moment!