Skip to content

4. Surface Grammar and Statement Forms ​

4.1 Scope ​

Scope:

  1. top-level and block-level statement forms
  2. the atomic statement set
  3. local binding and assignment
  4. constructor/init surface boundaries
  5. markers(...), stream(...), and data_schema(...)

Boundary:

  1. These forms are the source authoring surface.
  2. Canonical lowering targets and runtime truth remain defined by Chapters 5-9.
  3. Historical design notes are not authoring entrypoints.

4.2 Source Context and Statement Matrix ​

Authoring contextStatement unitSource shape (summary)Key componentsResult / effect
top-level source filesource_include_declinclude "path.culs";pathsource file dependency declaration
top-level source filelibrary_import_declimport Module;module nameimportable library module binding
top-level source fileprotocol_declprotocol name(...) { ... [return ...;] }protocol name, params, body with optional tail returncallable protocol definition
top-level entry sourcescript_stmtany executable statement form valid in a protocol bodystatement-specific componentsscript entry statement for the current run
protocol body direct formprotocol_ref_stmtModule.Protocol(arg0, k = v, ...);module name, protocol name, positional/named argsplan-time protocol expansion
protocol body direct forminclude_stmtinclude ProtocolName;protocol nameinclude visible protocol body
protocol body direct formlet_stmtlet x = expr;local name, expressionestablish local binding
protocol body direct formreturn_stmtreturn expr; / return out = expr, ...;return expression or named return bindingsprotocol return contract; runtime projection is defined in Chapter 9
protocol body direct formassign_stmtx = expr; / data.result.field = expr;existing local or limited member path, expressionupdate local binding
protocol body direct formstep_callop_name(k = v, ...);operator name, argsinvoke operator surface
protocol body direct formmarkers_decllet p = markers([CD3, CD19]);marker listdeclare controlled marker set
protocol body direct formstream_calllet s = stream(sample = x, unit = single_cell, [panel = p]);sample, unit, optional panelcreate unit stream
protocol body direct formdata_schema_decllet s = data_schema(label = "X", fields = [...]);label, fieldsdeclare structured result schema
protocol body direct formcontainer_ctor_exprlet c = tube(..., [load = [content_spec:Q, ...]]);container family, named args, optional loadinitialize container binding
protocol body direct formmutation_stmttarget << [sources ...];target, sourcescontainer material mutation / collection
protocol body block / scope formwith_env_stmtwith env(...) { [hold(target_expr);] ... }env args, optional hold(...) target declarations, bodyenvironment-scoped block
protocol body block / scope formwith_constraint_stmtwith constraint(...) { ... } / stmt with constraint(...);requirements/options, bodyexecution-requirement scope
protocol body control formrepeat_stmtrepeat expr { ... }statically decidable count or schedule(...)repeated execution
protocol body control formif_stmtif expr { ... } else { ... }statically decidable conditionbranch execution
protocol body control formcontinue_stmtcontinue;nonecontinue nearest repeat iteration
protocol body control formbreak_stmtbreak;nonebreak nearest repeat

Notes:

  1. General step_call_with_runspec is not part of the source surface contract.
  2. mutation_stmt (<<) is its own source form; it is not authored through legacy Transfer(...) / Mix(...) calls.
  3. sep/frac retain program = ... as their mechanism-bearing field.
  4. img/ecp/phy use quantity = ... and optional schema_ref.
  5. assign_stmt updates protocol-local bindings only; it does not mutate material state.
  6. In this table, top-level source file means declarations outside any protocol body.
  7. top-level entry source executable statements form the script entry for the current run; top-level executable statements parsed from dependency files are omitted from that entry and are not executed.

For repeat ... in schedule(...), mode has the closed type ScheduleMode: ScheduleMode.DISCRETE or ScheduleMode.CONTINUOUS. Omission selects DISCRETE. The lowercase tokens and strings remain compatible forms. Selecting an enum member does not change the existing discrete-point or continuous-window rules; mode and dependent schedule arguments must be validated together after binding.

4.3 Atomic Statement Set ​

The atomic statement set is:

  1. mutation_stmt (<<)
  2. agit
  3. sep
  4. frac
  5. img
  6. ecp
  7. phy
  8. with env
  9. stream
  10. markers
  11. data_schema
  12. constraint

Contract notes:

  1. mutation_stmt (<<) is a statement-first material action.
  2. agit is a statement-level operator with its own argument constraints.
  3. sep/frac are program-bearing operators.
  4. img/ecp/phy are quantity-bearing readout operators.
  5. constraint is available both as block form (with constraint(...) { ... }) and as trailing sugar over a single simple statement. Standard requirements may use qualified members such as ConstraintRequirement.GENTLE; existing bare names retain the same meaning. Requirement items remain static declarations, not dynamically evaluated variable expressions. Installed domain-specific extension members must be referenced by their qualified names and declare their applicable operations, scope and conflicts. Custom context predicates are not supported in this baseline and must not be accepted as executable restrictions. Their wire spelling alone is not an extension declaration. Existing scope, conflict and customized/schema checks apply unchanged. Stored extension requirements retain stable domain identity and version; execution requires explicit driver support and must reject unknown or unsupported identities before performing the action. Requirement meanings, applicable actions, usage scenarios and compatibility checks are defined in the Constraint Contract.
  6. Canonical env target declaration is positional hold(target_expr) inside with env(...); legacy hold(sample = ...) is a compatibility spelling, not preferred authoring.

4.4 Local Binding and Assignment ​

Forms:

  1. let x = expr;
  2. x = expr;

Rules:

  1. let creates a new protocol-local binding.
  2. assign updates an existing protocol-local binding.
  3. assign does not create new names.
  4. assign does not mutate the material store.

Assignment surface:

  1. scalar/text/bool/quantity locals
  2. limited member assignment paths under data_ref.result.* and data_group_ref.result.*

Excluded assignment forms:

  1. direct reassignment of container_ref / group handles
  2. direct reassignment of data_ref / data_group_ref
  3. index assignment such as x[i] = ...
  4. compound assignment sugar such as +=

4.5 Constructor and Init Boundary ​

4.5.1 Container Families ​

Source-level constructor families:

  1. container(...)
  2. tube(...)
  3. well(...)
  4. chamber(...)
  5. surface(...)
  6. plate(...)

Examples:

culs
let t = tube(label = "DNA", capacity = 1.5mL);
let w = well(label = "PCR_A1", capacity = 50uL, carrier_id = "PCRPlate1", carrier_position = "A1");
let p = plate(label = "PlateA", format = PlateFormat.WELL_96, carrier_id = "PlateA");

Container-side args may include:

  1. kind
  2. spec
  3. carrier_kind
  4. carrier_id
  5. carrier_position
  6. capacity
  7. open
  8. label
  9. barcode
  10. load

Rules:

  1. surface(...) must not provide volume capacity.
  2. plate(...) declares a plate carrier, distinct from a volume-bearing container and from an ordered group of wells. Each executing declaration establishes its own carrier identity, including declarations in separate protocol calls or allocating iterations. Aliases, protocol arguments and returned carrier values preserve that identity; equal labels or external identifiers do not merge carriers. Identity does not require a layout or display label to be known at declaration time. Its standard format type is PlateFormat, with WELL_6, WELL_12, WELL_24, WELL_48, WELL_96, and WELL_384 corresponding respectively to 2×3, 3×4, 4×6, 6×8, 8×12, and 16×24 layouts. Existing strings such as "96well" remain compatible. Explicit custom rows/cols remain separate from the standard format vocabulary. Actual protocol arguments are bound before selector bounds and per-well capacity are determined. Explicit arguments take precedence over parameter defaults. Logical coordinates may be retained before binding, but well allocation requires a valid, fully determined layout; an out-of-bounds selector prevents an executable plan.
  3. When plate(...) declares capacity, that value is the per-well capacity inherited by every concrete well synthesized from a selector on that plate. It is not a total capacity for the plate.
  4. load = [...] is only valid in constructor/init position.
  5. Every load item is content_spec:Q[Volume|Mass|Count]; cells is the count unit for cellular content.

Example with independent cellular count and carrier volume:

culs
let cells = tube(load = [
  content(kind = bio_cellular, type = cell_line, code = "RPE1"): 100000cells,
  content(kind = formulation, type = medium, code = "DMEM_F12"): 300uL
]);

After all authored load items are applied, runtime finalizes the container's material relationships. Explicit carrier volume is preferred. A count-only free cellular population uses the reference runtime's recorded default concentration policy to materialize an implicit carrier; adherent or otherwise immobilized cells retain count without an implicit carrier.

4.5.1.1 Container Target-View Accessors ​

Container expressions expose a narrow dot-accessor surface for target views:

culs
container_expr.contents
container_expr.structure.top
container_expr.structure.bottom
container_expr.structure.sidewall

Rules:

  1. container_expr.contents selects the material contents carried by a logical container.
  2. container_expr.structure.top selects the top structural facet of the container. It is closure-agnostic: on a closed vessel it may correspond to the cap/seal-facing structure; on an open vessel it corresponds to the upper rim/top structural facet, not air or liquid surface.
  3. container_expr.structure.bottom selects the bottom structural facet of the container.
  4. container_expr.structure.sidewall selects the sidewall structural facet of the container.
  5. container_expr.structure by itself is a target-view namespace, not a complete target view.
  6. The structure-facet member set is closed: top, bottom, and sidewall.
  7. These accessors are part of the container expression/type system, not a hold(...)-only spelling rule.
  8. The accessor is read-only and creates no new container, content identity, partition, region, or device component.
  9. A target-view expression may be bound with let and passed to parameters that explicitly accept ContainerTargetView or HoldTarget.
  10. Target-view expressions are not material containers. They must not be used as mutation sources/targets or as sample inputs to ordinary material/readout operators unless that operator contract explicitly accepts the view type.
  11. Grouped target views are expressed by grouping the view expressions, for example group([tube_a.structure.top, tube_b.structure.top]); group_ref.structure is not a broadcast form.
  12. No deeper structure path is defined. Forms such as tube.structure.top.inner, tube.lid, and tube.boundary are not container target-view accessors.

4.5.2 Content-Spec Families ​

Source-level content-spec families:

  1. content(...)
  2. blood(...)
  3. reagent(...)
  4. buffer(...)

Typical usage:

culs
let sample_tube = tube(
  label = "Sample",
  load = [blood(type = whole_blood): 200uL]
);

The content taxonomy contract for kind, type, code, name, and attrs is defined with the operator vocabulary in Chapter 6. The source forms in this section carry those fields.

4.5.3 Constructor Authoring Rules ​

Rules:

  1. content_spec describes initial material inside constructor load = [...].
  2. Standalone content(...), blood(...), reagent(...), and buffer(...) are not part of the execution-flow authoring contract.
  3. Examples may use family constructors for ergonomic compatibility, but canonical taxonomy examples should use content(kind = ..., type = ..., attrs = ...) when the exact canonical kind/type matters.
  4. Lowering targets such as AllocContainer(...), DefineContent(...), and LoadContent(...) are kernel-facing continuity forms, not preferred protocol-body authoring forms.

4.6 markers, stream, and data_schema ​

4.6.1 markers ​

Form:

culs
let panel = markers([CD3, CD19]);

Rules:

  1. markers(...) declares a controlled marker set.
  2. Marker names are not hardcoded by the core grammar.
  3. Downstream marker-aware authoring should only refer to previously declared marker names.

4.6.2 stream ​

Forms:

culs
let cells = stream(sample = sample_tube, unit = single_cell, panel = panel);
let mols = stream(sample = prepared_tube, unit = molecule);

Rules:

  1. sample is a container-facing input handle.
  2. unit names the emitted observation unit kind. The standard ObservationUnit.SINGLE_CELL member accepts the legacy single_cell token or string as a compatibility spelling. Other historical text remains compatibility metadata and does not declare an installed extension. Extension packages may install domain-specific observation unit types; source expressions reference their members using ordinary binding rules. A member from another domain is invalid. Installed units retain stable domain identity, version and member names across planning and storage, independent of implementation class paths. Unknown stored extension identities fail restoration without loading code. Declaring a unit does not confer single-cell interpretation or new measurement capabilities; observation contents and panel association retain their existing semantics.
  3. panel is optional and associates a marker panel with the output stream.
  4. stream(...) returns a unit_stream_ref-like surface handle.

4.6.3 data_schema ​

Form:

culs
let obs_schema = data_schema(label = "GelObservation", fields = [...]);

Rules:

  1. data_schema(...) declares structured readout/result shape.
  2. img/ecp/phy may reference that schema through schema_ref when the operator contract allows it.

4.7 Authoring Guidance ​

Examples should:

  1. prefer the source-level shapes in this chapter
  2. avoid exposing internal lowering targets unless explicitly marked as kernel-internal
  3. use .culs source files
  4. treat Chapter 6 as the detailed contract layer for operator args and return policy

4.8 Conformance Boundary ​

Parser implementations, parser tests, and examples must conform to these source forms; they do not define separate grammar behavior.

Lowering, validation, execution, and runtime consequences are specified in Chapters 5-9.

Culsma Language Reference · CC BY 4.0