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(...) returns (...) { ... }protocol name, params, optional named returns, bodycallable 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.

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.
  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 = "96well", 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(...) is a descriptor surface for plate/selector authoring; it is not the same as a simple volume-bearing container allocation.
  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 is primarily authored inside container 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 unit kind.
  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.

Public language reference for the current Culsma surface.