inc
September 5, 2020 ยท View on GitHub
Experiments with An Incremental Approach to Compiler Construction on the JVM.
Disclaimer
This is a hobby project. Expect lots of bugs and don't expect to be able to use this for anything useful.
Getting started
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
Language features
Modules
module Test {}
Let bindings and literals
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 = ()
}
If expressions
module Test/If {
let choose = if true then 1 else 0
}
Functions
module Test/Func {
let id = a -> a
let compose = f -> g -> a -> f(g(a))
}
Imports
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)
}
Data declarations
module Test/Data {
data Either[A, B] {
case Left(a: A)
case Right(b: B)
}
data Fix[F] {
case Unfix(unfix: F[Fix[F]])
}
}
Pattern matching
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()
}
}
Structure of the project
The dependency structure of the modules is described in the following diagram:

The relationship of the modules during compilation is as follows:

Continuous Benchmarks
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!