Recaffeinating Java ☕️ with custom semantics and extensions
Java Shell
Latest commit e1a5ef6 Oct 27, 2016 @biboudis biboudis committed on GitHub Update README.md
Permalink
Failed to load latest commit information.
recaf-desugar Make Using example more generic Sep 8, 2016
recaf-runtime Make Using example more generic Sep 8, 2016
resources Add files via upload Oct 27, 2016
.gitignore Update benchmark Feb 8, 2016
.project Add top level pom Feb 9, 2016
.travis.yml Add webhooks to travis Aug 29, 2016
LICENSE.md
README.md Update README.md Oct 27, 2016
pom.xml Add top level pom Feb 9, 2016
testgen Add convenient scripts Mar 14, 2016
testnogen Fix TestUsing Aug 29, 2016

README.md

Build Status Join the chat at https://gitter.im/cwi-swat/recaf

What is Recaf?

Recaf is an open-source framework for authoring extensions (dialects) as libraries for Java. You can redefine every major syntactic element of the language, either add new ones or create your own flavor of Java that matches your needs. It can be used to give syntactic support to libraries, to generate and instrument code. Last but not least you can experiment with the design and implementation of Java extensions in plain Java.

  • Recaf: Java Dialects as Libraries (pdf) will be presented at the 15th International Conference on Generative Programming: Concepts & Experience (GPCE'16) in Amsterdam.

Getting Started

> git clone git@github.com:cwi-swat/recaf.git
> cd recaf
> ./testnogen # or testgen to regenerate all test files

Hello World with a simple example!

Imagine we want to create our own try-with-resources statement for Java! Let's call it using.

String method(String path)  {
  using (BufferedReader br : new BufferedReader(new FileReader(path))){ 
    return br.readLine();
  }
}

The code above is not valid Java, but it can, most certainly be through Recaf! Using two small additions, we enable the generic transformation of that snippet of code into something that we can override and define. In our case, our goal is to define what using does. So firstly, we decorate method with the recaf keyword. That enables the generic translation of our code. Secondly, we use a special field that we also decorate with the same keyword. By doing that we enable an extension for the scope of the whole compilation unit (the are other ways as well). Now every recaffeinated method uses the Using extension. As you may have guessed this object defines the behavior of our new keyword.

recaf Using<String> alg = new Using<String>();

recaf String method(String path)  {
  using (BufferedReader br : new BufferedReader(new FileReader(path))){ 
    return br.readLine();
  }
}

The key point is that Recaf transforms code at compile time, applying a predefined set of rewrite rules (no need to hack around it or even to know anything about it). The user does not get involved with parsers, language workbenchs and compilers. The code is transformed into method calls of a certain interface. The runtime instance of that structure defines the whole operational behavior of the program.

For example, the statement return 1 + 1 is transformed in the following nested method calls chain: alg.Return(alg.Plus(alg.Lit(1), alg.Lit(1))). What happens in reality depends on the implemention of the runtime instance of alg. With Recaf we are able to override not only expressions but also the control flow of the program and orchestrate it with libraries for a certain style of execution e.g., for asynchronous computing, reactive computing.

Without diving into the gory details of Recaf, the body of method above, is transformed into method invocations to the Using object above (named alg). Note that this is valid Java now.

String method(String path) {
  return alg.Method(
    alg.Using(() -> new BufferedReader(new FileReader(path)), (BufferedReader br) -> {
      return alg.Return(() -> br.readLine());
    }));
}	

Language extension gymnastics with Recaf

In the following section we present some extensions of Java developed with Recaf. For demonstration purposes you can browse all extensions in the directory with the recaf files and examine the generated java code.

Spicing up Java (controlling the flow)

We support three new syntactic constructs that manipulate the control flow of the program. We can add support for generators, async and async* operations by programming the basic denotations of each operation as described in the Spicing up Dart with Side Effects article.

Generators

recaf Iterable<Integer> range(int s, int n) {
  if (n > 0) {
    yield! s;
    yieldFrom! range(s + 1, n - 1);
  }
}

Async

recaf Future<Integer> task(String url) {
  await String html = fetchAsync(url);
  return html.length();
}

Async*

recaf <X> Observable<X> print(Observable<X> src) {
  awaitFor (X x: src) {
    System.out.println(x);
    yield! x;
  }
}

Parsing Expression Grammars (PEGs)

The following example demonstrates language embedding and aspect-oriented language customization. We have defined a DSL for Parsing Expression Grammars (PEGs). The lit! construct parses an atomic string, and ignores the result. let is used to bind intermediate parsing results. For terminal symbols, the regexp construct can be used. The language overloads the standard sequencing and return constructs of Java to encode sequential composition and the result of a parsing process. The constructs choice, opt, star, and plus correspond to the usual regular EBNF operators. The choice combinator accepts a list of alternatives. The following parser implements parsing for primary expressions.

recaf Parser<Exp> primary() {
   choice {
      alt "value":
        regexp String n = "[0-9]+";
        return new Int(n);
      alt "bracket":
        lit! "("; let Exp e = addSub(); lit! ")";
        return e;
    }   
}

Constraint solving as a language

In this example we demonstrate deep embedding of a simple constraint solving language. We have developed a Recaf embedding which translates a subset of Java expressions to the internal constraints of Choco solver, which can then be solved. Note the use of recaff as we enable java expression overriding in this example.

recaf Solve alg = new Solve();
recaff Iterable<Map<String,Integer>> example() {
  var 0, 5, IntVar x;
  var 0, 5, IntVar y;
  solve! x + y < 5;
}

Program your UI with a Java-Swul like embedding

We have implemented SWUL as an extension of the direct style implementation of Java, without virtualized expressions. For example part of the following demonstration UI can be generated by the snippet that follows:

recaf JPanel example1() {
  panel { 
   label text! "Welcome!";
   panel border {
      center label text! "Hello world!";
      south panel grid {
          row 2:  
            button {
              text! "cancel";
              action { System.out.println("Cancel"); }
            }
            button text! "ok";
        }
   }
  }
}

We enumerate all the small extensions we developed with Recaf. The code is in plain Java and each file corresponds to one extension.

Bugs and Feedback

To discuss bugs, improvements and post questions please use our Github Issues. Also, join the chat at https://gitter.im/cwi-swat/recaf!

Team

Powered by Rascal

Under the hood we use the Rascal Metaprogramming Language. It is included as a runtime dependency in the project.