One AST, 38 target languages. An AST-First transpiler with type checking, effect inference, and capability-based security enforced at compile time.
Note
📢 Latest Release Notes (2026-08-27 v4.0.2)
- 🚀 Comprehensive Re-audit & Alignment:
- Legacy Reference Cleanup: Thoroughly updated target count references across
codegen/profile.go,compiler/doc.go,compiler/compiler.go,QUICK_REFERENCE.txt,skills/xiaoqinli/SKILL.md, anddocs/COMMUNITY_WHITELIST.md(aligned exactly to 38 backends). - Test Assertion & Dead Code Cleanup: Fixed profile count assertion in
codegen/codegen_test.go(aligned to 38) and cleaned up leftover comment headers for removed backends. - 100% Verification:
go vet ./...and package tests verified 100% green; recompiled and deployed binary toC:\Users\sj929\go\bin\xql.exe.
- Legacy Reference Cleanup: Thoroughly updated target count references across
You do not write text source code. You write a .xql.json file — an explicit abstract syntax tree:
{
"kind": "Program",
"declarations": [
{
"kind": "FunctionDecl",
"name": "greet",
"params": [{ "name": "name", "type": { "kind": "String" } }],
"returnType": { "kind": "String" },
"effects": ["pure"],
"grant": [],
"body": [
{
"kind": "ReturnStmt",
"value": {
"kind": "BinaryExpr",
"op": "+",
"left": { "kind": "Literal", "valueType": "String", "value": "Hello, " },
"right": { "kind": "Ident", "name": "name" }
}
}
]
}
]
}The compiler type-checks it, infers effects, verifies capability grants, links imported modules, and then emits source code for any of 38 backends.
Why this shape? Writing one AST instead of 38 dialects keeps semantics identical across targets. And for LLM-driven code generation, emitting a structured tree that a compiler will reject when wrong is far more reliable than emitting 38 flavours of surface syntax that only fail at runtime.
go build -o xql .Validate a program without generating anything:
$ ./xql validate --file examples/hello.xql.json
ok: all checks passedCompile the same AST to different languages:
$ ./xql compile --file examples/hello.xql.json --target go
package main
import "fmt"
func greet(name string) string {
return "Hello, " + name
}
func main() {
fmt.Println(greet("World"))
}$ ./xql compile --file examples/hello.xql.json --target py
def greet(name: str) -> str:
return ("Hello, " + name)
def main() -> None:
print(greet("World"))
if __name__ == "__main__":
main()Write to a file with --out, and list every backend with ./xql targets.
Every function declares the effects it performs and the capabilities it is granted. A caller must hold a superset of its callee's grants, checked before any code is generated.
Given readSecret declared with "grant": ["io"], and a main that calls it with an empty grant list:
$ ./xql compile --file cap_demo.xql.json --target go
[
{
"code": "XQL_E301",
"message": "function 'main' calls 'readSecret' but lacks required capabilities: [io]",
"suggested_fix": "Add the missing capability name to the caller function's @grant list.",
"level": "error"
}
]
$ echo $?
2Capability strings support wildcards (network:*). Grants are verified across the whole import graph, not just the entry file — moving privileged code into a module does not bypass the check.
Built-in effects: pure, network, filesystem, state.
Strict capability checking is on by default for compile; pass --no-strict-caps to relax it.
A program that never leaves the process is not much of a program. Calls into the
host platform — fetch, time.Sleep, document.createElement — are declared
with ExternDecl:
{
"kind": "ExternDecl",
"name": "fetch",
"params": [{ "name": "url", "type": { "kind": "String" } }],
"returnType": { "kind": "String" },
"effects": ["network"],
"grant": ["network"],
"targets": ["js", "ts", "chrome"]
}The name is matched verbatim against the callee, so dotted names like
time.Sleep and document.head.appendChild are declared exactly as they are
called. An extern has no body: the compiler emits the call and nothing else.
This is where capability security earns its keep. A host call is the one edge that actually reaches the outside world, and every caller must hold the extern's grant:
$ ./xql compile --file clock.xql.json --target go
[
{
"code": "XQL_E301",
"message": "function 'main' calls 'time.Sleep' but lacks required capabilities: [clock]",
...
}
]The declared effects also propagate: a function marked pure that calls a
network extern is rejected with XQL_E203.
targets restricts an extern to the backends whose host actually provides
it. Compiling a program that calls a browser API to a target that has never
heard of it is a compile error (XQL_E402) rather than output that only breaks
when someone runs it. Omit the field to allow every target.
params may be omitted entirely to declare an unchecked signature, for host
functions that are variadic or overloaded. Declared params are checked for
arity and argument types like any other call.
Methods. When the receiver is a runtime value the compiler cannot type —
res.json(), hud.classList.add() — declare the method instead, and it matches
any receiver:
{ "kind": "ExternDecl", "name": "json", "method": true, "effects": ["network"], "grant": ["network"] }The receiver is not verified, but the grant is still enforced at every call site.
Externs are not namespaced by their module: declare a platform's surface once
and import it, and the names stay callable as-is. Modules that declare the same
extern identically are merged; declarations that disagree are rejected
(XQL_E202).
| Category | Targets |
|---|---|
| Systems | go rust c cpp zig nim d fortran pascal |
| JVM / .NET | java kotlin groovy csharp |
| Scripting | py js ts ruby php perl lua tcl awk |
| Functional | haskell ocaml elixir julia crystal |
| Apple | swift ios shortcut |
| Shell | bash powershell bat |
| Mobile / Web | android chrome |
| Domain-specific | vala tccli |
android and ios emit multi-file project scaffolds (Gradle / Swift Package Manager) rather than a single source file.
How each target is verified. Advertising thirty-eight backends says nothing
about how well any one of them works. This table is generated from
compiler/verification.go, and the test suite fails if it drifts from the tier
the tests actually enforce.
| Evidence | Targets | What was checked |
|---|---|---|
| executed (33) | awk bash bat c cpp crystal csharp d dart elixir fortran go groovy haskell java js julia kotlin lua nim ocaml pascal perl php powershell py ruby rust swift tcl ts vala zig |
compiled and run, stdout asserted |
| compiled (3) | chrome ios tccli |
compiled by a real toolchain |
| smoke (2) | android shortcut |
codegen returns output; never compiled |
The executed tier needs 33 toolchains, which CI installs or inherits from the
runner image, and it sets XQL_E2E_REQUIRE=1 so a missing one fails the run
instead of skipping quietly.
Per-target caveats:
android— Gradle scaffold; structure checked, never assembledawk— rejects Result and struct literalsbash— rejects Resultbat— rejects struct literals and for-each;set /ais 32-bit, so int_width is excusedc— rejects Resultchrome— emits an extension bundle; the JavaScript is parsed, the browser is not consulted; rejects Resultcpp— rejects Resultcrystal— rejects Resultd— rejects Resultelixir— rejects Result, and break, continue or return from inside a loopfortran— rejects for-each loopsgroovy— rejects Resulthaskell— rejects Result, and break, continue or return from inside a loopios— SwiftPM package;swift buildsucceeds, nothing is runjs— rejects Resultnim— rejects Resultocaml— rejects Result, and break, continue or return from inside a looppascal— rejects for-each loopsperl— rejects Resultpowershell— rejects Resultshortcut— emits a Shortcuts workflow as JSON; structure checked, never imported; rejects Result, while, and break, continue or return inside a looptccli— emits Tencent Cloud CLI shell; no arithmetic, comparisons or structstcl— rejects Resultvala— rejects Result
Known limitations. A backend that cannot express a construct rejects it rather than silently degrading it:
| Construct | Rejected by |
|---|---|
Result<T, E> |
every target except the 16 below |
| for-each loops | fortran pascal |
| struct literals | bat awk tccli |
| arithmetic, comparisons, arrays | tccli |
Result<T, E> is the construct fewest backends implement. Sixteen do:
go rust ts py java csharp kotlin swift dart lua ruby php zig julia android ios
Twenty-seven of the others used to accept such a program and emit references to
a Result they never defined — Result.ok(users) and res.unwrap() copied
straight from the AST, naming a module that does not exist in Haskell, a command
that does not exist in PowerShell. They decline now. That is not a capability
being withdrawn; the output was never going to run. See
docs/breaking_changes.md.
All other targets carry real Result semantics. The kotlin and android
backends emit their own two-parameter Result<T, E> into the generated file's
package, which shadows the default-imported kotlin.Result<out T>; without it
the Gradle build fails with "One type argument expected".
No CI job assembles an APK — that needs an Android SDK and an AGP/Gradle/Kotlin
version triple that drifts with the runner image. The android scaffold is
verified structurally, and the Kotlin it emits is verified by the kotlin
end-to-end test, which compiles and runs the same generated constructs.
Split a program across files and wire them with ImportDecl:
{ "kind": "ImportDecl", "path": "./models.xql", "as": "models" }At compile time a linker resolves the import graph, merges every module into a single self-contained Program, and strips the now-meaningless alias qualifiers. Backends therefore only ever see a flat program — the path they are already well tested on.
Import cycles are rejected with XQL_E402, and cross-file symbol collisions are rejected before merging. See examples/e2e_workspace/ for a working three-file program.
ExternDecl is the exception to alias stripping and to namespacing: a host name
is one verbatim symbol, and every module that imports the declaring module can
call it under that same name.
xql compile --file <path.xql.json> --target <lang> [--out <output>] [--no-strict-caps]
xql validate --file <path.xql.json>
xql targets List all supported target languages
xql stdio MCP stdio mode
xql http [<:port>] [--mode rest] MCP / REST HTTP mode (default :8080)
Exit codes: 0 success · 1 validation failed · 2 compilation error · 3 argument error.
xql stdio speaks the Model Context Protocol over stdin/stdout, so an agent can drive the compiler directly. xql http serves the same tools over HTTP.
Tools exposed: compile, validate, targets, specs_inspect, specs_update, stdlib_matrix_inspect, stdlib_matrix_update, treesitter_mapping_inspect, treesitter_mapping_update, diagnostic_memory_inspect, diagnostic_memory_record, security_policy_inspect, codegen_strategy_inspect, codegen_strategy_update, skills_diagnose_and_fill, agent_search_query, agent_search_autoupdate.
xql http :8080 --mode rest
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /compile |
Compile an AST to a target language |
| POST | /validate |
Run all semantic checks only |
| GET/POST | /specs |
Inspect / update language profiles |
| GET/POST | /codegen/strategy |
Inspect / update codegen strategy |
| GET/POST | /evolution/diagnostics |
Inspect / record learned diagnostic fixes |
| GET/POST | /api/v1/search |
Query the agent search index |
| POST | /api/v1/search/autoupdate |
Rebuild the search index |
| GET | /skills/ |
Fetch embedded skill documents |
| GET | /health |
Liveness and version |
| GET | /metrics |
Prometheus metrics |
/metrics returns a stub unless built with -tags metrics.
docker compose up --buildThe image builds a static binary and ships it alongside Python, Node.js, and Go toolchains so generated code can be executed in the sandbox. The MCP HTTP server listens on :8080.
xiaoqinli/
main.go CLI entry point
ast/ AST node definitions, JSON parser, stable binary codec
check/ Type checker, effect inference, capability verifier
compiler/ Public library API: parse → check → link → codegen
codegen/ 38 language backends + dispatch
evolution/ Self-evolution state: diagnostic memory, skills, search index
server/ MCP (stdio + HTTP) and REST servers
vfs/ Session-scoped in-memory filesystem
skills/ Embedded skill documents (go:embed)
remedy/ Defect remediation helpers
examples/ Sample .xql.json programs
docs/ Architecture decision records
go build ./... # build everything
go vet ./... # static analysis
go test ./... # full test suite
go test -tags metrics ./... # with Prometheus metrics enabledThe end-to-end suite in codegen/local_e2e_test.go compiles the sample workspace and executes the result with real toolchains (Ruby, Lua, PHP, Java, …). Each language is skipped automatically when its toolchain is absent, so a partial local environment still yields a green run.
compiler/conformance_test.go asks the harder question: not whether each backend produced a program, but whether they all produced the same one. It runs a corpus of examples with known output in every language a toolchain here can run, and compares stdout line by line. That is what one AST across thirty-eight targets has to mean, and it is how a range loop that iterated one step too far in eight backends — panicking in Go, IndexError in Python, NaN in JavaScript, while C and Lua printed the right answer — was finally visible.
Importing the compiler as a library:
import "xiaoqinli/compiler"
result := compiler.CompileFromFile("app.xql.json", "go", "")
if !result.Success {
log.Fatal(result.Error)
}
fmt.Println(string(result.Code))Released under the MIT License.