Tutorial 04: Compiling to Multiple Targets¶
Write once in ll-lang, emit F#, TypeScript, Python, Java, C#, or LLVM IR.
The source¶
module Shapes
Shape = Circle Float | Rect Float Float | Empty
area(s Shape) Float =
| Circle r -> 3.14159 * r * r
| Rect w h -> w * h
| Empty -> 0.0
describe(s Shape) Str =
| Circle r -> strConcat "circle r=" (floatToStr r)
| Rect w h -> strConcat "rect " (strConcat (floatToStr w) (strConcat "x" (floatToStr h)))
| Empty -> "empty"
Compile to each target¶
lllc build --target fs shapes.lll # → shapes.fs
lllc build --target ts shapes.lll # → shapes.ts
lllc build --target py shapes.lll # → shapes.py
lllc build --target java shapes.lll # → shapes.java
lllc build --target cs shapes.lll # → shapes.cs
lllc build --target llvm shapes.lll # → shapes.ll
Generated F# (default)¶
module Shapes
type Shape =
| Circle of float
| Rect of float * float
| Empty
let area (s: Shape) : float =
match s with
| Circle r -> 3.14159 * r * r
| Rect(w, h) -> w * h
| Empty -> 0.0
let describe (s: Shape) : string =
match s with
| Circle r -> "circle r=" + string r
| Rect(w, h) -> "rect " + string w + "x" + string h
| Empty -> "empty"
F# discriminated unions. Idiomatic — you can drop this into any F# project.
Generated TypeScript¶
// Generated by lllc
type Shape =
| { readonly tag: "Circle"; readonly r: number }
| { readonly tag: "Rect"; readonly w: number; readonly h: number }
| { readonly tag: "Empty" };
function area(s: Shape): number {
switch (s.tag) {
case "Circle": return 3.14159 * s.r * s.r;
case "Rect": return s.w * s.h;
case "Empty": return 0.0;
}
}
function describe(s: Shape): string {
switch (s.tag) {
case "Circle": return "circle r=" + s.r.toString();
case "Rect": return "rect " + s.w.toString() + "x" + s.h.toString();
case "Empty": return "empty";
}
}
Tagged union pattern. TypeScript's switch exhaustiveness is enforced by the never fallthrough — the compiler emits that too.
Generated Python¶
# Generated by lllc
from __future__ import annotations
from dataclasses import dataclass
from typing import Union
@dataclass(frozen=True)
class Circle:
r: float
@dataclass(frozen=True)
class Rect:
w: float
h: float
@dataclass(frozen=True)
class Empty:
pass
Shape = Union[Circle, Rect, Empty]
def area(s: Shape) -> float:
match s:
case Circle(r=r): return 3.14159 * r * r
case Rect(w=w, h=h): return w * h
case Empty(): return 0.0
def describe(s: Shape) -> str:
match s:
case Circle(r=r): return "circle r=" + str(r)
case Rect(w=w, h=h): return "rect " + str(w) + "x" + str(h)
case Empty(): return "empty"
Python 3.10+ structural pattern matching. Frozen dataclasses for immutability.
Generated Java 21¶
// Generated by lllc
public sealed interface Shape permits Shape.Circle, Shape.Rect, Shape.Empty {
record Circle(double r) implements Shape {}
record Rect(double w, double h) implements Shape {}
record Empty() implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle(double r) -> 3.14159 * r * r;
case Rect(double w, double h) -> w * h;
case Empty() -> 0.0;
};
}
static String describe(Shape s) {
return switch (s) {
case Circle(double r) -> "circle r=" + r;
case Rect(double w, double h) -> "rect " + w + "x" + h;
case Empty() -> "empty";
};
}
}
Java 21 sealed interfaces + records + pattern switch. Exhaustiveness is enforced by the JVM.
Multi-target projects via lll.toml¶
For a project that needs to emit to multiple targets simultaneously, configure lll.toml:
[project]
name = "shapes"
[platform]
use = ["fsharp", "typescript", "python"]
Then one command emits all three:
lllc build
# → bin/fsharp/shapes.fs
# → bin/typescript/shapes.ts
# → bin/python/shapes.py
No flags, no scripts. The manifest drives the build.
When to use which target¶
| Target | Use when |
|---|---|
fs (default) |
F# / .NET ecosystem, self-hosting the compiler |
ts |
Frontend, Node.js, Deno — share types across a TypeScript codebase |
py |
Data pipelines, scripting, teams working in Python |
java |
JVM services, Android, teams on Java 21+ |
cs |
.NET/C# integration and tooling pipelines |
llvm |
IR-level toolchains, low-level compiler integration |
Checking target compatibility¶
lllc build --target ts shapes.lll
The compiler reports target-specific compatibility errors during build. E007 PlatformMismatch remains reserved for Platform.* availability checks.
Next steps¶
- ../why-ll-lang.md — the full value proposition with benchmark numbers
lllc mcp— wire an LLM agent to the compiler for instant multi-target feedback