Skip to content

Experiments with incremental compiler construction on the JVM

License

Notifications You must be signed in to change notification settings

DavidGregory084/inc

Repository files navigation

inc

Build Status License

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:

Dependency structure diagram

The relationship of the modules during compilation is as follows:

Diagram of compilation

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!