Why ll-lang¶
ll-lang is not a general-purpose language. It is optimized for one use case: LLM agents writing correct code on the first attempt.
Every design decision is evaluated against that goal.
The problem with mainstream languages for LLM codegen¶
When an LLM generates Python or TypeScript, two things compound:
-
Verbose syntax.
function,type,interface,class,return,{,},;— ceremony that consumes tokens without encoding logic. An LLM generating a simple ADT plus a pattern match spends half its token budget on structural punctuation. -
Late error detection. Type errors surface at runtime, after execution, often after damage is done. An LLM generating a 200-line module gets no signal until it runs — and the error message is a stack trace written for humans, not for a model to parse programmatically.
The feedback loop is: generate → execute → read prose error → regenerate. Each iteration burns context.
The ll-lang answer¶
Four properties, all working together:
1. Token-efficient syntax¶
No fn, type, in, then, with, return, {}, ;. 15 keywords total. Declarations use an uppercase/lowercase naming convention instead of reserved words.
Benchmark numbers (cl100k_base tokenizer, from benchmarks/results/):
| Pattern | ll-lang | F# | TypeScript | Python | Java |
|---|---|---|---|---|---|
| 3-constructor sum type | 11 | 14 | 36 | 49 | 35 |
| Pattern match (3 arms) | 39 | 43 | 58 | 52 | 58 |
| Curried function | 13 | 21 | 20 | 18 | 16 |
| Parametric ADT | 8 | 12 | 23 | 47 | 31 |
On real code (the Map red-black tree implementation): ll-lang is 17% more compact than compiled F#, and 1.3–5.9× more compact than TypeScript/Python/Java on type-heavy code.
The bootstrap compiler (2,900+ lines of ll-lang) is 8% more compact than its F# output — an entire compiler, measured end-to-end.
2. Compiled = works¶
The compiler catches at compile time what other languages defer to runtime:
| Error | Code | Example |
|---|---|---|
| Type mismatch | E001 |
E001 12:5 TypeMismatch Str Str[UserId] |
| Non-exhaustive match | E003 |
E003 15:1 NonExhaustiveMatch Shape missing:Empty |
| Unit mismatch | E004 |
E004 20:9 UnitMismatch Float[m] Float[s] |
| Tag violation | E005 |
E005 7:14 TagViolation Str[Email] Str[UserId] |
No null, no exceptions, no mutable state. If lllc build succeeds, the logic is correct by construction.
For an LLM agent, this turns code generation into a tight loop: write → lllc build → fix one error code → repeat. No execution required.
3. LLM-readable errors¶
Every error is one line, machine-readable, regex-parseable:
EXXX line:col ErrorKind details
E001 12:5 TypeMismatch expected:Str[UserId] got:Str hint:wrap:UserId
E003 15:1 NonExhaustiveMatch type:Shape missing:Empty
E008 3:1 InfiniteType var:a cycle:a=List[a]
No stack traces. No paragraphs. An LLM can lookup_error E003 via the MCP server and get the exact fix — no scraping required.
4. MCP integration¶
lllc mcp runs a stdio MCP server with 10 tools. Wire it to Claude Code, Cursor, or Zed:
{
"mcpServers": {
"lllc": {
"command": "lllc",
"args": ["mcp"]
}
}
}
An agent can now:
check_source → validate syntax + types (no codegen, fast)
compile_source → get generated F#/TS/Py/Java
run_file → compile + execute
lookup_error → get explanation + fix for E003
stdlib_search → find functions by name or type
grammar_lookup → get EBNF production for any grammar rule
The agent asks "does this compile?" and gets a structured JSON response with error codes, line numbers, and fix hints. No shell output parsing. No false positives from warnings mixed into stdout.
Self-hosting: the compiler is written in itself¶
The ll-lang compiler is self-hosted. The entire pipeline — lexer, parser, elaborator, Hindley-Milner inference, and all four codegens — is written in ll-lang (5,857 LOC across 10 stdlib modules).
Bootstrap fixpoint: compiler₁.fs == compiler₂.fs. Compile the compiler with itself, compare output byte-for-byte — they match.
This is not just a parlor trick. It means:
- The language is expressive enough for production compiler work. Recursive descent parsers, red-black trees, Algorithm W type inference, multi-target code emission — all in ll-lang.
- The stdlib is real code, not toy examples. Every idiom in the tutorials works at scale.
- The compiler validates itself. Any change to the language that breaks the stdlib breaks the bootstrap test.
The test suite includes inline stdlib tests that run the self-hosted compiler end-to-end on every commit (see CI for current totals).
Multi-target output¶
Write logic once, compile to any runtime:
lllc build --target fs # F# discriminated unions
lllc build --target ts # TypeScript sealed interfaces
lllc build --target py # Python @dataclass + Union
lllc build --target java # Java 21 sealed interfaces + records
lllc build --target cs # C# compile-safe skeleton backend
lllc build --target llvm # LLVM IR deterministic stub backend (experimental subset)
An LLM agent can prototype in ll-lang (fast iteration, compile-time safety), then ship to whichever runtime the rest of the team uses.
When to reach for ll-lang¶
ll-lang is the right tool when:
- An LLM agent is generating non-trivial type-safe logic
- You need to compile to multiple runtimes from one source
- You want compile errors, not runtime crashes, as the feedback signal
- Token budget matters (context windows are finite)
ll-lang is not the right tool when:
- You need rich ecosystem integrations (use the target language directly)
- You need async IO, WASM, or native binaries today (on the roadmap)
- The team does not want to add a compiler dependency
Getting started¶
git clone https://github.com/Neftedollar/ll-lang.git
cd ll-lang && dotnet build
lllc run hello.lll
Tutorials: 01 Hello World · 02 Types and Patterns · 03 Building a Parser · 04 Multi-target