Frontends
Java Frontend
Java Frontend
The Java frontend is a BraceFrontendBase subclass delivered as a single
1,047-line header. Like PHP and Rust, it implements modern Java features
through a pipeline of seven static source-rewrite transforms that
run before the brace parser sees the source.
| File | Lines | Purpose |
|---|---|---|
frontends/java/JavaFrontend.h |
1,047 | Frontend class with all method bodies inline |
frontends/java/JavaStdLib.h |
78 | findStdLibMapping for System.out.*, Math.*, Integer.parseInt, etc. |
Language registration
// JavaFrontend.h:9-10
std::string languageName() const override { return "java"; }
std::vector<std::string> fileExtensions() const override { return {".java"}; }
Only .java is recognised.
Preprocessing pipeline
JavaFrontend::parse (line 12-23) applies the rewrites in this order:
| Order | Transform | Lines | Purpose |
|---|---|---|---|
| 1 | rewriteJavaTextBlocks |
736-779 | Lower """...""" text blocks to string literals |
| 2 | stripJavaMethodModifiers |
392-423 | Strip public, private, protected, static, final, abstract, synchronized from method headers |
| 3 | stripJavaInterfaceMethodDeclarations |
529-597 | Interface method bodies become stubs |
| 4 | rewriteJavaRecordsToClasses |
599-735 | record Point(int x, int y) → class Point { int x; int y; ... } |
| 5 | stripJavaVarFromLambdaParams |
204-257 | (var x, var y) → (x, y) in lambda params |
| 6 | rewriteJavaArrowToJsArrow |
904-941 | -> → => so the brace parser sees lambda arrow |
| 7 | rewriteJavaVarDecls |
951-1043 | Type x = expr; → var x = expr; |
Print patterns
// JavaFrontend.h:26-31
return {
{"System.out.println", true, true},
{"System.out.print", true, false}
};
System.err.println and System.err.print are mapped via the
JavaStdLib table.
Features (file:line)
Text blocks (Java 15+)
String html = """
<html>
<body>
<p>Hello, %s!</p>
</body>
</html>
""";
rewriteJavaTextBlocks (line 736) implements a subset of JLS §13.5.6:
- Strip common leading whitespace (relative to the closing
"""). - Strip blank trailing lines.
- Replace embedded
\s(where supported) — not yet implemented. - Rebuild the content as a single string literal (line 825).
Lowered to:
String html = "<html>\n <body>\n <p>Hello, %s!</p>\n </body>\n</html>";
var keyword (Java 10+)
tryParseVarDecl (line 37) handles both var x = expr; and the
type-prefixed form via rewriteJavaVarDecls (line 951):
var list = new ArrayList<String>();
String name = "Alice";
Child c = new Child();
is rewritten to:
var list = new ArrayList<String>();
var name = "Alice";
var c = new Child();
so the brace parser's splitEmbeddedStatementStart doesn't split the
declaration into a stray type expression.
Records (Java 16+)
rewriteJavaRecordsToClasses (line 599) rewrites:
public record Point(int x, int y) {
public double distance() {
return Math.sqrt(x * x + y * y);
}
}
into:
public class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public double distance() {
return Math.sqrt(x * x + y * y);
}
}
Sealed classes (Java 17+)
sealed class Shape permits Circle, Square { … } is parsed as a regular
class. The sealed, permits, final, non-sealed keywords on class
members are stored in metadata but not enforced.
Pattern matching for instanceof (Java 16+)
if (obj instanceof String s) {
System.out.println(s.length());
}
stripJavaMethodModifiers and the brace parser handle this — s is
emitted as a VariableDeclNode whose value is the cast expression.
Pattern matching for switch (Java 21+)
String formatted = switch (obj) {
case Integer i -> "Integer: %d".formatted(i);
case String s -> "String: %s".formatted(s);
case null -> "null";
default -> obj.toString();
};
Switch expressions with patterns are recognised as ordinary switch
statements. The pattern case Integer i becomes a case with a
VariableDeclNode binding. Type patterns (case Type var name) work.
-> lambda-form switch expressions are converted to => via
rewriteJavaArrowToJsArrow.
Virtual threads (Java 21+)
Thread.startVirtualThread(() -> {
doWork();
});
Recognised as a normal method call; no special lowering for the virtual thread semantics (see limitations.md).
Lambda expressions (Java 8+)
list.forEach(s -> System.out.println(s));
list.removeIf((String s) -> s.isEmpty());
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
Java's -> is rewritten to => by rewriteJavaArrowToJsArrow
(line 904). The lambda parser produces a LambdaNode with
expressionBody for arrow-with-expression and body for arrow-with-block.
(var x, var y) -> … (Java 11+)
stripJavaVarFromLambdaParams (line 204) finds every ( var … ->
region and strips the var keyword so the brace parser sees plain
identifier parameters.
var for lambda parameters is the only Java-11+ syntactic form; type
annotations inside lambda parameters ((String s) -> …) are accepted.
Top-level statement kinds
tryParseLanguageSpecificStatement (line 94-191) handles:
| Statement | File:line | IR |
|---|---|---|
package com.foo; |
104-110 | BlockNode(metadata["java_package"]="com.foo") |
import com.foo.Bar; |
113-119 | ImportNode(module="com.foo.Bar") |
interface Name { … } |
122-131 | InterfaceDeclNode(name="Name") |
enum Name { A, B, C } |
134-155 | EnumDeclNode(name="Name") |
(args) -> body / arg -> body |
158-188 | LambdaNode |
Method declarations
tryParseFunctionDeclHeader (line 48) strips public, private,
protected, static, final, abstract, synchronized from the
header. The brace parser then handles the resulting line.
The body of the method is parsed recursively by BraceFrontendBase.
Standard library mapping (JavaStdLib.h)
The complete table at frontends/java/JavaStdLib.h:40-67:
| Java | MPL module.function |
|---|---|
System.out.println(...) |
Console.WriteLine(...) |
System.out.print(...) |
Console.Write(...) |
System.err.println(...) |
Console.ErrorWriteLine(...) |
System.err.print(...) |
Console.ErrorWrite(...) |
Math.abs(x) |
Math.Abs(x) |
Math.floor(x) |
Math.Floor(x) |
Math.ceil(x) |
Math.Ceil(x) |
Math.round(x) |
Math.Round(x) |
Math.sqrt(x) |
Math.Sqrt(x) |
Math.pow(x, y) |
Math.Pow(x, y) |
Math.max(a, b) |
Math.Max(a, b) |
Math.min(a, b) |
Math.Min(a, b) |
Math.log(x) |
Math.Log(x) |
Math.sin(x) |
Math.Sin(x) |
Math.cos(x) |
Math.Cos(x) |
Math.tan(x) |
Math.Tan(x) |
Math.random() |
Math.Random() |
System.exit(code) |
Process.Exit(code) |
System.getenv(name) |
Environment.GetVariable(name) |
System.getProperty(name) |
Environment.GetVariable(name) |
Integer.parseInt(s) |
Convert.ToInt(s) |
Double.parseDouble(s) |
Convert.ToFloat(s) |
String.valueOf(x) |
Convert.ToString(x) |
IR examples
Example 1 — record
public record Point(int x, int y) {
public double distance() {
return Math.sqrt(x * x + y * y);
}
}
After rewriteJavaRecordsToClasses:
public class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public double distance() {
return Math.sqrt(x * x + y * y);
}
}
After rewriteJavaVarDecls (no Type x = expr; to rewrite here).
After stripJavaMethodModifiers (no modifiers to strip).
Lowers to:
ClassDeclNode(name="Point",
metadata=["java_record"="true",
"java_record_components"="int,x;int,y"])
members:
VariableDeclNode(name="x", class_field="true")
VariableDeclNode(name="y", class_field="true")
constructor:
FunctionDeclNode(name="Point", isConstructor=true,
parameters=[("auto","x"),("auto","y")])
methods:
FunctionDeclNode(name="distance",
parameters=[],
body=StandardLibraryCallNode("Math","Sqrt",
BinaryOpNode("+",
BinaryOpNode("*", IdentifierNode("x"), IdentifierNode("x")),
BinaryOpNode("*", IdentifierNode("y"), IdentifierNode("y")))))
Example 2 — text block
String html = """
<html>
<body>Hello</body>
</html>
""";
After rewriteJavaTextBlocks:
String html = "<html>\n <body>Hello</body>\n</html>";
After rewriteJavaVarDecls:
var html = "<html>\n <body>Hello</body>\n</html>";
Example 3 — pattern matching for switch
static String describe(Object obj) {
return switch (obj) {
case Integer i -> "Integer %d".formatted(i);
case String s -> "String %s".formatted(s);
case null -> "null";
default -> "unknown";
};
}
-> becomes => (line 904). Switch becomes SwitchNode(expr=obj).
Pattern cases with bindings become SwitchCase with a binding
extractor in the body.
What is not supported (Java-specific gaps)
- Real sealed class enforcement:
permitsis parsed but not used to validate that the listed types are the only subclasses. - Virtual threads:
Thread.startVirtualThread(...)is an ordinary call — no coroutine semantics. - Scoped values (Java 21+ preview): not supported.
- Structured concurrency (Java 21+ incubator): not supported.
- String templates (Java 21+ preview, JEP 459):
STR."Hello \{name}"is parsed as an ordinary string concatenation. - Implicitly declared classes and instance main methods (Java 21+
preview):
void main()is parsed as a regular method. - Foreign Function & Memory API:
java.lang.foreignreferences pass through; no FFM bindings are emitted. recordcanonical constructor overrides: the canonical ctor is generated automatically; explicit compact constructors (public Point { … }) are not specially recognised.