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:                       #]
         [user id=1 email='ada@example.com']

         [# Code is data, plus a `?`:           #]
         [?for [in $u //user] [yield $u@email]]
        

The evaluator is `cx_code_eval(doc, code, target_format)` at the C ABI level; every binding's Layer-1 `Doc.eval(code)` wraps it (see bindings). The full normative spec is [`spec/code.md`](../../spec/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/code.md`](../../spec/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 **forty normative directives** — including `[?modify]`. Adding or removing any directive requires a governance change per [`spec/governance.md`](../../spec/governance.md) §10. The authoritative registry lives in [`spec/code.md`](../../spec/code.md) §4.1; this subsection is the at-a-glance grouping.

**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; par=true enables parallel evaluation, ordered=true preserves source order)
[?reduce] Fold a sequence to a single value via a binary closure starting from [init …] (sequential left-fold by default; par=true 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
[?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 (10).** Workers + channels + 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 with :buffer of given size
[?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)
[?stop] Initiate graceful shutdown on a service or worker handle

**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 shared).** `[?stop]` (above) 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. V (the native reference) carries the spec-conformant implementation; Tier-2 bindings (Python, Go, Rust) follow per the parity matrix in [`spec/v0_8_0_status.md`](../../spec/v0_8_0_status.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/cxpath_alignment.md`](../../spec/cxpath_alignment.md)) 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                       [# every user, anywhere in doc      #]
         //user[= $_@active true]     [# every active user                #]
         //user/@email                [# scalar attribute values          #]
         //section[1]                 [# first section (1-indexed)        #]
         //section[h2]                [# section that has an h2 child     #]
        

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`
              [# Common axis idioms                                              #]
           //section//*                              [# all descendants       #]
           //code/ancestor::section                   [# enclosing sections    #]
           //user/following-sibling::user             [# later peer users      #]
           //tr/parent::table                         [# tables containing tr  #]
           //h2/preceding-sibling::*[1]               [# immediate prior sib   #]
           //user[= $_@role 'admin']                  [# only-if-admin filter  #]
           //user/attribute::email                    [# == //user/@email      #]
            

Axis-qualified steps apply to document-anchored paths. From a **binding**, the child / descendant / attribute steps work directly (`$u/name`, `$u//item`, `$u@email`); axis-qualified steps on a binding (`$u/ancestor::…`) are not yet supported — anchor the path at the document and filter instead. 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']`).
              //user[1]                              [# first user                #]
           //user[= $_@id 42]                     [# attr equality             #]
           //user[@active]                        [# attr present              #]
           //user[$strings:contains $_@email '@example.com']
           //user[and [>= $_@age 18] [= $_@country 'US']]
           //section[h2]                          [# has an h2 child           #]
           //section[and [$count $_/h2] [$count $_/p]]
            

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 //x[= $_ 1]]`.

              //user[= $_@age 42]              [# single attr → ok             #]
           //section[> [$count $_/p] 3]     [# both sides single → ok       #]
           //user[= $_@email 'ada@example.com']
           [# a multi-valued operand raises — filter the sequence
              instead of comparing it wholesale #]
           [$exists //user/tag[= $_ 'admin']]
            

Paths on bindings

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

              [# $u/name — direct child · $u/* — all children ·
            $u//item — descendant · $u@active — attribute (shorthand) ·
            $u/@active — attribute (full axis form)                    #]
           [?let [= $u [user active=true region='EU'
                        [name 'Ada'] [item 1] [item 2]]]
             ($u/name, $u/*, $u//item, $u@active, $u/@active)]
            

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. Axis-qualified steps (`ancestor::` etc.) are document-path syntax and are not yet supported on a binding; map-key access on a map-valued binding uses `[$map:get $m 'key']`.

Where CXPath values are accepted

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

              [?for [in $u //user[= $_@active true]] [yield [$render $u]]]
           [?if //admin [then [admin-panel]] [else ()]]
           [?match $n [case //prose [p $n]] [else ()]]
           [?let [= $cfg //meta/@config] [$render $cfg]]
           [?modify $doc //user/@status [set 'verified']]
           [$count //user[= $_@active true]]
            

The same path expression — `//user[= $_@active true]` — 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. This is the unification CX ratifies: 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/v0_8_0_status.md`](../../spec/v0_8_0_status.md) gate 26.4.

Deliberate divergences from XPath 3.1

CXPath is a working subset of XPath 3.1 + deliberate changes. The full alignment table is in [`spec/cxpath_alignment.md`](../../spec/cxpath_alignment.md); 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 full XPath 3.1 alignment table — what is adopted, what is deliberately not, what is on the XPath 4.0 watch list — is in [`spec/cxpath_alignment.md`](../../spec/cxpath_alignment.md).

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/code.md`](../../spec/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]               [# bind email attribute             #]
           [user @active=true $u]        [# attr filter; bind whole user     #]
           [user @id=42 [name $n]]       [# bind name's body                 #]
           [post author=$a body=$b]      [# bind two attrs                   #]
            

**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/code.md`](../../spec/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:

              [?match $status
           [case 200    :ok]
           [case 404    :not-found]
           [case 500    :server-error]
           [case 'admin' :elevated]
           [case true   :enabled]
           [case null   :missing]
           [else        :unknown]]
            

Integers, floats, strings, bools, dates, datetimes, and `null` are all matchable as scalar literals. Float equality follows the EBV rules — `nan` is **never** equal to `nan` (per IEEE-754). Use `is-nan($x)` to test for NaN explicitly.

CXPath patterns

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

              [doc [heading 'Title'] [prose 'Body.']]
           [?let [= $node [$first //heading]]
             [?match $node
               [case //heading [h2 [$text $node]]]
               [case //prose   [p  [$text $node]]]
               [else           $node]]]
            

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

              [users [user email='ada@ex.com' active=true]
                [user email='bob@ex.com' active=false]]
           [?for [in $u //user[= $_@active true]] [yield $u@email]]
            

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]`.
              [?let [= $node [section [h2 'T'] [code 'x = 1']]]
             [?match $node
               [case [section * $body] [matched-section]]
               [case [** [code $c]]    [highlighted $c]]
               [case _                 $node]]]
            

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:

              [# Match only elements whose schema type is ServerConfig #]
           [?match $cfg
             [case [:ServerConfig $c] [$validate-server $c]]
             [case [:ClientConfig $c] [$validate-client $c]]
             [else [result status=err code='CXER0103' reason='unknown config']]]
            

Type-guard resolution is by the document's schema binding (see schema in the data section); with no schema in scope type guards are rejected at program load. 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).

          [users [user role='admin' email='root@example.com']
              [user role='user'  email='ada@example.com']]
         [?let [= $admins //user[= $_@role 'admin']]
           [?for [in $a $admins] [yield $a@email]]]
        

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.

              [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']]]

           [# CXPath generator                                           #]
           [?for [in $u //user] [yield $u@email]]

           [# Pattern generator (the `[?find]` replacement)              #]
           [?for [user @active=true $u] [yield $u@email]]

           [# Filter with [where …]                                      #]
           [?for [in $u //user] [where [>= $u@age 18]] [yield $u@id]]

           [# Multiple generators (Cartesian, in declared order)         #]
           [?for [in $u //user]
                 [in $p //post[= $_@author $u@id]]
                 [yield [pair user=$u@id post=[$text $p/title]]]]

           [# Parallel evaluation                                        #]
           [?for [in $r //request] [par] [yield [got [$text $r]]]]

           [# Streaming over large input                                 #]
           [?for [in $row //logs/row] [stream] [yield [$text $row]]]
            

`[par]` and `[stream]` are evaluation-modifier clauses — same surface, different scheduler. `[par]` distributes iterations across worker threads (add `[ordered]` to preserve source order). `[stream]` couples the generator to a streaming input source so that the iteration runs in bounded memory regardless of input size. Full semantics in [`spec/code.md`](../../spec/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:

              [# Destructures $result as [ok V] or raises CXER0100 #]
           [?let [= $result [ok 42]]
             [?match $result [ok $v] [yield [rendered $v]]]]
            

**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:

              [?let [= $node [code 'x = 1']]
             [?match $node
               [case [prose $p]   [p $p]]
               [case [code $c]    [pre $c]]
               [case 'separator'  [hr]]
               [else              ()]]]

           [# SQL Searched-CASE — predicate-only mode (no scrutinee)     #]
           [?let [= $x 120] [= $y 10]
             [?match
               [when [> $x 100]   :big]
               [when [> $y 50]    :medium]
               [else              :small]]]

           [# Guards on case arms — [where …] filters even on a match    #]
           [?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]]]]
            

**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/v0_8_0_status.md`](../../spec/v0_8_0_status.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.

              [site [admin 'root'] [config debug=false]]
           [?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 ()]]]
            

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/cxdm.md`](../../spec/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 shared by Python lists/dicts, JSON-template engines, and YAML/TOML processors. 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.

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

           [# Multiple bindings — flat, sequential, each sees the prior #]
           [?let [= $meta [$first //meta]]
                 [= $base [$string $meta@host]]
                 [= $port [$string $meta@port]]
                 [= $url  [$concat 'https://' $base ':' $port]]
             [endpoint target=$url]]
            

`[?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
              [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']]

           [# Scalar attribute update                                    #]
           [?modify $doc //user[= $_@id 1]/@name [set 'Alice']]

           [# Delete matching nodes                                      #]
           [?modify $doc //user[= $_@active false] [delete]]

           [# Transform with [using …]                                   #]
           [?modify $doc //price [using [?fn ($p) [* $p 1.1]]]]

           [# Rename — keep attrs + body, change name                    #]
           [?modify $doc //para [rename p]]

           [# Set + delete attrs                                         #]
           [?modify $doc //user [set-attr verified true]]
           [?modify $doc //user [delete-attr legacy-id]]

           [# Append + prepend children                                  #]
           [?modify $doc //section[= $_@id 'intro'] [append [para 'Added.']]]
           [?modify $doc //list [prepend [item 'header item']]]

           [# Insert siblings                                            #]
           [?modify $doc //h2 [insert-before [hr]]]
           [?modify $doc //code [insert-after [aside 'note']]]

           [# Whole-node replace                                         #]
           [?modify $doc //deprecated [replace ()]]

           [# Multi-step pipeline composition (see [[pipe]])             #]
           [?pipe $doc
             [?modify //user/@status [set 'verified']]
             [?modify //user[= $_@banned true] [delete]]
             [?modify //user [set-attr last-audit '2026-05-22']]]
            

**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). For a 10 MB document with one matched node, the new heap cost is < 1 KB per matched node — 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/v0_8_0_status.md`](../../spec/v0_8_0_status.md) gate 30.

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.

              [shop [user active=true age=30]
               [user active=true age=20]
               [user active=false age=40]]
           [?pipe //user[= $_@active true]
             [?fn ($us) [?for [in $u $us] [yield $u@age]]]
             [$reverse]
             [$nth _ 1]]
            

**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:

              [users [user name=ada] [user name=bob]]
           [?pipe //user
           [?fn ($us) [?for [in $u $us] [yield [decorated name=$u@name]]]]
           [$count]]
            

**`[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/code.md`](../../spec/code.md) §6.5; the subsections below organize the ~80 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
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).

              [site [user active=true] [user active=false]
                 [tag 'a'] [tag 'b'] [tag 'a']]
           ([$count //user[= $_@active true]],
            [$distinct [?for [in $t //tag] [yield [$text $t]]]],
            [?take 3 [$range 1 100]],
            [union (1, 2) (2, 3)])
            

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.

              [?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'])
            

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.

builtin arity semantics
sum(seq) 1 Sum of numeric scalars; empty yields int 0; any float → float result
min(seq) 1 Minimum numeric; empty yields int 0
max(seq) 1 Maximum numeric; empty yields int 0
avg(seq) 1 Arithmetic mean as float; 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
idiv(a, b) 2 Integer (truncating) division regardless of operand kinds

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. CX floats are **finite-only**: `NaN` and `±Inf` never arise (an operation that would produce one raises `CXER0101`), so there is nothing to test with an `is-nan`. Exponentiation is `[$math:pow]` in `cx-stdlib/math`.

              [orders [order amount=10] [order amount=32]]
           ([$sum [?for [in $o //order] [yield $o@amount]]],
            [$round 3.7],
            [$mod 17 5],
            [cast '42' :int])
            

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
eq(a, b) 2 Structural equality — types, values, attributes, children all match

`and` / `or` / `not` are bare operator heads (`[and P Q]`, no `$` sigil); `eq` is the structural equality the `=` operator also uses. There is no `xor` / `if()` / `instance-of` builtin — branch with `[?if]`, and test types by schema validation or `[?match]` shape dispatch.

              [site [user verified=true active=true banned=false] [error 'e1']]
           [?let [= $u [$first //user]]
             ([and $u@verified $u@active [not $u@banned]],
              [$exists //error],
              [eq [a 1] [a 1]])]
            

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.

              [# name → 'section' · string → 'one' · [$_last] → the last
            section · [= $_position 1] → the first                    #]
           [doc [section 'one'] [section 'two']]
           ([$name [$first //section]],
            [$string [$first //section]],
            //section[$_last],
            //section[= $_position 1])
            

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/code.md`](../../spec/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.
          [?match [?let [= $u [$lookup-user $id]?] [$render $u]]
           [case [err code='CXER0204'] [err message='no such user']]
           [case [err code='CXER0163'] [err message='lookup timed out']]
           [else [err message='lookup failed']]]
        

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.

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/code.md`](../../spec/code.md) §9.5 — this subsection is the developer-facing summary.

**Pattern / parse — 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 ANCHOR_ERROR Duplicate &anchor; cyclic *merge; unresolved *name Author error — flagged at parse time
CXER0102 DUPLICATE_KEY Map literal with duplicate key Author error
CXER0103 TYPE_ERROR Value-comparison on multi-valued operand; sized-overflow; type-coerce failure [?match] [case [err code="CXER0103"] …]; explicit coercion via to-int/to-float

**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] lookup miss Use [?worker-handle :name N :optional true] or check exists

**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] 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
CXER0280 RENDER_FAILED Sequence-diagram renderer could not produce output Check directive shape; report renderer version + program
CXER0281 UNRENDERABLE_DIRECTIVE Directive shape outside §10.1.2 locked render rules 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/code.md`](../../spec/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:**

              [?http-service on=http port=8080 name='users'
           read-timeout=30s
           write-timeout=30s
           max-body-bytes=10485760            [# 10 MiB                  #]
           max-connections=1000
           grace-period=30s

           [resource method=GET path='/users/:id'
             [?let [= $u //user[@id=$request/path-params/id]]
               [response status=200 [body $u]]]]

           [resource method=POST path='/users'
             consumes=application/cx
             [?let [= $body $request/body]
               [response status=201 [body [$create-user $body]]]]]]
            

**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:**

              [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]]]
            

**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:

              [?let [= $c [?http-client target='https://api.example.com'
                                    timeout=30s
                                    [resilience [?retry max=3]]]]
             [?pipe $c [$get _ '/users/42']]]
            

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

              [?let [= $ch    [?channel name='jobs' buffer=8]]
              [= $sent1 [?send 1 to=$ch]]
              [= $sent2 [?send 2 to=$ch]]
           ([?receive from=$ch], [?receive from=$ch])]
            
              [?let [= $ch [?channel name='jobs' buffer=100]]
             ([?worker name='producer'
                [body [?for [in $i [$range 1 100]]
                        [yield [?send $i to=$ch]]]]],
              [?worker name='consumer'
                [body [?for [in $i [$range 1 100]]
                        [yield [$process [?receive from=$ch]]]]]])]
            

**Channel semantics.** `[?channel :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:

              [?let [= $w [?worker name='sync-job' [body [$sync-database]]]]
             ([?wait-for worker=$w],                  [# block until done #]
              [?cancel worker=$w],                    [# cooperative      #]
              [?check-cancel])]                       [# polling point    #]
            

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

              [?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]]]]
            

**Shape.** `[?select]` clauses are clause-children: `[case [from CH $msg] HANDLER]` for a receive and `[case [timeout DURATION] HANDLER]` for a deadline. The spec/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:

              [?let [= $f1 [?async [+ 1 1]]]
              [= $f2 [?async [* 2 2]]]
              [= $f3 [?async [$upper 'go']]]
           [?await-all $f1 $f2 $f3]]
            

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

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

**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 stable in V. Tier-2 binding parity (Python `asyncio`, Go `chan`, Rust `tokio`) ships per [`spec/v0_8_0_status.md`](../../spec/v0_8_0_status.md) gate 28.

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/code.md`](../../spec/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)`.

              [?retry max=5 backoff=exponential delay=200ms jitter=equal
           [on [?fn ($e) [= $e@code 'CXER0163']]]        [# only on timeout #]
           [?pipe $client [$get _ '/users/42']]]
            

[?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
              [?timeout 5s
             [$slow-database-query $q]
             [on-timeout [result status=err code='CXER0141' reason='db slow']]]
            

[?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
              [?circuit-breaker
             threshold=0.5 window=60s reset=30s min-samples=20
             name='users-api'
             [?pipe $client [$get _ '/users/42']]]
            

[?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
              [?fallback
             [?pipe $client [$get _ '/users/42']]
             [recover-with [$load-from-cache 42]]]
            

**`$err` in `[recover-with …]`.** 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
              [?rate-limit max=100 per=1s name='github-api'
             [?pipe $client [$get _ '/repos/cx-home/cx']]]
            

[?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
              [?bulkhead max-concurrent=10 queue=50 name='db-pool'
             [$query-database $q]]
            

Composition

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

              [?retry max=3
            [?timeout 10s
              [?circuit-breaker threshold=0.5 window=60s reset=30s
                [?http-client target='https://api.example.com' method='get']]]]
            

Reading: retry up to 3 times a 10-second-bounded call gated by a 50%-failure circuit-breaker. If the breaker opens, `[?retry]` sees `CXER0150` and decides whether to keep retrying (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=true]`) 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).

              [shop
             [user [name 'Ada']]
             [item active=true 'i1']
             [pizza [name 'Margherita'] [price 12]]]
           [?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]]]
            

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.

              [?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]]
            

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.

Eight worked examples (§W1–§W8) each have a paired conformance fixture `program-slice-wN-*` in [`conformance/code.txt`](../../conformance/code.txt); 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 B15 closure (`spec/cxpath.md` §2.2) — bare-fn-call, path predicate, and slice postfix coexist.

                  [# W1 — strided range, then slice, then comprehension #]
             [?def $odds [$range 1 99 2]]
             [?def $first-ten $odds[:10]]
             [?def $squares [?for [in $x $first-ten] [yield [* $x $x]]]]

             [# W5 — infinite range with [take …] #]
             [?for [in $x [$range 1 *]] [where [$prime? $x]] [take 100] [yield $x]]

             [# W7 — map comprehension over CXPath source #]
             [?for [in $u //user] [yield-map [$u@id $u@email]]]
                

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]` or the explicit `[?to-iterator]` directive. 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/grammar.ebnf`](../../spec/grammar.ebnf) productions [130]-[135] (path / predicate base) and [159]-[160] (general PredicateExpr + `:bind` step modifier). XPath 3.1 alignment and divergences are tabulated in [`spec/cxpath_alignment.md`](../../spec/cxpath_alignment.md). CXPath is a first-class value kind; general predicates with `$_` / `$_position` / `$_last`.

Quick reference

              [; Every user in the document. ]
           //user

           [; Every user whose @active attribute equals true. ]
           //user[= $_@active true]

           [; Email addresses of every active user. ]
           //user[= $_@active true]/@email

           [; The third user in document order. ]
           //user[3]

           [; Anything two levels deep under root. ]
           /*/*

           [; Every sibling that follows the first user. ]
           //user[1]/following-sibling::user

           [; Every ancestor element of a [para] node. ]
           //para/ancestor::*

           [; General predicate — prefix code over the $_ context binding. ]
           //user[and [>= $_@age 18] $_@active]

           [; Range and set combinators. ]
           [$range 1 10]
           [union //a //b]
           [intersect //x //y]
           [except //all //banned]
            

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

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:

              [org
             [team [member role='lead' [name 'a']] [member [name 'b']] [member [name 'c']]]
             [team [member role='lead' [name 'e']]]]
           [?for [in $t //team]
                 [in $m $t/member]
                 [where [and [= $m@role 'lead'] [>= [$count $t/member] 3]]]
             [yield $m/name]]
            

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

In the host bindings

Every Tier-1 binding exposes CXPath identically through its Layer-1 surface — same selector, same bytes returned across the C ABI.

              emails = doc.select_all("//user[= $_@active true]/@email")
            
              emails, _ := doc.SelectAll("//user[= $_@active true]/@email")
            
              let emails = doc.select_all("//user[= $_@active true]/@email")?;
            
              emails := doc.select_all('//user[= $_@active true]/@email')!
            

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 full normative table lives in [`spec/errors.md`](../../spec/errors.md).

CXER001x — security caps

- `CXER0010` — function call depth exceeded. - `CXER0011` — collection size cap reached (`sequence_len`, `map_entries`, `capture_size`). - `CXER0014` — eval-time include attempted without `--include-root`.

CXER004x — evaluator gates

- `CXER0040` — purity gate: `SideEffect` / `ReadOnly` filter raised under `[?cx pure-only]`. - `CXER0041` — module gate: `on_declaration` module called without `use-module`. - `CXER0042` — eval gate: `cx:eval` invoked without `[?cx allow-eval]`. - `CXER0043` — eval depth gate: `cx:eval` recursion exceeded `max-eval-depth`.

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]

              [?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]]