CX · Reference guideMigration
PlaygroundDownloadsAboutv0.18.0-pre.1

Migration

The v0.8.0 reshape (the historical big break)

v0.8.0 is the pre-release API/format-stability boundary and remains the one breaking migration in CX's history: releases since have been additive under that stability boundary, so migrating to the current release means crossing the v0.8.0 reshape and nothing else. There were no external users at the time, so the renames below were applied directly across the codebase rather than shipped as user-facing tooling. This section is the reference for the shape of the changes.

Rename: programs → code. Every internal programs reference becomes code. The brand layer ("CX is data and code") was renamed immediately on d-2026-05-22-01; the internal rename cascades through:

was is
cx_program_eval cx_code_eval
vcx/programs/ vcx/code/
spec/programs.md spec/03-approved/core/code.md
conformance/programs.txt conformance/code.cxd (today’s CX-native suite)
_cx_program_diagram (wasm) _cx_code_diagram (wasm)
--- in_cxl (fixture marker) --- in_code (fixture marker)

[?find] is retired. [?find] was always sugar for [?for] over a pattern. The migration is one of two forms:

was is
[?find [user $u] :yield $u] //user or [?for [user $u] [yield $u]]
[?find [user @active=true $u] :yield $u] //user[@active=true]
[?find [user [email $e]] :yield $e] //user/email
count([?find [user @active=true $u] :yield $u]) [$count //user[@active=true]]

CXPath is now a value kind. A path expression like //user parses to a cx.PathNode value, evaluates to a sequence, and round-trips canonically as the terse form. The retired v0.7.0 CXPath subset is replaced by XPath 3.1 alignment — see the path-expression sections of spec/03-approved/core/code.md.

Multi-arm [?match]. [?match] now supports N arms with [case …] / [when …] / [else …] clause children, scalar literal patterns, _ wildcard, and [where …] guards. The 2-arg form (single arm, raises CXER0100 on miss) is preserved unchanged.

      [# v0.7.x — nested if chain (legacy slot form)                 #]
         [?if (name($n) = 'prose') :then [p $n]
              :else [?if (name($n) = 'code') :then [pre $n]
                          :else ()]]

         [# v0.8.0 — multi-arm match (clause-child form)                #]
         [?match $n
           [case [prose $p] [p $p]]
           [case [code $c]  [pre $c]]
           [else            ()]]
    

[?modify] is new. There was no idiomatic update directive in v0.7.x — code had to rebuild via [?for]. v0.8.0 ships the eleven-action vocabulary ([set V] / [delete] / [using F] / [rename N] / [set-attr N V] / [delete-attr N] / [append V] / [prepend V] / [insert-before V] / [insert-after V] / [replace V]). See modify.

Two-layer bindings. See spec/03-approved/misc/bindings.md. Layer 1 is the canonical 16-method surface, identical across V/Python/Go/Rust. Layer 2 is opt-in host idiom packs (cxlib.idioms in Python, cxlib/idioms in Go, cxlib::idioms in Rust). v0.7.x had a single flat surface per binding; v0.8.0 codifies the separation. See bindings.

Binding scope reduced to four. v0.8.0 ships only V (native), Python, Go, Rust. The TypeScript, Java, C#, Ruby, Kotlin, and Swift bindings are moved to lang/_archived/ (preserved, not deleted). Restoration is opt-in once Layer-1 is stable; community contributions accepted but not blocked on. The make tests for archived bindings are no longer in the default make test target.

The renames above are mechanical — they do not attempt to convert hand-rolled nested [?if] chains to multi-arm [?match] (that requires semantic understanding of what each branch tests). The conversion is straightforward by hand once you know the multi-arm form (see match).

From XML

XML is the closest projection to CX — same element / attribute / body shape. Migration is one-shot: cx --from=xml --to=cx input.xml > input.cx.

  • DOCTYPE / DTD — drop. Use a CX schema (.cxs) instead. See schema.
  • Namespaces — xmlns:* declarations become CX attributes on the root element; namespace-qualified element names use the same prefix:name form. See namespaces.
  • CDATA — projects to triple-quoted strings.
  • Processing instructions — preserved as [?pi …] elements; their bodies are opaque text.
  • XPath queries — port to CXPath. The selection core is identical (see the path-expression sections of spec/03-approved/core/code.md); you drop XPath's for/let/if/function constructs in favor of the CX directives.
  • XSLT / XQuery transforms — port to CX code. There is no automated converter; the structural mapping is direct because the data model is the same.

From JSON / YAML / TOML

These are projection formats. Migration is again one-shot: cx --from=json --to=cx input.json > input.cx.

The projection rules are deterministic and reversible (see json, yaml, toml). What you gain on the CX side:

  • Comments. JSON has none; YAML and TOML have ad-hoc comments. CX has structured [; … ] comments that survive cx fmt and round-trip across projections.
  • Schemas. JSON Schema / YAML schemas are external artifacts. CX schemas are first-class CX, in the same parser, with the same toolchain.
  • Queries. JSONPath / jq / YAML query systems are format-specific. CXPath works on any projected document regardless of source format.
  • Updates. No idiomatic pure-functional update for JSON / YAML / TOML at the format level. [?modify] gives you one.
  • Identity. Canonical JSON, JCS, and jq -S exist but are not universally adopted. CX has one canonical form built into every tool.

Retired surfaces and replacements

Surfaces removed or renamed across v0.7.x → v0.8.0, with their replacement.

retired replacement source
[?find] [?for] over pattern, or CXPath //name CXPath value-kind alignment
[- comment -] [; comment ] lexer consolidation
:contains predicate hack ancestor:: axis CXPath value-kind alignment
:direct modifier hack following-sibling:: axis CXPath value-kind alignment
XPath general-comparison = value-comparison only CXPath value-kind alignment
cx_program_eval (C ABI) cx_code_eval programs → code rename
in_cxl (fixture marker) in_code programs → code rename
cxl-* playground starter ids code-* starter ids Phase 7.3
TypeScript / Java / C# / Ruby / Kotlin / Swift bindings archived under lang/_archived/ d-2026-05-22-03
Layer-1-only flat binding surface Layer 1 + Layer 2 d-2026-05-22-07

Archived bindings are not deleted. Files move to lang/_archived/<lang>/ and remain in git history. The restoration path: each archived binding can return on opt-in once Layer 1 is stable. Community contributions are welcome on the archived path; they are not blocked on by the core team.

Doc-gen pipeline consolidated. v0.8.0 ships a single pre-publish documentation generator at scripts/gen_guide/ rendering docs-src/canonical/ into the multi-page guide at docs/guide/ — eat-our-own-dog-food by running cx on every section. See docgen for the pipeline and [make guide] for the entry point.

Extend and contribute

CX is open to contribution at every layer. This subsection is the developer-facing how-to: filing issues, adding fixtures, proposing changes, shipping a binding, debugging a failure.

Filing an issue

Issues live in the project tracker. Every report should include: minimal CX reproducer (one file), cx --version output, error code (e.g. CXER0100) + message, expected vs observed behaviour, and the workaround you used if any. For parse / canon / hash bugs, a 5-line CX snippet is usually enough; for evaluator bugs include the directive shape and the input doc.

Adding a conformance fixture

The conformance suite is conformance/*.cxd files in per-feature buckets (code.cxd for directives, data_bin.cxd for binary wire, etc.) — CX-native suites of [case id=…] blocks with [in-cx] / [in-code] / [call] inputs and an [out-text] (or [out-err]) expectation. (The older === test: / --- in_cx text form is legacy.) To add one:

  • Identify the right file (code.cxd for code-language features; binding_api.cxd for Layer-1 parity).
  • Add the fixture block following existing format.
  • Run the runner (devbox run -- make test-vcx for V conformance; make test-binding-api-parity for Layer-1 cross-binding).
  • If the runner errors on your fixture, the implementation doesn't handle it — that's the bug; file it. If the run passes, your fixture is now a regression gate.

Proposing a design change

CX changes that affect the spec / ABI / surface are made by editing the spec — the spec is the single source of truth. The workflow:

  • Draft the change as an edit to the relevant spec file under spec/, with was-vs-is tables, context, the specific decisions, alternatives considered, and conformance gates.
  • Review window — submit for owner + community review. Iterate the tables until reviewers can scan the design in 2-3 minutes.
  • Ratify — the reviewed edit graduates into spec/03-approved/.
  • Implement — landing PRs cite the ratified spec change. Its conformance gates block the next release.

Keep spec edits scannable: lead with was-vs-is tables and keep the prose tight so reviewers can grasp the design quickly.

Shipping a new binding

Contributing a binding for a language not currently in the Tier-1 set (V / Python / Go / Rust):

  • Implement the 16-method Layer-1 surface (spec/03-approved/misc/bindings.md §2.1) — case-adjusted for host conventions but otherwise identical.
  • FFI extern declarations against the C ABI symbols in include/cx.h. Match the version's cap bits.
  • Call cx_init() once per process; call cx_thread_register() at every FFI chokepoint (or use a per-thread guard pattern).
  • Add a quickstart in docs-src/canonical/sections/07-bindings.cxd.
  • Wire conformance — your binding must pass conformance/binding_api.cxd byte-identically (gate 28.6). The runner pattern lives at lang/python/conformance_code.py / lang/go/cxlib/conformance_code_test.go — mirror in your language.
  • PR to lang/<your-name>/cxlib/ with the binding code plus the conformance harness.

For restoring an archived binding (TypeScript / Java / C# / Ruby / Kotlin / Swift): see lang/_archived/README.md for the recipe.

Debugging an implementation

Reproducing and triaging a CX bug:

  • Reduce the repro. Get to the smallest input that triggers the failure (one CX file, ideally < 20 lines).
  • Capture diagnostic output. cx code-tree repro.cx shows the parsed program tree with source spans; cx --ast repro.cx dumps the full AST as JSON; and cx code-diagram repro.cx --level=min|compact|full renders the program's structure at increasing detail.
  • Bisect if the bug appeared between versions — git bisect run sh -c 'cx repro.cx; test $? = 0'.
  • Check the conformance suite — does a similar fixture exist? If yes, what's different about yours?
  • File a fixture alongside the bug report (see §add-fixture) so the fix lands a regression gate.

For V-side debugging: the impl lives at vcx/code/*.v (post-rename, was vcx/programs/). Build with devbox run -- make -C vcx build; run unit tests with make test-vcx.