CX · Reference guideThe code language
PlaygroundDownloadsAboutv0.18.0-pre.1

The code language

The CX code language

CX code is CX data with a leading ? on the element name. A directive like [?for] is a normal element to the parser; the evaluator recognizes the ? prefix and dispatches on the directive name. Everything else — patterns, paths, function calls, builtins, errors — is built from ordinary CX shapes.

Data is data; code is data plus a ?:

input.cx

        [user id=1 email='ada@example.com']
      

prog.cx

        [?for [in $u //user] [yield $u@email]]
      
        $ cx --data=input.cx prog.cx
'ada@example.com'
      

Fixture program-for-g6-01-code-is-data-plus-a-question-mark in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The evaluator is cx_code_eval(input, program, output_target, err_out) at the C ABI level; every binding's Layer-1 Doc.eval(code) wraps it (see bindings). The full normative spec is spec/03-approved/core/code.md. This section is the working developer guide.

Code can do five things: select (CXPath, see cxpath), iterate ([?for], see for), dispatch ([?match], see match), bind ([?let] / [?def] / [?fn]), and update ([?modify], see modify). Every other directive is one of these five lifted into a specific domain — concurrency, async, resilience, services.

Code is data. A directive is an element whose name happens to start with ?. You can pattern-match on [?for ...] exactly the way you pattern-match on [user ...]. You can transform code with [?modify]. You can hash it, project it to JSON, ship it across a wire, and evaluate it on the other end. The render rules in spec/03-approved/core/code.md §10.1 turn any well-formed program into a sequence diagram deterministically — because the program is just a tree.

Pure-functional core. Every directive returns a value; there is no assignment, no mutation, no rebinding. The concurrency primitives (channels, workers, async) exchange only immutable values, so there is no shared-mutable-state hazard by construction. See pure-updates for the lens/zipper foundation that [?modify] builds on.

Directive registry

There are currently eighty normative directives. Adding or removing any directive requires a governance change per spec/03-approved/process/governance.md. The authoritative registry lives in spec/03-approved/core/code.md §4.1; this subsection is an at-a-glance grouping of the directives this guide teaches — see the registry for the complete set.

Core constructs (11). Built into the evaluator; everything else composes from these:

directive purpose
[?match] Pattern dispatch — single-arm assertion or multi-arm first-match (see match)
[?modify] Pure-functional update — apply an action at every CXPath focus (see modify)
[?for] Iteration / comprehension over a CXPath, pattern, or sequence (see for)
[?let] Local binding scoped to a body (see let)
[?fn] Anonymous function literal — body of $bindings → expression
[?def] Named top-level definition (function or value)
[?if] Boolean branch with EBV condition (see if + ebv)
[?else] Value-or-default coalesce — recover on [err] or absence (getOrElse; see errors)
[?pipe] Pipeline — canonical form of the | operator (see pipe)
[?map] Map a closure over a sequence (sequential by default; the bareword [par] clause enables parallel evaluation and always preserves source order; [ordered] is a tombstoned no-op)
[?reduce] Fold a sequence to a single value via a binary closure starting from [init …] (sequential left-fold by default; [par] requires associativity)

Resilience (6). Wrap an inner expression with a failure-handling policy. See resilience for the parameter tables:

directive purpose
[?retry] Re-run on failure with backoff + jitter; bounded by max=
[?timeout] Bound execution time; cancel + return [on-timeout …] on expiry
[?circuit-breaker] Open after threshold-exceeded failures in a rolling window
[?fallback] Run [recover-with …] secondary if primary returns [err …] ($err bound in the recovery)
[?rate-limit] Token-bucket cap — max= calls per per= duration
[?bulkhead] Bound max-concurrent= in-flight; optional [queue …]

Services + clients (3). HTTP service definition + client construction. See services:

directive purpose
[?http-service] Declare an HTTP service with resource handlers; returns a handle
[?service-handle] Look up an existing service by name=
[?http-client] Construct an HTTP client (optionally wrapped in resilience)

Concurrency (11). Workers + channels + subscriptions + selection. See workers:

directive purpose
[?worker] Spawn a worker; concurrent with siblings in the enclosing scope
[?worker-handle] Look up an existing worker by name=
[?channel] Create a FIFO channel — [?channel name=… buffer=N]; buffer=0 is rendezvous
[?subscribe] Attach a subscription to a retained channel — resume from a position within the retained window
[?monitor] Subscribe to a worker lifecycle-event stream (spawned / cancelled / panicked / done)
[?send] Blocking send to a channel; returns [ok] or CHANNEL_CLOSED
[?try-send] Send with timeout= — SEND_TIMEOUT if buffer stays full
[?receive] Blocking receive — returns next value or CHANNEL_CLOSED
[?try-receive] Receive with timeout= — RECV_TIMEOUT if empty
[?close] Close a channel; second close raises CHANNEL_ALREADY_CLOSED
[?select] Multi-way receive — first ready case wins (uniform-random on ties)

Async (8). Futures + barriers + cancellation. See async:

directive purpose
[?async] Evaluate EXPR asynchronously; return a future immediately
[?await] Block until a future is terminal; return value or [err]
[?await-all] Join N futures — all-or-error barrier
[?await-any] Yield first to terminate; others continue running
[?await-race] Yield first to terminate; cancel the rest
[?cancel] Request cooperative cancellation on a future/worker handle
[?check-cancel] Polling cancellation point inside a CPU-bound loop
[?sleep] Cancellable sleep for DURATION

Lifecycle (2). [?stop] — initiate graceful shutdown on a service or worker handle — and [?wait-for] — block until a service or worker terminates, returning its terminal value or [err].

Retired in v0.8.0. [?find] is no longer a directive. Use [; version-literal-ok ] [?for] with a pattern generator, or CXPath //pattern. See migration for mechanical migration rules. The rationale: CXPath is the universal selector, so the [?find] shape was redundant with [?for]+CXPath.

Implementation status. Multi-arm [?match], [?modify], and the CXPath value kind in constructs other than [?for] are fully specified but not all are wired through every binding yet. The reference evaluator carries the spec-conformant implementation; the bindings follow per the parity matrix in spec/03-approved/misc/parity-matrix.md. Where a feature is spec'd-but-not-shipped this guide says so explicitly.

CXPath — the universal selector

CXPath is the single selector vocabulary across reading, iteration, dispatch, and update. It aligns with XPath 3.1 (see spec/03-approved/core/code.md §5.5) with deliberate divergences kept minimal. Path is a value kind, not parser sugar — paths compile, cache, round-trip through every projection, and visualize as themselves.

The simplest CXPath is the descendant search:

//user is every user anywhere in the document; //user[= $_@active true] every active one; //user/@email the attribute values; //section[1] the first section (1-indexed); //section[h2] the sections that have an h2 child:

input.cx

        [site
  [user active=true email='ada@example.org']
  [user active=false email='bob@example.org']
  [section [h2 'A']]
  [section [p 'B']]]
      

prog.cx

        ([$count //user],
 //user[= $_@active true]/@email,
 //user/@email,
 //section[1],
 [$count //section[h2]])
      
        $ cx --data=input.cx prog.cx
(2, [email ada@example.org], ([email ada@example.org], [email bob@example.org]), [section [h2 A]], 1)
      

Fixture program-cxpath-g6-02-descendant-search-basics in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The grammar is the XPath 3.1 selection core, lifted into CX with one comparison operator family:

      PathExpr   ::= '//' StepList | '/' StepList | StepList
         Step       ::= ( Axis '::' )? NodeTest Predicate*
         Predicate  ::= '[' PredExpr ']'
         PredExpr   ::= INT | AttrTest | StepTest | ScalarLiteral
                      | BindingOrPath | FusedPrefixForm
    

The twelve axes

CXPath supports twelve axes. The default axis is child, so name ≡ child::name. The full set:

axis direction what-it-selects
child down Direct children of context node (default axis)
descendant down Children, grandchildren, deeper — excludes self
descendant-or-self down descendant plus the context node itself
parent up The unique parent of context (or empty at root)
ancestor up Parent, grandparent, up to root — excludes self
ancestor-or-self up ancestor plus the context node itself
following-sibling lateral Later siblings with same parent
preceding-sibling lateral Earlier siblings with same parent
following document Every node after context in document order (excluding descendants)
preceding document Every node before context in document order (excluding ancestors)
self self Context node itself — useful for predicate filters
attribute attr Attributes of context — shorthand is @name

The common idioms, one per axis — all descendants of a section, the section enclosing a code block, the users after the first, the table around a row, the sibling just before a code block, an attribute filter, and the attribute axis spelled in full (//user/attribute::email is //user/@email):

input.cx

          [doc
  [section [h2 'T'] [code 'x'] [p 'q']]
  [users
    [user role='admin' email='a@example.org']
    [user role='user'  email='b@example.org']]
  [table [tr [td 1]]]]
        

prog.cx

          ([$count //section/descendant::*],
 [$name [$first //code/ancestor::section]],
 //user[1]/following-sibling::user/@email,
 [$name [$first //tr/parent::table]],
 [$name [$first //code/preceding-sibling::*[1]]],
 //user[= $_@role 'admin']/@email,
 //user/attribute::email)
        
          $ cx --data=input.cx prog.cx
(3, section, [email b@example.org], table, h2, [email a@example.org], ([email a@example.org], [email b@example.org]))
        

Fixture program-cxpath-g6-03-axis-idioms in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The same step vocabulary applies from a binding (grammar [135a]): $u/name (child), $u//item (descendant), $u@email (attribute), $u/.. (parent), and $m.key (map-key access on a map-valued binding). See bindings.

Predicates — positional, attribute, function, composite

A predicate is [...] after a step. The predicate body is prefix CX code over the context node $_ — plus a small set of atomic shorthands. The admitted shapes:

  • Positional — INT selects the Nth match (1-indexed). //section[1] is the first section. Negative positions are NOT supported; [$_last] selects the last, [= $_position N] the Nth.
  • Attribute test — [@name] (attribute present) and [@!name] (attribute absent). Value comparisons are prefix forms: [= $_@name V], [!= …], [< …] etc.
  • Step-existence — a bare child name: [h2] keeps elements that have an h2 child.
  • Prefix expression — any prefix CX form over $_: comparisons ([= $_@id 42]), boolean combinators ([and …] / [or …] / [not …]), and function calls ([$strings:contains $_@email '@example.com']).

input.cx

          [site
  [user id=42 active=true email='ada@example.com' age=30 country='US']
  [user id=7 email='bob@else.org' age=17 country='US']
  [section [h2 'A'] [p 'x']]
  [section [p 'y']]]
        

prog.cx

          [?lib 'cx-stdlib/strings']
(//user[1]/@id,
 //user[= $_@id 42]/@email,
 [$count //user[@active]],
 //user[$strings:contains $_@email '@example.com']/@id,
 //user[and [>= $_@age 18] [= $_@country 'US']]/@id,
 [$count //section[h2]],
 [$count //section[and [$count $_/h2] [$count $_/p]]])
        
          $ cx --data=input.cx prog.cx
([id 42], [email ada@example.com], 1, [id 42], [id 42], 1, 1)
        

Fixture program-cxpath-g6-04-predicate-shapes in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Predicates chain: //user[= $_@active true][1] is the first active user — the second predicate filters the result of the first. The order matters.

Comparison — one operator family

One operator syntax: = != < <= > >=. No keyword synonyms (eq / ne / lt / ...) — they are not in the grammar. Semantics are value-comparison: both sides must be single values; a multi-valued operand raises CXER0103; no type coercion.

This is the deliberate divergence from XPath 3.1's general-comparison default. In XPath, //x = 1 is true if any x equals 1 — a common gotcha when the path silently returns multiple nodes. In CX, the path must return a single value, or the comparison raises. To express "any equals", filter the sequence itself: [$exists //user/tag[= [$text $_] 'admin']].

input.cx

          [site
  [user age=42 email='ada@example.com' [tag 'admin'] [tag 'dev']]
  [section [p 1] [p 2] [p 3] [p 4]]]
        

prog.cx

          (//user[= $_@age 42]/@email,
 [$count //section[> [$count $_/p] 3]],
 //user[= $_@email 'ada@example.com']/@age,
 [$exists //user/tag[= [$text $_] 'admin']])
        
          $ cx --data=input.cx prog.cx
([email ada@example.com], 1, [age 42], true)
        

Fixture program-cxpath-g6-05-value-comparison in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Paths on bindings

A CXPath rooted at a binding navigates from that value instead of from the document root:

$u/name is the direct child, $u/* all children, $u//item the descendants, $u@active the attribute shorthand and $u/@active the full axis form:

prog.cx

          [?let [= $u [user active=true region='EU'
             [name 'Ada'] [item 1] [item 2]]]
  ($u/name, $u/*, $u//item, $u@active, $u/@active)]
        
          $ cx prog.cx
(Ada, ([name Ada], [item 1], [item 2]), ([item 1], [item 2]), true, true)
        

Fixture program-cxpath-g6-06-paths-on-bindings in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The path operators that work on bindings are the same operators that work on the implicit document — one vocabulary. The leading // form is anchored at the document root, regardless of context; $u//x is anchored at $u. Use //x to escape upward when you already have a binding. Parent and map-key steps ride the same surface (grammar [135a]): $u/.. steps to the parent, and $m.key reads a key from a map-valued binding.

Where CXPath values are accepted

CXPath is a value kind. Every construct that takes "a selector" accepts a CXPath value:

input.cx

          [site [admin 'root'] [meta config='prod'] [user active=true status='new'] [prose 'p']]
        

prog.cx

          [?let [= $n [$first //prose]]
  ([?for [in $u //user[= $_@active true]] [yield [seen $u@status]]],
   [?if //admin [then [admin-panel]] [else ()]],
   [?match $n [case //prose [p [$text $n]]] [else ()]],
   [?let [= $cfg //meta/@config] [$string $cfg]],
   [?modify $doc //user/@status [set 'verified']],
   [$count //user[= $_@active true]])]
        
          $ cx --data=input.cx prog.cx
([seen new], [admin-panel], [p p], prod, [site [admin root] [meta config=prod] [user active=true status=verified] [prose p]], 1)
        

Fixture program-cxpath-g6-07-path-in-every-position in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

A path appears as a [?for] generator, as an [?if] condition, as a [?match] arm, as a [?let] initializer, as a [?modify] focus, and as a builtin argument — the six positions above, in that order. This is the unification we ratify: no separate "path syntax" per construct.

Status note. CXPath value-kind at non-[?for] positions is production-ready in V; full Tier-2 binding parity is tracked in spec/03-approved/misc/parity-matrix.md.

Deliberate divergences from XPath 3.1

CXPath is a working subset of XPath 3.1 + deliberate changes. The normative treatment is spec/03-approved/core/code.md §5.5; the headline differences:

  • One comparison family (= != < <= > >=) — XPath's split into general (=) and value (eq) is gone; CX uses value-comparison semantics exclusively. Rationale: the general-comparison gotcha is XPath's most-reported source of subtle bugs.
  • Path is a value kind — XPath paths are syntactic fragments inside XPath expressions only. In CX they are first-class values.
  • 1-indexed positions — adopted from XPath verbatim (//x[1] is the first), but CX explicitly rejects [0] (raises CXER0100); XPath silently yields the empty sequence.
  • No general-purpose for / let inside paths — CX moves binding into the [?for] / [?let] directive surface; predicates take expressions but not binding clauses. Use the surrounding directive.
  • No paren-calls anywhere — XPath's count(//user) spelling does not exist; every call is the head-dispatch form [$count //user], in predicates too ([> [$count $_/*] 0]). position() / last() are the reserved predicate bindings $_position / $_last.

The normative CXPath treatment — semantics, the desugar table, and predicate evaluation — is spec/03-approved/core/code.md §5.5; the XPath 3.1 parity corpus is conformance/xpath_31_parity.cxd.

Patterns and matching

A pattern is a literal CX subtree extended with binding holes ($name), wildcards (*, **, _), type guards (:TypeName), scalar literals, and CXPath values. Patterns appear in [?for]'s first generator, in [?match] arms, and in destructuring [?let]. The full pattern grammar is in spec/03-approved/core/code.md §5.

Element-shape patterns

An element-shape pattern matches an element by name, attributes, and (recursively) body content. The pattern uses ordinary CX syntax with bind-holes where you want to capture values:

[user email=$e] binds the email attribute; [user @active=true $u] filters on an attribute and binds the whole user; [user @id=42 [name $n]] binds a child's body; [post author=$a body=$b] binds two attributes:

input.cx

          [site
  [user id=42 active=true email='ada@example.com' [name 'Ada']]
  [post author='ada' body='hello']]
        

prog.cx

          ([?for [user email=$e] [yield $e]],
 [?for [user @active=true $u] [yield $u@id]],
 [?for [user @id=42 [name $n]] [yield $n]],
 [?for [post author=$a body=$b] [yield [pair $a $b]]])
        
          $ cx --data=input.cx prog.cx
('ada@example.com', 42, Ada, [pair 'ada' 'hello'])
        

Fixture program-for-g6-08-element-shape-patterns in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Binding semantics depend on context. A pattern [NAME $x] without attribute predicates auto-unwraps the body — text body binds the string, scalar body binds the scalar, empty body binds the element itself, structured body binds the element. A pattern [NAME @attr… $x] with attribute predicates always binds $x to the matched element. The rationale is in spec/03-approved/core/code.md §5.2 rule 5: when you name attributes you are filtering "what kind of element", so the bind value is the element; when you don't, the bind value is the inner content.

Scalar literal patterns

Scalar literals are valid patterns. They match by value-equality:

prog.cx

          [?for [in $status (200, 404, 500, 'admin', true, null, 7)]
  [yield [?match $status
    [case 200     :ok]
    [case 404     :not-found]
    [case 500     :server-error]
    [case 'admin' :elevated]
    [case true    :enabled]
    [case null    :missing]
    [else         :unknown]]]]
        
          $ cx prog.cx
:ok
:not-found
:server-error
:elevated
:enabled
:missing
:unknown
        

Fixture program-match-multi-g6-09-scalar-literal-arms in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Integers, floats, strings, bools, dates, datetimes, and null are all matchable as scalar literals. Float equality follows IEEE-754 — nan is never equal to nan. Test for NaN explicitly with [?lib 'cx-stdlib/math'] and [$math:is-nan $x].

CXPath patterns

A CXPath value used as a pattern matches if the candidate value is in the path's result sequence:

input.cx

          [doc [heading 'Title'] [prose 'Body.']]
        

prog.cx

          [?let [= $node [$first //heading]]
  [?match $node
    [case //heading [h2 [$text $node]]]
    [case //prose   [p  [$text $node]]]
    [else           $node]]]
        
          $ cx --data=input.cx prog.cx
[h2 Title]
        

Fixture program-match-multi-g6-10-cxpath-arms in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

In a [?for] body, the same CXPath as the generator selects directly:

input.cx

          [users [user email='ada@example.org' active=true]
       [user email='bob@example.org' active=false]]
        

prog.cx

          [?for [in $u //user[= $_@active true]] [yield $u@email]]
        
          $ cx --data=input.cx prog.cx
'ada@example.org'
        

Fixture program-for-g6-11-cxpath-generator-selects in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

CXPath patterns and element-shape patterns compose in the same arm list — [?match] can dispatch on a mix of both, with first-match-wins ordering (see match).

Wildcards — *, **, _

Three wildcard forms:

  • * — any single element. [* $x] matches any element and binds it. [section * $body] matches any section and binds the body wildcard slot.
  • ** — recursive descent. [**] matches any element at this position or deeper. Wrapping an inner pattern — [** [user $u]] — matches a user anywhere in the body and binds it.
  • _ — anything, no bind. _ matches any value (including non-elements) without binding it. The idiomatic catch-all in [?match].

prog.cx

          [?let [= $node [section [h2 'T'] [code 'x = 1']]]
  [?match $node
    [case [section * $body] [matched-section]]
    [case [** [code $c]]    [highlighted $c]]
    [case _                 $node]]]
        
          $ cx prog.cx
[matched-section]
        

Fixture program-match-multi-g6-12-wildcards in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Named bindings — $name

$name (lowercase, leading $) is a binding hole. It appears in attribute-value position ([user email=$e]), in body position ([user $u]), and in a destructuring [?for] generator ([?for [in [user $u] /users/user] …]).

A bind hole always succeeds at its position; the bind value depends on what the surrounding pattern dictates (see pattern-element-shape). Naming collisions inside one arm raise CXER0100 (PARSE_ERROR) — [user @id=$x @name=$x] would require the values be equal, which the current spec does not support; use a single bind and check with a [when …] clause.

Type-guard heads

A type guard :TypeName at pattern-head position narrows by schema-typed shape:

prog.cx

          [?let [= $cfg [server-config host='a.example.org']]
  [?match $cfg
    [case [:ServerConfig $c] [server $c]]
    [case [:ClientConfig $c] [client $c]]
    [else [result status=err reason='unknown config']]]]
        
          $ cx prog.cx
error: cx-err:CXER0100: [:ServerConfig] names schema type `ServerConfig` but NO schema is in scope — declare one with `[?cx schema=PATH.cxs]`, carry it inline with `[?cx schema-inline …]`, or pin it in cx.lock (schema.md §13; code.md §5.4)
        

Fixture program-match-multi-g6-13-type-guard-without-schema in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Type-guard resolution is by the document's schema binding (see schema in the data section); with no schema in scope a type guard is refused at program load, as the fixture above shows (CXER0100, naming the type and the three ways to bring a schema into scope). Pair with schema binding ([?cx schema='…']) for type-driven dispatch.

Bindings and paths

A binding is a name introduced by [?let], [?for], [?fn], or [?match] pattern. Bindings are referenced via $name. They are immutable — there is no rebinding (per CX's pure-functional discipline).

input.cx

        [users [user role='admin' email='root@example.com']
       [user role='user'  email='ada@example.com']]
      

prog.cx

        [?let [= $admins //user[= $_@role 'admin']]
  [?for [in $a $admins] [yield $a@email]]]
      
        $ cx --data=input.cx prog.cx
'root@example.com'
      

Fixture program-let-g6-14-binding-then-iterate in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

A binding's value is whatever expression produced it: a sequence, a scalar, an element, an array, a map, an error, a future, a CXPath, a function literal. Every value is a sequence (per the sequence-flat principle); single values are sequences of one.

Navigation from a binding uses CXPath syntax (see cxpath-bindings). The path operators that work on bindings are the same operators that work on the implicit document — one vocabulary. $u/email is a child step; $u@email is the attribute shorthand; $u/@email is the full attribute axis form.

Scope. A binding is in scope inside the directive body that introduced it. [?let [= $x E] BODY] puts $x in scope for BODY only; outside the let it does not exist. [?for [in $u SEQ] [yield E]] puts $u in scope for E (one iteration at a time, per item).

Shadowing. A nested binding with the same name shadows the outer for the inner scope; this is the standard lexical rule and matches every host language. CX does not warn on shadowing — readability is the author's responsibility.

Core constructs

The six core constructs are how every CX program is built. Each is documented in the following sub-sections.

[?for] — iteration

[?for] is the iteration / comprehension primitive. The expression in the [in …] clause is either a CXPath (yields a sequence to iterate), a pattern (matches the generator — the [?find] replacement), or any expression that evaluates to a sequence.

input.cx

          [site
  [user id=1 email='ada@example.com'   age=36 active=true]
  [user id=2 email='grace@example.com' age=17 active=false]
  [post author=1 [title 'Notes']]
  [request 'r1'] [request 'r2']
  [logs [row 'a'] [row 'b']]]
        

prog.cx

          ([; the path generator ]
 ([?for [in $u //user] [yield $u@email]]),
 [; the pattern generator ]
 [?for [user @active=true $u] [yield $u@email]],
 [; a where filter ]
 [?for [in $u //user] [where [>= $u@age 18]] [yield $u@id]],
 [; two generators, in declared order ]
 [?for [in $u //user]
       [in $p //post[= $_@author $u@id]]
       [yield [pair user=$u@id post=[$text $p/title]]]],
 [; parallel evaluation, results in source order ]
 ([?for [in $r //request] [par] [yield [got [$text $r]]]]),
 [; lazy evaluation over a large input ]
 ([?for [in $row //logs/row] [lazy] [yield [$text $row]]]))
        
          $ cx --data=input.cx prog.cx
(('ada@example.com', 'grace@example.com'), 'ada@example.com', 1, [pair user=1 post=Notes], ([got r1], [got r2]), (a, b))
        

Fixture program-for-g6-15-six-generator-forms in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[par] and [lazy] are evaluation-modifier clauses — same surface, different scheduler. [par] distributes iterations across a bounded worker pool (min(4, ncpu) workers by default; [par N] caps it at N) and always reassembles results in source order — the retired [ordered] clause survives as a tombstoned no-op. [lazy] couples the generator to a streaming input source so that the iteration runs in bounded memory regardless of input size. Full semantics in spec/03-approved/core/code.md §7.

Multiple [where …] clauses AND together; multiple [yield …] clauses concatenate (per the sequence-flat principle — yields never produce a list-of-lists).

[?match] — dispatch (single + multi-arm)

[?match] is pattern dispatch. CX supports two forms: 2-arg destructure (single arm, asserts the value matches or raises) and multi-arm (first-match- wins across N arms). The multi-arm form is a recent addition.

Single-arm form — pattern-bind a value and assert the shape:

prog.cx

          [?let [= $result [ok 42]]
  [?match $result [ok $v] [yield [rendered $v]]]]
        
          $ cx prog.cx
[rendered 42]
        

Fixture program-match-g6-16-single-arm-destructure in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Multi-arm form — first-match-wins across [case …] / [when …] / [else …] clauses. Arms may mix element-shape patterns, scalar literals, CXPath paths, wildcards, and predicate-only [when …] clauses:

prog.cx

          ([?let [= $node [code 'x = 1']]
   [?match $node
     [case [prose $p]   [p $p]]
     [case [code $c]    [pre $c]]
     [case 'separator'  [hr]]
     [else              ()]]],
 [; predicate-only arms, no scrutinee ]
 [?let [= $x 120] [= $y 10]
   [?match
     [when [> $x 100]   :big]
     [when [> $y 50]    :medium]
     [else              :small]]],
 [; a guard on a case arm ]
 [?let [= $req [http-get '/admin']]
   [?match $req
     [case [http-get $u] [where [= $u '/admin']] [route $u]]
     [case [http-get $u]                         [status 403]]
     [else                                       [status 400]]]])
        
          $ cx prog.cx
([pre x = 1], :big, [route /admin])
        

Fixture program-match-multi-g6-17-three-dispatch-shapes in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Arm semantics:

  • [case PAT R] — match against pattern PAT. Arms evaluate top-to-bottom; the first one whose pattern matches "wins". Later arms are not tried, even if they would also have matched.
  • [when PRED R] — match iff predicate truthy. No pattern; the predicate is evaluated with the scrutinee in scope as $_ (and any earlier [case …] bindings still in scope only if the same arm).
  • [else R] — catch-all, always last. Equivalent to [case _ R]. Multiple [else …] clauses raise CXER0100.
  • [case PAT [where PRED] R] — guard on a case arm. The pattern must match AND the predicate must be truthy.
  • No implicit EBV in [case …]. [case …] tests pattern equality / structural match; it does not apply EBV. [when …] uses EBV.

No-match behavior differs by form. The 2-arg form raises CXER0100; the multi-arm form yields () (the empty sequence) if no arm matches and no [else …] is present. An unreachable arm (covered by an earlier arm with no guard) raises a warning at parse time but does not fail compilation.

Status. Multi-arm [?match] is fully spec'd. V's evaluator carries the production-ready implementation; Tier-2 binding parity is per spec/03-approved/misc/parity-matrix.md.

[?if] — boolean branch (with EBV)

[?if cond [then E1] [else E2]] is the explicit boolean branch. The condition is evaluated with the EBV (Effective Boolean Value) rule — see ebv below.

input.cx

          [site [admin 'root'] [config debug=false]]
        

prog.cx

          ([?if //admin [then [admin-panel]] [else [user-panel]]],
 [?let [= $age 36]
   [?if [>= $age 18] [then :adult] [else :minor]]],
 [?let [= $cfg [$first //config]]
   [?if $cfg@debug [then [debug-trace]] [else ()]]])
        
          $ cx --data=input.cx prog.cx
([admin-panel], :adult)
        

Fixture program-if-g6-18-ebv-conditions in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

For multi-way conditionals prefer [?match] multi-arm (see match). [?if] stays separate from [?match] because they have different contracts — [?if] uses EBV (truthy/falsy with container rules), [?match] [case …] does not (it tests strictly by structural equality / pattern match).

The [else …] clause is optional. When omitted, an untrue condition yields () (the empty sequence). Many CX idioms exploit this — [?if $n@hidden [then ()] [else [$render $n]]] reads as "render unless hidden".

EBV — the Effective Boolean Value rule

The EBV rule, applied in order (per spec/03-approved/core/cxdm.md §4.6):

  • Empty sequence () → false.
  • Single Scalar of kind bool → that bool's value.
  • Single Scalar of kind string → length > 0.
  • Single Scalar of kind int / float → value != 0 (and not nan for float).
  • Single Scalar of kind null → false.
  • Single Scalar of kind date / datetime / bytes → true (existence is truthy).
  • Single Node (element) → true.
  • Single Array (v1.1 / v0.8.0) → true iff [; version-literal-ok ] non-empty (length > 0).
  • Single Map (v1.1) → true iff non-empty.
  • Sequence of length > 1 → true.

This is the rule that makes [?if //service …] mean "if any service exists", [?if @debug …] mean "if the debug attribute exists and is truthy", and [?if @items …] mean "if the items array is non-empty" when @items is an Array-typed attribute.

The container rules (Array/Map empty-is-falsy) follow the convention JSON-template engines and YAML/TOML processors share. CX deliberately diverges from XPath 3.1 here, which raises a type error on EBV(array); CX's pragmatic rule reads more naturally in template position.

[?let] — binding

[?let] introduces a local binding. The bound name is in scope inside the body that follows.

input.cx

          [meta host='api.example.com' port='8443']
        

prog.cx

          [?let [= $meta [$first //meta]]
      [= $base [$string $meta@host]]
      [= $port [$string $meta@port]]
      [= $url  [$concat 'https://' $base ':' $port]]
  [endpoint target=$url]]
        
          $ cx --data=input.cx prog.cx
[endpoint target='https://api.example.com:8443']
        

Fixture program-let-g6-19-sequential-bindings in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[?let] is the only way to give an expression a name currently — there is no def inside a function body, no inline where (that is a [?for] filter). To name a value that is used multiple times, [?let] is idiomatic.

[?modify] — pure-functional updates

[?modify] is the update directive. It takes a document (or sub-tree), a CXPath focus, and one or more action slots; it returns a new document with the action applied at every focus match. The original is unchanged (pure-functional).

The eleven action slots are:

action description
[set V] Replace matched value, element body, or attribute value with V
[delete] Remove matched node from its parent body (or attribute from element)
[using F] Apply function F to each match; F receives the match, returns the replacement
[rename N] Rename matched element (preserves attrs + body)
[set-attr N V] Add new attribute N=V, or overwrite if N already present
[delete-attr N] Remove attribute N from matched element
[append V] Add child V at end of matched body
[prepend V] Add child V at start of matched body
[insert-before V] Insert V as a sibling immediately before each match
[insert-after V] Insert V as a sibling immediately after each match
[replace V] Replace entire matched node (including its surrounding placement) with V

Every action on one document, in the table's order — then a three-step pipeline (see pipe); each result is a new document, and the twelve results below share one unchanged original:

input.cx

          [doc
  [user id=1 name='Ada' active=true]
  [user id=2 name='Bob' active=false banned=true]
  [section id='intro' [para 'Hi.']]
  [list [item 'a']]
  [h2 'T'] [code 'x'] [price 10.0] [para 'p'] [deprecated 'old']]
        

prog.cx

          ([?modify $doc //user[= $_@id 1]/@name [set 'Alice']],
 [?modify $doc //user[= $_@active false] [delete]],
 [?modify $doc //price [using [?fn ($p) [* $p 1.1]]]],
 [?modify $doc //para [rename p]],
 [?modify $doc //user [set-attr verified true]],
 [?modify $doc //user [delete-attr banned]],
 [?modify $doc //section[= $_@id 'intro'] [append [para 'Added.']]],
 [?modify $doc //list [prepend [item 'header item']]],
 [?modify $doc //h2 [insert-before [hr]]],
 [?modify $doc //code [insert-after [aside 'note']]],
 [?modify $doc //deprecated [replace ()]],
 [?pipe $doc
   [?modify //user/@status [set 'verified']]
   [?modify //user[= $_@banned true] [delete]]
   [?modify //user [set-attr last-audit '2026-05-22']]])
        
          $ cx --data=input.cx prog.cx
([doc [user id=1 name=Alice active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] 11.00 [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [p Hi.]] [list [item a]] [h2 T] [code x] [price 10.0] [p p] [deprecated old]], [doc [user id=1 name=Ada active=true verified=true] [user id=2 name=Bob active=false banned=true verified=true] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [para Hi.] [para Added.]] [list [item a]] [h2 T] [code x] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [para Hi.]] [list [item header item] [item a]] [h2 T] [code x] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [para Hi.]] [list [item a]] [hr] [h2 T] [code x] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] [aside note] [price 10.0] [para p] [deprecated old]], [doc [user id=1 name=Ada active=true] [user id=2 name=Bob active=false banned=true] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] [price 10.0] [para p] ()], [doc [user id=1 name=Ada active=true status=verified last-audit='2026-05-22'] [section id=intro [para Hi.]] [list [item a]] [h2 T] [code x] [price 10.0] [para p] [deprecated old]])
        

Fixture program-modify-g6-20-eleven-actions in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Pure-functional — structural sharing. [?modify] does not copy the whole tree. Only the spine from root to each matched node is copied; unchanged branches are shared with the original (immutable persistent data structure). The new allocation is O(depth × matches) — the spine, not the document size — and the original $doc value is still valid for read or hash.

Multi-match. The focus path selects a sequence; the action applies to every node in that sequence. Zero matches returns the original unchanged (not an error — the no-match-no-op invariant lets pipelines compose safely). All-matches-at-once means there is no ordering hazard between matches.

Identity invariant. [?modify $doc //x [set v]] where //x matches no nodes returns $doc unchanged — same canonical bytes, same SHA-256. [?modify $doc //x [delete]] where every //x is removed returns a value where [$count //x] is 0. Round-trip through ast-bin / data-bin / JSON preserves the post-modify shape exactly.

Status. [?modify] is fully spec'd. The V evaluator carries the production-ready implementation. Tier-2 binding parity is tracked in spec/03-approved/misc/parity-matrix.md.

Pipelines and composition

The [?pipe] directive threads the output of one expression into the input of the next. Pipelines compose [?modify], [?for], builtins, and user-defined functions into readable top-to-bottom data flow.

input.cx

          [shop [user active=true age=30]
      [user active=true age=20]
      [user active=false age=40]]
        

prog.cx

          [?pipe //user[= $_@active true]
  [?fn ($us) [?for [in $u $us] [yield $u@age]]]
  [$reverse]
  [$nth _ 1]]
        
          $ cx --data=input.cx prog.cx
20
        

Fixture program-pipe-g6-21-stages-are-transforms in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Stages are bare transforms. [?pipe SRC STAGE …] evaluates SRC, then supplies the value to each STAGE left-to-right. A stage is a transform — a bare name, a $ref, or a [$call …] — never wrapped in a keyword (there is no [through] clause). [?pipe 5 add1 add1] → 7.

Stage invocation (§8.9.1). How the value is supplied depends on the stage shape: a hole-form stage carries a single partial-application hole _ and the value fills it ([$get _ '/users/42'] → get(client, '/users/42')); a no-hole stage receives the value appended as its final positional argument (count ≡ [$count _]). A pipe threads exactly one value, so a stage may carry at most one _ (two or more → cx-err:CXER0100). A non-callable stage (a literal, map, or data element) is cx-err:CXER0100 — it never silently passes the value through.

Fan-out behavior. When SRC evaluates to a sequence, a stage sees the whole sequence as a single value (the sequence-flat principle — no implicit per-item dispatch). For a per-item transform, thread a [?fn] lambda that runs the comprehension:

input.cx

          [users [user name=ada] [user name=bob]]
        

prog.cx

          [?pipe //user
  [?fn ($us) [?for [in $u $us] [yield [decorated name=$u@name]]]]
  [$count]]
        
          $ cx --data=input.cx prog.cx
2
        

Fixture program-pipe-g6-22-fan-out-through-a-lambda in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[tap f] — observe without diverting (§8.9.2). A [tap f] stage applies f to the current value for its side effect; the value passes through unchanged and f's return is discarded. Inside a stage, _ names the current value, so [tap [$log:info 'seen' fields={v: _}]] logs and flows on. A tap propagates an [err] only when its code is CXER0001 (panic) or CXER0260 (cancellation); every other error is discarded for the data flow (the error-hook still observes it).

Railway short-circuit (§8.9.3). The pipe is a single-channel failure railway: the first stage to yield [err …] skips every remaining stage — taps included — and that [err] is the result. An empty result is not a short-circuit; it flows to the next stage. There is no infix | and no [through] wrapper — both retired.

Built-in functions

Built-ins are the standard library at the expression layer. They are pure-functional, total over their declared input types, and stable across bindings. Full normative table in spec/03-approved/core/code.md §6.5; the subsections below organize the ~47 names by category.

Invocation. Every call uses the bracket head-dispatch form: [$name args…] for the built-in surface, or [$module:name args…] for stdlib-module calls. There is no paren-call form — name(args) is a parse error everywhere. The whitespace-separated element form [name a b] without a leading $ is always element construction; write [$count //user] (call) versus [count //user] (data element). The name(args) spelling in the tables below is arity notation (mirroring the spec), not invocation syntax.

Sequence built-ins

Operate on a Sequence. Argument may be a CX program sequence, the implicit document, or a single non-sequence value (treated as a 1-element sequence).

builtin arity semantics
count(seq) / length(seq) 1 Integer count; scalar input yields 1
empty(seq) 1 Boolean — true iff count(seq) == 0
exists(seq) 1 Boolean — true iff count(seq) > 0; opposite of empty
present(value) 1 Boolean — true iff value is present; false only for absence (the empty sequence / empty node-set). The canonical "did this step match anything" test — unlike exists, it never looks inside: a childless element, an empty string, and null are all present
first(seq) 1 First item; scalar passes through; XQuery fn:head parity
last(seq) 1 Last item; scalar passes through
head(seq) 1 Synonym for first
tail(seq) 1 All items except first; scalar yields ()
reverse(seq) 1 Items in reverse order; scalar passes through
distinct(seq) 1 Items with structural duplicates removed; first-occurrence order
nth(seq, n) 2 1-indexed item access; out-of-range raises CXER0100
position(seq, item) 2 1-based index of first structural match; zero if absent
range(lo, hi, step?) 2-3 Arithmetic progression, inclusive bound; [$range lo *] is a lazy open iterator
iterate(f, seed) 2 Lazy infinite iterator: seed, f(seed), f(f(seed)), …
unfold(f, seed) 2 Anamorphism: f(state) yields () to stop or [value, next-state] to emit
identity(x) 1 Returns x unchanged — for pipe stages and shape testing

Set combinators are reserved operator heads, not [$…] builtins: [union A B], [intersect A B], [except A B]. Slicing / windowing / ordering / grouping are directives and comprehension clauses: [?take], [?drop], [?filter], [?flatten], [order-by …], [group-by …] (see for).

input.cx

          [site [user active=true] [user active=false]
      [tag 'a'] [tag 'b'] [tag 'a']]
        

prog.cx

          ([$count //user[= $_@active true]],
 [$distinct [?for [in $t //tag] [yield [$text $t]]]],
 [?take 3 [$range 1 100]],
 [union (1, 2) (2, 3)])
        
          $ cx --data=input.cx prog.cx
(1, (a, b), (1, 2, 3), (1, 2, 3))
        

Fixture program-builtin-g6-23-sequence-builtins in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

String built-ins

Operate on a String scalar. Non-string scalars are stringified by the canonical scalar printer; element arguments raise CXER0100 unless documented otherwise.

builtin arity semantics
upper(s) 1 Unicode upper-cased copy of s
lower(s) 1 Unicode lower-cased copy of s
contains(s, sub) 2 Boolean substring test; empty sub yields true
starts-with(s, prefix) 2 Boolean prefix test; empty prefix yields true
ends-with(s, suffix) 2 Boolean suffix test; empty suffix yields true
substring(s, start, len?) 2-3 1-indexed substring; positions clamp; never raises
string-length(s) 1 Integer count of Unicode codepoints (NOT bytes)
normalize-space(s) 1 Strip leading/trailing + collapse internal whitespace
concat(s1, s2, …) ≥1 String concatenation; non-string scalars stringified
text(elem) 1 Body text of elem as string; concatenates text children

The wider string toolkit — trim, split/join, replace, case-folding variants, padding — is the cx-stdlib/strings module ([?lib 'cx-stdlib/strings'], then [$strings:trim …], [$strings:split …], [$strings:join …], [$strings:replace …]). Regular expressions are cx-stdlib/re. See the Standard-library pages.

prog.cx

          [?lib 'cx-stdlib/strings']
([$upper 'hello world'],
 [$contains 'ada@example.com' '@example.com'],
 [$strings:join ('a', 'b', 'c') ', '],
 [$strings:split 'a,b,c' ','],
 [$strings:replace 'foofoo' 'foo' 'bar'])
        
          $ cx prog.cx
('HELLO WORLD', true, 'a, b, c', (a, b, c), barbar)
        

Fixture program-builtin-g6-24-string-builtins in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Numeric built-ins

Operate on numeric scalars. sum/min/max/avg operate on a sequence and skip non-numeric items; floor/ceiling/round/abs operate on a single value. A numeric operand is an int, a float, or one of the exact kinds decimal / bigint — and the exact kinds are their own family, not floats in disguise: a decimal or bigint operand folds exactly (int embeds losslessly), while mixing one with a float raises CXER0100 — [cast] is the only bridge. That is the arithmetic heads' rule; the aggregates are a different operand shape, never a different numeric semantics.

builtin arity semantics
sum(seq) 1 Sum of numeric scalars; empty yields int 0; int/float mix → float; decimal/bigint fold exactly
min(seq) 1 Minimum numeric, in its own kind; empty yields int 0
max(seq) 1 Maximum numeric, in its own kind; empty yields int 0
avg(seq) 1 Arithmetic mean — float, or exact over decimal/bigint; empty yields float 0.0
abs(x) 1 Absolute value; preserves int/float kind
floor(x) 1 Largest integer ≤ x; int passes through
ceiling(x) 1 Smallest integer ≥ x; int passes through
round(x) 1 Half-away-from-zero rounding (XPath fn:round)
mod(a, b) 2 Remainder; sign follows dividend; b=0 raises CXER0101
div(a, b) 2 True division if either is float; integer division if both int; exact over decimal/bigint, as /
idiv(a, b) 2 Integer (truncating) division regardless of operand kinds; exact quotient over decimal/bigint

Exact division either terminates or says so: a decimal/bigint quotient that does not terminate raises CXER3002 and the refusal names $math:div-decimal, the verb carrying an explicit precision + mode. There is no default rounding context. /, div and an exact avg are one implementation, so they never disagree. idiv is exempt — an integral quotient always terminates.

Type coercion is the single explicit cast operator — [cast '42' :int], [cast 3 :float], [cast 42 :string] — there is no to-int / to-float family; a failed cast is CXER0290 (E_CAST_FAILED). CX floats are IEEE-754: NaN and ±Inf exist and follow IEEE-754 semantics — test for NaN with [$math:is-nan] from cx-stdlib/math. CXER0101 (E_ARITH_DIVIDE_ZERO) is division or modulo by zero, specifically. Exponentiation is [$math:pow] in cx-stdlib/math.

input.cx

          [orders [order amount=10] [order amount=32]]
        

prog.cx

          ([$sum [?for [in $o //order] [yield $o@amount]]],
 [$round 3.7],
 [$mod 17 5],
 [cast '42' :int])
        
          $ cx --data=input.cx prog.cx
(42, 4, 2, 42)
        

Fixture program-builtin-g6-25-numeric-builtins in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Logical built-ins

Boolean combinators and EBV-aware tests. and / or short-circuit; not is total over any EBV-typed argument.

builtin arity semantics
not(v) 1 Boolean negation under EBV (cxdm §6); an Iterator operand raises — force it first
and(a, b, …) ≥1 Boolean — true iff all args truthy; short-circuit L→R; bare operator head
or(a, b, …) ≥1 Boolean — true iff any arg truthy; short-circuit L→R; bare operator head
= (a, b) 2 Structural equality as an operator head — types, values, attributes, children all match

and / or / not are bare operator heads ([and P Q], no $ sigil), and so is =: over elements it is structural equality, the same comparison the value operators use. There is no xor / if() / instance-of builtin — branch with [?if], and test types by schema validation or [?match] shape dispatch.

input.cx

          [site [user verified=true active=true banned=false] [error 'e1']]
        

prog.cx

          [?let [= $u [$first //user]]
  ([and $u@verified $u@active [not $u@banned]],
   [$exists //error],
   [= [a 1] [a 1]])]
        
          $ cx --data=input.cx prog.cx
(true, true, true)
        

Fixture program-builtin-g6-26-logical-builtins in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Node-accessor built-ins

Operate on an Element node — read structural properties. Script-level equivalents of the Layer-1 binding methods (see layer-1).

builtin arity semantics
name(elem) 1 Element name as a string (including any namespace prefix); non-element raises CXER0100
local-name(elem) 1 Element name without namespace prefix
string(value) 1 A scalar's canonical text, or an element/attribute's text content
text(elem) 1 Body text of elem; concatenates text children (string builtin, listed for discoverability)

Structural navigation is CXPath, not accessor calls: children are $e/*, the parent is //x/parent::* (document-anchored), attributes are $e@name. Inside a predicate the reserved bindings $_position and $_last replace XPath's position() / last() — there are no zero-arity accessor calls.

input.cx

          [doc [section 'one'] [section 'two']]
        

prog.cx

          ([$name [$first //section]],
 [$string [$first //section]],
 //section[$_last],
 //section[= $_position 1])
        
          $ cx --data=input.cx prog.cx
(section, one, ([section one], [section two]), [section one])
        

Fixture program-builtin-g6-27-node-accessors in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Errors as values

CX treats errors as ordinary values. An error is a CX element with kind [err :code "cx-err:CXERnnnn" …]. It can be passed around, pattern-matched, transformed, and recovered from. Full error registry in spec/03-approved/core/code.md §9.5.

Propagation operators. Two postfix operators thread errors through expressions:

  • f($x)? — call f; if it errored, propagate the error unchanged through the surrounding directive (which catches it). This is the "early return" form.
  • f($x)! — call f; if it errored, raise immediately as a host exception (cross the CX/binding boundary). Use sparingly — it converts the error from a value to a runtime trap.

A lookup that fails on one item recovers by matching the [err] it produced, and the loop goes on:

input.cx

        [items
  [item id=1]
  [item id=2 bad=true]
  [item id=3]]
      

prog.cx

        [?for [item $i]
      [yield [?let [= $r [$validate-item $i]]
        [?match $r
          [case [err$err] [skip id=$i@id reason=$err@code]]
          [else $r]]]]]
      
        $ cx --data=input.cx prog.cx
[ok id=1]
[skip id=2 reason=cx-err:CXER0100]
[ok id=3]
      

Fixture program-err-003-on-error-recovery in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Recovery is [?match] (see match) over the [err] channel: the errorable expression is the scrutinee, and an [err] it produces is captured (not propagated) so a [case [err …] …] arm can dispatch on its code. Match by plain attribute equality ([err code='CXER0204']) or capture the code ([err @code=$c]). Arms evaluate first-match-wins; an [else …] (or bare $err capture) is the catch-all. With no catch-all and no match, the [err] propagates to the next enclosing [?match] or out of the program. For a plain value-or-default, [?else EXPR DEFAULT] (§8.13) is the lighter sugar; [?fallback PRIMARY [recover-with …]] (§10.2.4) binds $err in the recovery.

Why not exceptions. Exceptions contaminate every signature with an invisible \"may throw\" that no tool can see, and force every caller to either catch or be transparent. In CX the error is in the value, the propagation discipline is in the operators (? and !), and the handling is in [?match] — all three visible in the source. The cost of an error path is the cost of one value: no stack scan, no unwind tables. And because an error is just a value, it crosses every boundary values cross — pipelines, [?await], worker channels — without a parallel control-flow mechanism: a worker that errors sends the [err] through its result channel and terminates cleanly.

Error code registry (CXER0100–0299)

The CXER0100–0299 range is reserved for code-language errors. Errors are organized into subsystems by tens digits. The complete normative map is in spec/03-approved/core/code.md §9.5 — this subsection is the developer-facing summary.

Parse / arithmetic / application — CXER010x:

code symbolic meaning recovery
CXER0100 PARSE_ERROR Pattern syntax invalid; directive name not in registry; arity mismatch Fix source — this is author error, not runtime
CXER0101 E_ARITH_DIVIDE_ZERO Division or modulo by zero — [$div] / [$mod] / [$idiv] and the / and % operator heads Guard the divisor, or [?match] [case [err code="cx-err:CXER0101"] …]
CXER0102 E_PARTIAL_APP Invalid partial application — a _ hole in a rest position, or a partial applied with more arguments than held positions Author error — fix the call shape
CXER0103 MULTI_VALUED_PREDICATE Value-comparison or predicate with a multi-valued operand Filter the sequence and compare single values ([$exists //x[= $_ V]])

Nearby classes that are not CXER010x: anchor / merge errors are data-language E codes (spec/03-approved/core/cxdm.md §11); a sized-integer literal out of range is the lex-time CXERLEX-RANGE refusal; a failed cast is CXER0290 (E_CAST_FAILED), recovered by matching on the code or spelling a representable value — coercion is always the explicit [cast V :int] form.

Resilience — CXER014x / CXER015x:

code symbolic meaning recovery
CXER0140 RETRY_EXHAUSTED [?retry] reached max= attempts without success [?match] [case [err code="CXER0140"] …]; tune max= / backoff=
CXER0141 TIMEOUT [?timeout] DURATION elapsed [on-timeout EXPR] or [?match] [case [err code="CXER0141"] …]
CXER0150 BREAKER_OPEN [?circuit-breaker] tripped; rejecting calls Wait :reset DURATION; fallback path
CXER0151 RATE_LIMITED [?rate-limit] tokens exhausted Honor :retry-after slot in the err; back off
CXER0152 BULKHEAD_FULL [?bulkhead] max-concurrent= + queue= exhausted Backpressure caller; reduce concurrency

Services — CXER016x:

code symbolic meaning recovery
CXER0160 BAD_REQUEST HTTP 400 — malformed body or path-param parse failure Return [response status=400 [body $err]] to client
CXER0161 UNAUTHORIZED HTTP 401 — :auth returned err Return 401; client re-authenticates
CXER0162 NOT_FOUND HTTP 404 — no resource matched path + method Return 404; client checks URL
CXER0163 REQUEST_TIMEOUT HTTP 408 — :read-timeout exceeded Retry with backoff; reduce request size
CXER0164 PAYLOAD_TOO_LARGE HTTP 413 — body > max-body-bytes= Chunk upload; increase max-body-bytes= if appropriate
CXER0165 INTERNAL_ERROR HTTP 500 — unhandled err in resource body Recover the body with [?match]/[?fallback]; log and return generic 500 to client
CXER0166 SHUTTING_DOWN HTTP 503 — service in shutdown drain Client retries elsewhere; do not retry same instance

Client — CXER018x:

code symbolic meaning recovery
CXER0180 CONNECTION_REFUSED TCP connect failure Retry with backoff; check :target URL
CXER0181 TLS_HANDSHAKE_FAILED TLS negotiation failed Check :tls config; cert validity; retry
CXER0182 INVALID_RESPONSE Server response could not be parsed Log raw response; report upstream issue

Concurrency — CXER020x / CXER022x:

code symbolic meaning recovery
CXER0200 CHANNEL_CLOSED Receive from drained-closed channel; send to closed channel Treat as normal stream-end; [?match] on err code
CXER0201 SEND_TIMEOUT [?try-send] buffer-full timeout Drop, queue elsewhere, or back off
CXER0202 RECV_TIMEOUT [?try-receive] empty timeout Poll again; abandon work if pattern persists
CXER0203 CHANNEL_ALREADY_CLOSED Second [?close] on same channel Idempotent close idiom — [?else [?close $ch] ()]
CXER0220 WORKER_PANIC Worker body raised unhandled err Inspect cause= attribute; restart worker or supervisor pattern
CXER0221 WORKER_CANCELLED Worker terminated via [?cancel] Expected on graceful shutdown; clean up resources
CXER0222 WORKER_NOT_FOUND [?worker-handle name=N] lookup miss Recover with [?match] on the err code, or [?else] a default

Async — CXER024x / CXER026x:

code symbolic meaning recovery
CXER0240 AWAIT_ALL_FAILED [?await-all] saw ≥ 1 non-done future Inspect :causes slot — sequence of [err …] per failed future
CXER0241 AWAIT_TIMEOUT [?await $f timeout=DURATION] deadline expired (future itself still running) Tune timeout=; abandon or [?cancel $f]
CXER0260 CANCELLED Operation observed cooperative cancellation Expected; clean up; do not retry

Visualization — CXER028x:

code symbolic meaning recovery
CXER0281 UNRENDERABLE_DIRECTIVE Directive shape outside §10.1.2 locked render rules — the sole live renderer code (CXER0280 is retired to reserved) Adjust source; renderer is conservative on purpose

The CXER0000–0099 range is reserved for data-language errors (parse, validate, canonicalize); see limits in the data section. CXER0300+ is reserved for future subsystems.

Concurrency

CX has three concurrency surfaces, all spelled as directives. They build on the pure-functional core: every channel send, every worker call, every async await sees only immutable values, so there is no shared-mutable-state hazard. Spec: spec/03-approved/core/code.md §10.3–§10.5.

Services and clients

[?http-service] declares a named HTTP service with resource handlers. [?http-client] is its client side. Resources handle requests and return responses; the bodies are ordinary CX code.

Service definition:

prog.cx

          [?test-service-client
   [service [?http-service on=http port=0 name="svc-001"
              [resource [get "/hello"] [response status=200 body="world"]]]]
   [client-call [?let [= $c [?http-client target=$test-target]] [?pipe $c [$get _ "/hello"]]]]]
        
          $ cx prog.cx
[response status=200 body=world]
        

Fixture program-svc-001-get-happy-path in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The service above is declared on port=0 (the harness picks a free port) and the client call rides through the corpus's own [?test-service-client] helper, which binds $test-target to the running service; the tunables — read-timeout=, write-timeout=, max-body-bytes=, max-connections=, grace-period= — are attributes on the same element.

Resource parameters. Each [resource] declares method=METHOD path=PATH, optional produces= / consumes= MIME attributes, optional [auth …] predicate-expression clause, and a body expression in positional position. PATH supports :name parameter binding — /users/:id exposes $request/path-params/id inside the body.

Request and response shapes:

input.cx

          [exchange
  [request method='GET' path='/users/42'
    [path-params [id 42]]
    [query-params]
    [headers [header name='Accept' value='application/cx']]
    [body]]
  [response status=200
    [headers [header name='Content-Type' value='application/cx']]
    [body [user id=42]]]]
        

prog.cx

          (//request/@method, //request/path-params/id, //response/@status, //response/body/user/@id)
        
          $ cx --data=input.cx prog.cx
([method GET], [id 42], [status 200], [id 42])
        

Fixture program-cxpath-g6-28-request-and-response-shapes in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Lifecycle. [?http-service] returns a handle. [?service-handle name=N] looks up an existing service. [?stop $handle] initiates graceful shutdown — drains in-flight requests for up to grace-period=, then force-closes; new requests during drain receive HTTP 503 (CXER0166). Process SIGTERM / SIGINT also triggers graceful shutdown.

HTTP client. [?http-client target=URL …] returns a client value; operations pipe through it:

prog.cx

          [?test-service-client
   [service [?http-service on=http port=0 name="svc-002"
              [resource [post "/echo"] [response status=200 [body $request/body]]]]]
   [client-call [?let [= $c [?http-client target=$test-target]] [?pipe $c [$post _ "/echo" [payload value=42]]]]]]
        
          $ cx prog.cx
[response status=200 [body [payload value=42]]]
        

Fixture program-svc-002-post-happy-path in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

A client takes timeout= and a [resilience …] child (any of the resilience directives) on the same element as target=.

Status. Services are a stable surface; Tier-1 V binding ships the full implementation. Tier-2 bindings expose [?http-client] and [?service-handle] lookup but service-hosting may defer to the host's native HTTP framework. See bindings.

Workers and channels

[?worker] spawns a concurrent worker. [?channel] creates a FIFO message queue. [?send] and [?receive] move values across. [?select] is the multi-way receive.

prog.cx

          [?let [= $ch    [?channel name='jobs' buffer=8]]
      [= $sent1 [?send 1 to=$ch]]
      [= $sent2 [?send 2 to=$ch]]
  ([?receive from=$ch], [?receive from=$ch])]
        
          $ cx prog.cx
(1, 2)
        

Fixture program-conc-g6-29-channel-send-receive in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

A producer worker and a consumer over one channel keep the producer's order:

prog.cx

          [?let [= $ch [?channel name="c8" buffer=4]] [= $_ [?for [in $i (1, 2, 3, 4)] [yield [?send $i to=$ch]]]] [?for [in $j (1, 2, 3, 4)] [yield [?receive from=$ch]]]]
        
          $ cx prog.cx
1
2
3
4
        

Fixture program-conc-008-fifo-single-producer in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Channel semantics. [?channel name=… buffer=N] has a bounded FIFO buffer. buffer=0 is synchronous (rendezvous — send blocks until receive). FIFO ordering is preserved per producer-consumer pair; with multiple producers, per-producer FIFO is preserved but there is no global cross-producer ordering.

Send / receive variants.

  • [?send V to=CH] — block when buffer full; returns [ok] once buffered. Returns CXER0200 (CHANNEL_CLOSED) if CH is closed.
  • [?try-send V to=CH timeout=DURATION] — same but returns CXER0201 (SEND_TIMEOUT) on buffer-full timeout.
  • [?receive from=CH] — block when empty; returns next value. Returns CXER0200 when CH is closed and drained.
  • [?try-receive from=CH timeout=DURATION] — same but returns CXER0202 (RECV_TIMEOUT) on empty-channel timeout.
  • [?close CH] — mark closed. Subsequent [?send] fails immediately; subsequent [?receive] drains buffered values then returns CHANNEL_CLOSED. Double-close raises CXER0203.

Worker observability. Workers are handle-addressable:

prog.cx

          [?let [= $w [?worker name="w3" [body 42]]] [= $_ [?wait-for worker=$w]] [= $_ [?cancel worker=$w]] [?wait-for worker=$w]]
        
          $ cx prog.cx
42
        

Fixture program-conc-012-worker-cancel-after-done-noop in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[?wait-for worker=$w] blocks until the worker is done, [?cancel worker=$w] requests cooperative cancellation (a no-op once the worker has finished, as above), and [?check-cancel] is the polling point inside a hot loop (see async).

Select — multi-way receive. Evaluates all [case …] clauses concurrently; proceeds with the first ready. Uniform-random selection on simultaneous readiness:

prog.cx

          [?let [= $jobs   [?channel name='jobs' buffer=1]]
      [= $signal [?channel name='sig'  buffer=1]]
      [= $sent   [?send 42 to=$jobs]]
  [?select
    [case [from $jobs $job]   [handled $job]]
    [case [from $signal $sig] [signalled $sig]]
    [case [timeout 5s]        [heartbeat]]]]
        
          $ cx prog.cx
[handled 42]
        

Fixture program-conc-g6-30-select-first-ready in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Shape. [?select] clauses are clause-children: [case [from CH $msg] HANDLER] for a receive and [case [timeout DURATION] HANDLER] for a deadline. The spec/03-approved/core/code.md §10.4.7 registry holds the authoritative shape. The v0.7.x :case [:from CH $msg H] [; version-literal-ok ] colon-slot form is retired — there is no dual-accept.

Async, await, and cancellation

[?async EXPR] evaluates EXPR in a new async context and returns a future immediately:

prog.cx

          [?let [= $f1 [?async [+ 1 1]]]
      [= $f2 [?async [* 2 2]]]
      [= $f3 [?async [$upper 'go']]]
  [?await-all ($f1, $f2, $f3)]]
        
          $ cx prog.cx
(2, 4, GO)
        

Fixture program-async-g6-31-await-all in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Future state machine. Futures progress through pending → running → done / failed / cancelled. Once terminal, the state is immutable.

Barrier variants:

  • [?await $f] — block until terminal; return value on done, propagate err on failed, CANCELLED on cancelled.
  • [?await $f timeout=DURATION] — add a hard deadline; on expiry return CXER0241 (AWAIT_TIMEOUT). The future continues running — timeout= bounds the caller's wait, not the future itself.
  • [?await-all FUTURES] — block until all terminal. If all done, yield sequence of values. If any failed/cancelled, yield CXER0240 with a [causes …] child carrying every non-done cause in input order.
  • [?await-any FUTURES] — yield first to terminate. Other futures continue running; their results are discarded.
  • [?await-race FUTURES] — yield first to terminate; other futures are cancelled. Use for "I only need one".

Cooperative cancellation contract. [?cancel $f] requests cancellation — it is a signal, not a kill. The runtime guarantees cancellation observation at:

  • HTTP client calls at next network read/write boundary.
  • [?sleep DURATION] — immediately.
  • [?send] / [?receive] / [?try-send] / [?try-receive] — immediately, returning CXER0260 (CANCELLED).
  • [?for] — at every iteration boundary.
  • [?await] — immediately.

Pure CPU loops without yield points do NOT observe cancellation. Authors who want a cancellable hot loop must insert [?check-cancel] at appropriate boundaries — it evaluates to [ok] normally, or CXER0260 (CANCELLED) if cancellation is pending.

prog.cx

          [?def cancellable-compute impure ($n)
  [?for [in $i [$range 1 $n]]
    [where [= [?check-cancel] [ok]]]
    [yield [* $i $i]]]]
[$cancellable-compute 4]
        
          $ cx prog.cx
1
4
9
16
        

Fixture program-async-g6-32-check-cancel-in-a-loop in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Composition with resilience. [?async] composes with every resilience directive. [?async [?timeout 5s [$slow-call]]] returns a future that resolves to CXER0141 (TIMEOUT) after 5s if slow-call hasn't completed.

Status. Async/await is a stable surface of the reference evaluator; binding parity ships per spec/03-approved/misc/parity-matrix.md.

Resilience directives

The six resilience directives wrap an inner expression with a failure-handling policy. They are not new control-flow primitives — they are normal CX code with cleanly-named policies. Spec: spec/03-approved/core/code.md §10.2.

Common error shape. Every resilience directive on terminal failure emits a structured [err …] per the §10.2.7 error-code matrix (see error-registry).

[?retry] — bounded re-run with backoff

Re-run an inner body on [err …] return, up to max= attempts, sleeping between attempts per backoff= / delay= / jitter=. Terminal failure raises CXER0140 (RETRY_EXHAUSTED).

parameter default semantics
max=INT (required) Maximum attempts; must be > 0
backoff=STRATEGY exponential constant / linear / exponential / fibonacci
delay=DURATION 100ms Base delay; combined with backoff formula
jitter=MODE equal none / full / equal / decorrelated
[on PREDICATE] [?fn ($e) true] Per-err filter; retry iff truthy
body (positional) (required) Inner expression to retry

Backoff formulas (delay for attempt N, N ≥ 1): constant = delay=; linear = delay × N; exponential = delay × 2^(N-1); fibonacci = delay × fib(N).

Jitter modes (D = pre-jitter delay): none = D; full = uniform(0, D); equal = D/2 + uniform(0, D/2); decorrelated = uniform(delay, last_delay × 3).

The full policy reads [?retry max=5 backoff=exponential delay=200ms jitter=equal [on [?fn ($e) [= $e@code 'CXER0163']]] BODY] — retry only on a timeout. The fixture below retries a body that fails twice and succeeds on the third attempt:

prog.cx

          [?retry max=5 [?test-err-then-ok err-count=2 ok-value=done]]
        
          $ cx prog.cx
done
        

Fixture program-retry-002-success-on-third-attempt in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[?timeout] — hard deadline

Bound execution time of an inner body. On expiry, cooperatively cancel the body and return the [on-timeout …] clause body (if present) or [result status=err code='cx-err:CXER0141' elapsed=DURATION].

parameter default semantics
DURATION (positional) (required) Hard deadline; e.g. 5s, 250ms
body (positional) (required) Inner expression with cooperative cancel points
[on-timeout EXPR] (none) Fallback expression on expiry; otherwise raises CXER0141

prog.cx

          [?timeout 100ms
   [?let [= $_ [?sleep 500ms mock]] never]
   [on-timeout fallback]]
        
          $ cx prog.cx
fallback
        

Fixture program-timeout-003-on-timeout-recovery in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[?circuit-breaker] — fail-fast on consecutive failures

Three states: closed (passes through), open (rejects with CXER0150 (BREAKER_OPEN) without calling :body), half-open (allows one probe). Transitions:

  • Closed → open when failure ratio in rolling window= exceeds threshold=, with at least min-samples= recorded.
  • Open → half-open after reset= elapses.
  • Half-open → closed if probe succeeds.
  • Half-open → open if probe fails.
parameter default semantics
threshold=RATIO (required) Failure ratio 0.0–1.0 to trip breaker
window=DURATION (required) Rolling-window length for sampling
reset=DURATION (required) Half-open delay after trip
min-samples=INT 10 Minimum sample count before threshold check
name=STR (lexical position) State-identity key for cross-position sharing
body (positional) (required) Inner expression

prog.cx

          [?for [in $i (1, 2, 3, 4, 5)]
      [yield [?circuit-breaker threshold=0.5 window=60s reset=30s
                               min-samples=2 name="cb-002" [?test-always-err]]]]
        
          $ cx prog.cx
[err code=test-failure message='test failure' at='3:60']
[err code=test-failure message='test failure' at='3:60']
[err code=cx-err:CXER0150 message='circuit breaker open' at='2:14' [until [instant 30s]]]
[err code=cx-err:CXER0150 message='circuit breaker open' at='2:14' [until [instant 30s]]]
[err code=cx-err:CXER0150 message='circuit breaker open' at='2:14' [until [instant 30s]]]
        

Fixture program-cb-002-trips-open in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[?fallback] — alternative on error

If primary returns [err …], evaluate secondary and return its value (which may itself be [err] — [?fallback] does not wrap that case).

parameter default semantics
body (positional) (required) First expression tried (primary)
[recover-with SECONDARY] (required) Fallback expression evaluated only on primary err

prog.cx

          [?fallback [body [err code='down' message='service down']] [recover-with secondary]]
        
          $ cx prog.cx
secondary
        

Fixture program-fallback-002-primary-err-secondary-ok in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

$err in the recovery scope. Inside the recovery scope, $err is bound to the primary's [err …] value, so recovery can introspect the failure (e.g. [recover-with [response status=500 reason=$err/@message]]). The clause-child [recover-with …] form is the sole accepted shape (no legacy colon-slot).

[?rate-limit] — token-bucket cap

Permits at most max= invocations per per= window. Excess invocations return CXER0151 (RATE_LIMITED) with a retry-after=DURATION attribute — the time until the next token frees.

parameter default semantics
max=INT (required) Tokens per window; must be > 0
per=DURATION (required) Refill window
name=STR (lexical position) State-identity key
body (positional) (required) Inner expression

prog.cx

          [?for [in $i (1, 2, 3, 4)]
      [yield [?rate-limit max=2 per=1s name="rl-002" $i]]]
        
          $ cx prog.cx
1
2
[err code=cx-err:CXER0151 message='rate limit exceeded' at='2:14' retry-after::duration=1s]
[err code=cx-err:CXER0151 message='rate limit exceeded' at='2:14' retry-after::duration=1s]
        

Fixture program-ratelimit-002-over-limit in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

[?bulkhead] — bound concurrent calls

Bounds concurrent invocations. If max-concurrent= slots are busy and queue= slots are full, returns CXER0152 (BULKHEAD_FULL). Queued requests wait FIFO for a free slot.

parameter default semantics
max-concurrent=INT (required) In-flight cap; must be > 0
queue=INT 0 Wait-queue depth; 0 = no queue, fail-fast on full
name=STR (lexical position) State-identity key
body (positional) (required) Inner expression

prog.cx

          [?test-concurrent tasks=(
   [?bulkhead max-concurrent=1 queue=0 name="bh-002" [?let [= $_ [?sleep 500ms mock]] first]],
   [?bulkhead max-concurrent=1 queue=0 name="bh-002" second])]
        
          $ cx prog.cx
([err code=cx-err:CXER0152 message='bulkhead saturated' at='3:4' max=1], first)
        

Fixture program-bulkhead-002-saturated-no-queue in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Composition

Resilience directives compose by nesting. The outermost runs first, so its policy sees the inner policies' err codes. Standard pattern reads top-down:

prog.cx

          [?retry max=3 backoff=constant delay=10ms jitter=none [?timeout 100ms
            [?let [= $_ [?sleep 500ms mock]] never]]]
        
          $ cx prog.cx
[err code=cx-err:CXER0140 message='retry budget exhausted after 3 attempts' at='1:1' attempts=3 [cause [err code=cx-err:CXER0141 message='operation timed out after 100ms' at='1:55' elapsed::duration=100ms]]]
        

Fixture program-compose-001-retry-over-timeout in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Reading: retry up to 3 times a deadline-bounded body; the timeout's CXER0141 is what [?retry] sees on each attempt, and the exhausted retry names it as the cause. A breaker in the same position surfaces CXER0150, and [?retry] decides whether to keep retrying (the default [on …] predicate returns truthy → yes; override to scope which errs retry).

State identity (stateful directives). [?circuit-breaker], [?rate-limit], and [?bulkhead] carry state across invocations. By default state is keyed by lexical source-text position — every evaluation of the same source node (including iterated under [?for] or [?map … [par]]) shares state. Pass name=STR to key by name instead, enabling cross-position sharing (one rate-limit budget enforced by callers in different modules).

Evaluation framing — directives, patterns, comprehensions

The directive registry above lists each form; this section shows how the forms fit together — directives as the language layer over data, pattern matching as the way programs walk a tree, comprehensions as the composition glue. CXPath is a value kind (with [?find] retired) and [expr] is the general predicate.

Directives turn data into a language

A directive is a CX element whose name starts with ?. It carries semantics — iteration, branching, substitution, include, function call — that the evaluator interprets when running a CX program. Directives parse as regular CX, evaluate to text or values, and compose with each other through their slot bodies.

        EXPR                                     [; bare expression evaluates ]
           [?for [in $x PATH] [yield BODY]]         [; iterate ]
           [?if COND [then THEN] [else ELSE]]       [; branch ]
           [?let [= $x EXPR] BODY]                  [; bind a name ]
           [?def name ($a $b) BODY]                 [; define a function ]
      

Pattern matching — walking the tree

The current program surface replaces XPath-style queries with [?for] pattern generators and CXPath path values ([?find] retired). A pattern is a CX literal where element names match by shape, attribute predicates restrict by value, and $bindings capture matched subtrees. [?for] walks the tree (deep by default) and binds for each match; [?match] tests a single value (multi-arm dispatch).

input.cx

          [shop
  [user [name 'Ada']]
  [item active=true 'i1']
  [pizza [name 'Margherita'] [price 12]]]
        

prog.cx

          ([?for [user [name $n]] [yield $n]],
 [?for [item @active=true $i] [yield $i]],
 [?for [pizza [name $n] [price $p]]
       [yield [hit name=$n price=$p]]])
        
          $ cx --data=input.cx prog.cx
(Ada, [item active=true i1], [hit name=Margherita price=12])
        

Fixture program-for-g6-33-pattern-generators-walk-the-tree in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Comprehensions and composition

[?for] iterates a sequence (or matches a pattern across the tree) and yields per-iteration values; [where …] filters, [order-by …] sorts, [group-by …] aggregates. [?let] binds names; [?if] branches. The [?pipe] directive composes them.

prog.cx

          (([?for [in $x (1, 2, 3)] [where [> $x 1]] [yield [* $x $x]]]),
 [?let [= $a 10] [= $b 32] [+ $a $b]],
 [?pipe (1, 2, 3, 4) [$count]])
        
          $ cx prog.cx
((4, 9), 42, 4)
        

Fixture program-for-g6-34-comprehension-let-pipe in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Future surface — ranges, slices, Iterator

A future surface extends the comprehension surface with strided ranges ([$range 1 99 2], [$range 1 *]), slice expressions on a binding postfix ($xs[2:5], $xs[::-1], $xs[:, "name"]), first-class slice values ([?def $s [2:$_last:2]]), and a new Iterator value kind in CXDM §2 alongside Sequence / Array / Map. Comprehensions become lazy by default; named iterators ([?def]) memoize on first walk so re-use is safe.

Four worked examples (§W1–§W4) each have a paired conformance fixture program-slice-wN-* in conformance/code.cxd; until the implementation lands the fixtures are out_err: cx-err:CXER0100 (parse-deferred), then flip to out_text in a single commit per fixture. The reconciliation test program-cxpath-fncall-slice-composition verifies grammar disambiguation against the fused predicate-body closure (spec/03-approved/formal/grammar.ebnf [159b]) — bare-fn-call, path predicate, and slice postfix coexist.

The W1 program — a strided range, a slice, a comprehension — is pinned today at its parse refusal, and flips to its output the day the surface lands:

prog.cx

            [?def $odds [$range 1 99 2]]
[?def $first-ten $odds[:10]]
[?def $squares [?for [in $x $first-ten] [yield [* $x $x]]]]
$squares
          
            $ cx prog.cx
error: cx-err:CXER0100: [?def] parse: CXDEF_PARSE: missing function name at line 1:1
          

Fixture program-slice-w1-strided-range-then-slice in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The path-to-iterator taxonomy (D26): a CXPath value is not an Iterator — it is a description of a selection. Evaluation materialises it to a sequence (eager, the kind currently in use). The sequence flows into Iterator combinators on opt-in via [?for]; walking an Iterator back to a concrete collection is the registered force-materializer family [?to-sequence] / [?to-array] / [?to-map] (a [?to-iterator] directive is planned but not registered). There is no iterator → path direction — paths describe; iterators walk.

CXPath reference — quick form

At-a-glance CXPath reference. The normative grammar lives in spec/03-approved/formal/grammar.ebnf productions [130]/[131]/[135] (path steps / binding paths) and [159]/[160a] (general PredicateExpr + the (bind $name) step annotation). XPath 3.1 alignment and divergences are in spec/03-approved/core/code.md §5.5. CXPath is a first-class value kind; general predicates with $_ / $_position / $_last.

Quick reference

In order: every user; every active user; the active users' emails; the third user in document order; everything two levels under the root; the siblings after the first user; the ancestors of a [para]; a general predicate — prefix code over $_; a range; and the three set heads union, intersect, except:

input.cx

          [org
  [team
    [user active=true  age=30 email='a@example.org']
    [user active=false age=40 email='b@example.org']
    [user active=true  age=17 email='c@example.org']
    [para 'p']]
  [a 1] [b 2]]
        

prog.cx

          ([$count //user],
 [$count //user[= $_@active true]],
 //user[= $_@active true]/@email,
 //user[3]/@email,
 [$count /*/*],
 [$count //user[1]/following-sibling::user],
 ([?for [in $e //para/ancestor::*] [yield [$name $e]]]),
 //user[and [>= $_@age 18] $_@active]/@email,
 [$range 1 10],
 [union //a //b],
 [intersect //user //team/user[= $_@active true]],
 [except //user //user[= $_@active false]])
        
          $ cx --data=input.cx prog.cx
(3, 2, ([email a@example.org], [email c@example.org]), [email c@example.org], 3, 2, (org, team), [email a@example.org], (1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ([a 1], [b 2]), ([user active=true age=30 email='a@example.org'], [user active=true age=17 email='c@example.org']), ([user active=true age=30 email='a@example.org'], [user active=true age=17 email='c@example.org']))
        

Fixture program-cxpath-g6-35-quick-reference in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Four leading forms

Every CXPath has one of four leading-token forms. - // descendant — //user — descendant-or-self::node()/child::user; the most common shape. - / absolute — /root/item — child of the document root; rare in well-shaped data. - relative — user/email — applied against the current evaluation context. - $name/ binding — $u/email — rooted at a previously-bound value (per [?for] / [?let] / (bind $name)).

Twelve axes

Every step can specify an axis. The default axis is child::; the attribute:: axis has the dedicated @ sigil. CX implements all twelve XPath 3.1 axes: child, descendant, descendant-or-self, parent, ancestor, ancestor-or-self, self, following-sibling, preceding-sibling, following, preceding, attribute.

Composite shortcut forms: /name ≡ child::name; //name ≡ descendant-or-self::node()/child::name; @name ≡ attribute::name; .. ≡ parent::node(); . ≡ self::node(). Attribute axis is name-disjoint from element axes — //user/@name selects the attribute only; //user/name selects the child element only.

General predicate + reserved bindings

Predicates filter the step sequence. A predicate body is prefix CX code coerced to a boolean via the Effective Boolean Value (EBV) rule. The atomic shorthands — [@name] / [@!name] (attribute present / absent), [N] (position), a bare child name (step-existence) — remain available and round-trip via canonical re-emit. Value comparisons are always prefix: [= $_@name V].

Three reserved bindings in scope inside every predicate body: $_ (current candidate item, type matches the candidate kind), $_position (1-based int index — equivalent to XPath position()), $_last (int cardinality of the candidate sequence — equivalent to XPath last()). Predicates are eagerly materialised, so $_last is known on every predicate evaluation.

Comparison sigils are value-comparison only: =, !=, <, <=, >, >=. No keyword synonyms (no eq/ne/lt/etc.). Comparing a sequence with more than one item raises CXER0103. Purity — every predicate body MUST be pure; impure callees fail load with CXER0230.

Cross-step reference — nested generators

To reference an outer step's focus from an inner predicate ("leads of teams with at least three members"), stack [?for] generators — each generator names its focus, and every later clause sees the earlier bindings:

input.cx

          [org
  [team [member role='lead' [name 'a']] [member [name 'b']] [member [name 'c']]]
  [team [member role='lead' [name 'e']]]]
        

prog.cx

          [?for [in $t //team]
      [in $m $t/member]
      [where [and [= $m@role 'lead'] [>= [$count $t/member] 3]]]
  [yield $m/name]]
        
          $ cx --data=input.cx prog.cx
a
        

Fixture program-for-g6-36-nested-generators-see-outer-bindings in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

The grammar reserves a (bind $name) path-step annotation (grammar.ebnf [160a]) for naming a step's focus inline; the nested-generator form above is the conformance-exercised idiom.

In the host bindings

Every binding exposes CXPath identically through its Layer-1 Doc.select_all — the same selector string, the same bytes returned across the C ABI; the bindings page shows each spelling. The selector, run here by cx itself:

input.cx

          [users
  [user active=true  email='ada@example.org']
  [user active=false email='bob@example.org']
  [user active=true  email='cy@example.org']]
        

prog.cx

          //user[= $_@active true]/@email
        
          $ cx --data=input.cx prog.cx
[email ada@example.org]
[email cy@example.org]
        

Fixture program-cxpath-g6-37-active-emails-one-selector in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.

Error code catalog

Every CX error carries a structured payload: cx-err:CODE then a unit separator (US, 0x1F) then a human-readable description then optionally another unit separator and a value. Bindings preserve this shape so [?match] can match on code rather than on text. The catalog below covers the current ranges; the normative registry is spec/03-approved/core/code.md §9.4/§9.5, with the governance rules for code allocation in spec/03-approved/process/governance.md §9.6.

Capability and runtime caps

- CXER0271 — capability denied at an effect point. Capabilities are deny-by-default; the param-less eval entry points run with the empty capability set — "pure-only" is the empty set, not a pragma. - CXER0272 — E_STACK_EXHAUSTED: the evaluator's native-stack exhaustion guard.

Purity and eval gates

- CXER0230 — purity gate: an impure callee where purity is required (e.g. inside a CXPath predicate) is rejected at parse / module-load. - CXER0233 — a pure def reaching [?eval] / cx:eval-tree (tree-eval is impure, like cx:eval). - CXER4113 — lib-widen gate: [?lib] inside an eval tree may not widen the capability set. - CXER4114 — eval depth gate: cx:eval / [?eval] recursion exceeded the shared depth budget.

E9xx — include resolver

- E901 — absolute path rejected. - E902 — path escapes include root (lexical or symlink). - E903 — URL-scheme path rejected. - E904 — cycle detected. - E905 — max depth exceeded. - E906 — file not found. - E907 — file not readable. - E908 — path is a directory. - E909 — I/O error during read. - E910 — non-UTF-8 content. - E911 — included file parse failure.

Recovering with [?match]

An ungranted read answers CXER0271 — the capability refusal, an err value like any other:

prog.cx

          [?lib 'cx-stdlib/io']
[$io:read-file "/var/log/app.log"]
        
          $ cx prog.cx
[err code=cx-err:CXER0271 message='E_CAP_DENIED: read capability required for io-read-file; none granted (grant via --allow-read)' at='2:1']
        

Fixture io-001-read-file-cap-denied in deps/cx-core-code/conformance/stdlib/io.cxd — replayed against this cx when the page was built.

Under --allow-read, a read of a missing file answers CXER3401; either way a [case [err @code=$c] …] arm dispatches on the code by name, and the catch-all arms take whatever is left:

prog.cx

          [?lib 'cx-stdlib/io']
[?match [$io:read-file '/no/such/file.cx']
  [case [err @code=$c] [where [= $c 'cx-err:CXER0271']] [needs-capability]]
  [case [err$e] [other-error code=$e@code]]
  [else :ok]]
        
          $ cx --allow-read prog.cx
[other-error code=cx-err:CXER3401]
        

Fixture program-err-g6-38-recover-by-code in deps/cx-core-code/conformance/code.cxd — replayed against this cx when the page was built.