Skip to content
MatrixAgesPublic

About

Program language design for AI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

zxc

A programming language for AI-written business systems. Write logic in ZX (TypeScript-like), wire it together in RX (XML), and compile to a native executable.

MIT License Experimental Zig 0.17.0

CLI · RX Reference · Compiler · Feature Reference · Design

Example

A checkout flow. RX is the map — every step, and what flows between them, in one file:

<!-- checkout.rx -->
<Module>
  <Call fn="subtotal" in={$in.items} />
  <Call fn="discount" in={{subtotal: $ctx.subtotal, coupon: $in.coupon}} />
  <Call fn="shipping" in={{amount: $ctx.discount, region: $in.region}} />
  <Call fn="total" in={{amount: $ctx.discount, shipping: $ctx.shipping}} />

  <Return value={$ctx.total} />
</Module>

ZX holds the logic — each step is one typed function in its own file:

// shipping.zx
export type Input = {
  amount: u64
  region: string
}

export type Output = u64

export default function (in: Input): Output {
  return match {
    in.amount >= 10000 => 0,
    in.region == "remote" => 1500,
    _ => 600
  }
}

Call no longer accepts out. Each non-void call automatically exposes its result as $ctx.<filename>: discount.zx becomes $ctx.discount. The name comes from the target filename without its suffix; Call has no name attribute. RX attributes assemble data and simple expressions; function calls and collection logic belong in ZX.

The complete checkout example includes every referenced function. From that directory, build and run:

zxc build checkout.rx --out build/checkout

./build/checkout '{"items":[{"price":4500,"quantity":2},{"price":3000,"quantity":1}],"coupon":"SAVE10","region":"remote"}'
# {"payable":10800,"shipping":0}

To change the shipping rule, an AI only needs shipping.zx — checkout.rx already tells it what goes in and what comes out. The compiler infers types across every Call, so a mismatched edit fails at build time, not in production.

Why zxc

  • Structure you can read — RX files are the call graph and data flow. No digging through function bodies.
  • Boundaries the compiler enforces — typed Input / Output per unit, ownership checks, and a dependency graph that must be acyclic.
  • Small, local changes — change a rule in one .zx file; the RX wiring stays untouched.
  • Native output — ZX compiles to Zig; release packages embed the Zig toolchain, and -no-zig packages use the Zig named by ZXC_ZIG.

How It Works

flowchart LR
  RX[".rx modules"] --> Graph["Dependency graph<br/>acyclic check"]
  ZX[".zx functions"] --> Check["Type & ownership<br/>check"]
  Graph --> Infer["Type inference<br/>across Call"]
  Check --> Infer
  Infer --> IR["IR"] --> Zig["Zig"] --> Bin["Native executable"]
Loading

Every .rx and .zx file in the entry's closure is loaded, checked and type-inferred together, then lowered to Zig. zxc embeds the official Zig toolchain, so ordinary native builds need no separate Zig installation. Contract verification requires an external SMT solver.

Compose Modules

A module is just another unit. Call.module replaces the former Call.service attribute. Call it with module and branch on its result:

<!-- order.rx -->
<Module>
  <Call module="checkout" in={$in} />

  <Switch on={$ctx.checkout.shipping}>
    <Case value={0}>
      <Return value={{payable: $ctx.checkout.payable, free_shipping: true}} />
    </Case>
  </Switch>

  <Return value={{payable: $ctx.checkout.payable, free_shipping: false}} />
</Module>

Functions form modules, modules form larger modules, and every level follows the same rules. Cycles are rejected anywhere in the graph.

Contracts

Add requires / ensures to a ZX function and prove it with an SMT solver:

// bounded_discount.zx
export type Input = {
  price: u64
  discount: u64
}

export type Output = u64

export default function (in: Input): Output
  requires (in.discount <= in.price)
  ensures (out <= in.price)
{
  return in.price - in.discount
}
zxc verify bounded_discount.zx --solver z3 --out bounded_discount.smt2
# verified: entry paths satisfy supported safety obligations and reached contracts

The command assumes Z3 is on PATH; otherwise pass its executable path to --solver.

Remove the requires and verification fails with a concrete counterexample: the subtraction can underflow. Verification covers fixed-width integers, booleans, static objects and branches.

RX Tags

Tag Purpose
<Module> Root of an .rx file; the module is identified by its file path
<Call> Call a ZX function (fn) or another RX module (module)
<Return> Return a value from the module
<Task> Group steps; optional out={...} exposes $ctx.task.<name>
<Switch> Branch on a value
<Case> A branch of Switch matching one value
<Default> The fallback branch of Switch
<Import> Declare a module dependency without calling it
<Parallel> Run supported pure Call/Task branches concurrently
<Emit> Reserved event syntax; execution is not implemented
<Store> Define application-lifetime shared memory in *.store.rx
<Object> A state object inside Store
<Field> A typed field with an initial value inside Object
<Gateway> Expose modules over HTTP in *.gateway.rx
<Group> A route prefix inside Gateway
<Route> Map a path and method to a module

See the RX Reference for attributes and nesting rules.

ZX Language

Area What ZX offers
Units One default-exported function per file, with typed Input and Output
Types u8–u64, i32, i64, f32, f64, bool, string, objects, enums, T? optionals, T[] lists, [A, B] tuples
Bindings const only, destructuring and spread; no semicolons
Control flow if, switch, match and loop expressions
Collections Capture-free map, filter, reduce
Errors const [err, res] = try expression; finite error sets and checked success narrowing
Concurrency async expression, await task, cancel task and parallel({name: () => expression})
Ownership No pointers and no clone; the compiler decides how values move
Imports Relative paths, @/ project paths and packages; imports must be acyclic
Standard lib Encoding, crypto, paths, URLs, compression, target info, files, processes and HTTP; see the module index
Native code Explicitly declared zig: and c: interfaces
Contracts requires / ensures, proven with an SMT solver

See typed errors and ZX async and parallel for examples. A task handle can be consumed at most once by await or cancel. cancel requests cooperative cancellation and waits for the task to finish. Task execution requires an I/O-capable host.

CLI

Command Description
zxc build <entry> --out <path> Build a native executable from an .rx or .zx entry
zxc build <entry> --watch Rebuild whenever sources or dependencies change
zxc build <entry> --watch --run Restart the native app after successful rebuilds
zxc build <entry> --mode lib --out <dir> Publish a library for zxc and Zig consumers
zxc build ... --target --cpu --optimize Cross-compile and tune the output
zxc build ... --asm <file> Also write the generated assembly
zxc <file.zx> --out <file.zig> Emit Zig source only
zxc fmt <source> Format ZX, RX or package configuration
zxc verify <file.zx> --solver <z3> Prove contracts and safety obligations
zxc fpga <file.zx> --out <file.sv> Generate a SystemVerilog kernel
zxc check-rx --entry <file.rx> Check RX structure and call targets
zxc pkg install Resolve and install dependencies from pkg.yaml

See the CLI docs for every option.

License

MIT

About

Program language design for AI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages