4. Surface Grammar and Statement Forms
4.1 Scope
Scope:
- top-level and block-level statement forms
- the atomic statement set
- local binding and assignment
- constructor/init surface boundaries
markers(...),stream(...), anddata_schema(...)
Boundary:
- These forms are the source authoring surface.
- Canonical lowering targets and runtime truth remain defined by Chapters 5-9.
- Historical design notes are not authoring entrypoints.
4.2 Source Context and Statement Matrix
| Authoring context | Statement unit | Source shape (summary) | Key components | Result / effect |
|---|---|---|---|---|
| top-level source file | source_include_decl | include "path.culs"; | path | source file dependency declaration |
| top-level source file | library_import_decl | import Module; | module name | importable library module binding |
| top-level source file | protocol_decl | protocol name(...) returns (...) { ... } | protocol name, params, optional named returns, body | callable protocol definition |
| top-level entry source | script_stmt | any executable statement form valid in a protocol body | statement-specific components | script entry statement for the current run |
| protocol body direct form | protocol_ref_stmt | Module.Protocol(arg0, k = v, ...); | module name, protocol name, positional/named args | plan-time protocol expansion |
| protocol body direct form | include_stmt | include ProtocolName; | protocol name | include visible protocol body |
| protocol body direct form | let_stmt | let x = expr; | local name, expression | establish local binding |
| protocol body direct form | return_stmt | return expr; / return out = expr, ...; | return expression or named return bindings | protocol return contract; runtime projection is defined in Chapter 9 |
| protocol body direct form | assign_stmt | x = expr; / data.result.field = expr; | existing local or limited member path, expression | update local binding |
| protocol body direct form | step_call | op_name(k = v, ...); | operator name, args | invoke operator surface |
| protocol body direct form | markers_decl | let p = markers([CD3, CD19]); | marker list | declare controlled marker set |
| protocol body direct form | stream_call | let s = stream(sample = x, unit = single_cell, [panel = p]); | sample, unit, optional panel | create unit stream |
| protocol body direct form | data_schema_decl | let s = data_schema(label = "X", fields = [...]); | label, fields | declare structured result schema |
| protocol body direct form | container_ctor_expr | let c = tube(..., [load = [content_spec:Q, ...]]); | container family, named args, optional load | initialize container binding |
| protocol body direct form | mutation_stmt | target << [sources ...]; | target, sources | container material mutation / collection |
| protocol body block / scope form | with_env_stmt | with env(...) { [hold(target_expr);] ... } | env args, optional hold(...) target declarations, body | environment-scoped block |
| protocol body block / scope form | with_constraint_stmt | with constraint(...) { ... } / stmt with constraint(...); | requirements/options, body | execution-requirement scope |
| protocol body control form | repeat_stmt | repeat expr { ... } | statically decidable count or schedule(...) | repeated execution |
| protocol body control form | if_stmt | if expr { ... } else { ... } | statically decidable condition | branch execution |
| protocol body control form | continue_stmt | continue; | none | continue nearest repeat iteration |
| protocol body control form | break_stmt | break; | none | break nearest repeat |
Notes:
- General
step_call_with_runspecis not part of the source surface contract. mutation_stmt (<<)is its own source form; it is not authored through legacyTransfer(...)/Mix(...)calls.sep/fracretainprogram = ...as their mechanism-bearing field.img/ecp/phyusequantity = ...and optionalschema_ref.assign_stmtupdates protocol-local bindings only; it does not mutate material state.- In this table,
top-level source filemeans declarations outside any protocol body. top-level entry sourceexecutable 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.
4.3 Atomic Statement Set
The atomic statement set is:
mutation_stmt (<<)agitsepfracimgecpphywith envstreammarkersdata_schemaconstraint
Contract notes:
mutation_stmt (<<)is a statement-first material action.agitis a statement-level operator with its own argument constraints.sep/fracare program-bearing operators.img/ecp/phyare quantity-bearing readout operators.constraintis available both as block form (with constraint(...) { ... }) and as trailing sugar over a single simple statement.- Canonical env target declaration is positional
hold(target_expr)insidewith env(...); legacyhold(sample = ...)is a compatibility spelling, not preferred authoring.
4.4 Local Binding and Assignment
Forms:
let x = expr;x = expr;
Rules:
letcreates a new protocol-local binding.assignupdates an existing protocol-local binding.assigndoes not create new names.assigndoes not mutate the material store.
Assignment surface:
- scalar/text/bool/quantity locals
- limited member assignment paths under
data_ref.result.*anddata_group_ref.result.*
Excluded assignment forms:
- direct reassignment of
container_ref/ group handles - direct reassignment of
data_ref/data_group_ref - index assignment such as
x[i] = ... - compound assignment sugar such as
+=
4.5 Constructor and Init Boundary
4.5.1 Container Families
Source-level constructor families:
container(...)tube(...)well(...)chamber(...)surface(...)plate(...)
Examples:
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 = "96well", carrier_id = "PlateA");Container-side args may include:
kindspeccarrier_kindcarrier_idcarrier_positioncapacityopenlabelbarcodeload
Rules:
surface(...)must not provide volumecapacity.plate(...)is a descriptor surface for plate/selector authoring; it is not the same as a simple volume-bearing container allocation.- When
plate(...)declarescapacity, that value is the per-well capacity inherited by every concretewellsynthesized from a selector on that plate. It is not a total capacity for the plate. load = [...]is only valid in constructor/init position.- Every load item is
content_spec:Q[Volume|Mass|Count];cellsis the count unit for cellular content.
Example with independent cellular count and carrier volume:
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:
container_expr.contents
container_expr.structure.top
container_expr.structure.bottom
container_expr.structure.sidewallRules:
container_expr.contentsselects the material contents carried by a logical container.container_expr.structure.topselects 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.container_expr.structure.bottomselects the bottom structural facet of the container.container_expr.structure.sidewallselects the sidewall structural facet of the container.container_expr.structureby itself is a target-view namespace, not a complete target view.- The structure-facet member set is closed:
top,bottom, andsidewall. - These accessors are part of the container expression/type system, not a
hold(...)-only spelling rule. - The accessor is read-only and creates no new container, content identity, partition, region, or device component.
- A target-view expression may be bound with
letand passed to parameters that explicitly acceptContainerTargetVieworHoldTarget. - Target-view expressions are not material containers. They must not be used as mutation sources/targets or as
sampleinputs to ordinary material/readout operators unless that operator contract explicitly accepts the view type. - Grouped target views are expressed by grouping the view expressions, for example
group([tube_a.structure.top, tube_b.structure.top]);group_ref.structureis not a broadcast form. - No deeper structure path is defined. Forms such as
tube.structure.top.inner,tube.lid, andtube.boundaryare not container target-view accessors.
4.5.2 Content-Spec Families
Source-level content-spec families:
content(...)blood(...)reagent(...)buffer(...)
Typical usage:
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:
content_specis primarily authored inside container constructorload = [...].- Standalone
content(...),blood(...),reagent(...), andbuffer(...)are not part of the execution-flow authoring contract. - Examples may use family constructors for ergonomic compatibility, but canonical taxonomy examples should use
content(kind = ..., type = ..., attrs = ...)when the exact canonicalkind/typematters. - Lowering targets such as
AllocContainer(...),DefineContent(...), andLoadContent(...)are kernel-facing continuity forms, not preferred protocol-body authoring forms.
4.6 markers, stream, and data_schema
4.6.1 markers
Form:
let panel = markers([CD3, CD19]);Rules:
markers(...)declares a controlled marker set.- Marker names are not hardcoded by the core grammar.
- Downstream marker-aware authoring should only refer to previously declared marker names.
4.6.2 stream
Forms:
let cells = stream(sample = sample_tube, unit = single_cell, panel = panel);
let mols = stream(sample = prepared_tube, unit = molecule);Rules:
sampleis a container-facing input handle.unitnames the emitted unit kind.panelis optional and associates a marker panel with the output stream.stream(...)returns aunit_stream_ref-like surface handle.
4.6.3 data_schema
Form:
let obs_schema = data_schema(label = "GelObservation", fields = [...]);Rules:
data_schema(...)declares structured readout/result shape.img/ecp/phymay reference that schema throughschema_refwhen the operator contract allows it.
4.7 Authoring Guidance
Examples should:
- prefer the source-level shapes in this chapter
- avoid exposing internal lowering targets unless explicitly marked as kernel-internal
- use
.culssource files - 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.
