Skip to content

Latest commit

 

History

241 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Xiaoqinli (xql)

Go Report Card License: MIT Go Version

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:
    1. 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, and docs/COMMUNITY_WHITELIST.md (aligned exactly to 38 backends).
    2. 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.
    3. 100% Verification: go vet ./... and package tests verified 100% green; recompiled and deployed binary to C:\Users\sj929\go\bin\xql.exe.

What is AST-First?

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.


Quick start

go build -o xql .

Validate a program without generating anything:

$ ./xql validate --file examples/hello.xql.json
ok: all checks passed

Compile 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.


Capability security

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 $?
2

Capability 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.


Host functions

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).


Supported targets (38)

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 assembled
  • awk — rejects Result and struct literals
  • bash — rejects Result
  • bat — rejects struct literals and for-each; set /a is 32-bit, so int_width is excused
  • c — rejects Result
  • chrome — emits an extension bundle; the JavaScript is parsed, the browser is not consulted; rejects Result
  • cpp — rejects Result
  • crystal — rejects Result
  • d — rejects Result
  • elixir — rejects Result, and break, continue or return from inside a loop
  • fortran — rejects for-each loops
  • groovy — rejects Result
  • haskell — rejects Result, and break, continue or return from inside a loop
  • ios — SwiftPM package; swift build succeeds, nothing is run
  • js — rejects Result
  • nim — rejects Result
  • ocaml — rejects Result, and break, continue or return from inside a loop
  • pascal — rejects for-each loops
  • perl — rejects Result
  • powershell — rejects Result
  • shortcut — emits a Shortcuts workflow as JSON; structure checked, never imported; rejects Result, while, and break, continue or return inside a loop
  • tccli — emits Tencent Cloud CLI shell; no arithmetic, comparisons or structs
  • tcl — rejects Result
  • vala — 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.


Multi-file projects

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.


Integration

CLI

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.

MCP server

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.

REST API

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

docker compose up --build

The 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.


Project layout

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

Development

go build ./...                # build everything
go vet ./...                  # static analysis
go test ./...                 # full test suite
go test -tags metrics ./...   # with Prometheus metrics enabled

The 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))

License

Released under the MIT License.

About

面向 AI Agent 的 AST-First 安全转译器。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages