7 Input structure
This chapter defines statement execution, line patterns, repeats, sections,
logical records, conditional execution, and let. Chapter 5
and the formal grammar define syntax;
chapter 4 defines scanning and input positions;
chapter 6 defines reading types and value reads.
Examples use chapter 2's transport and complete canonical
JSON documents under chapter 3, with schemas for
data and errors.
7.1 Statement execution and frames
Implementations MUST execute statements in source order along the selected control-flow path. Ordinary blocks, if/else bodies, and sections bodies MUST NOT introduce value frames. Each repeat iteration MUST introduce a fresh frame. A value definition MUST define or replace its name in the current frame. Within an iteration, an enclosing value remains available until a local definition replaces that spelling. A previous iteration's local values MUST NOT carry into the next iteration.
After each iteration, implementations MUST collect its final local values as rows of columns (§7.5), then discard its frame. Looking up an enclosing value MUST NOT supply an omitted local row. On completion, the repeat MUST publish its columns in the enclosing frame. Column order follows possible body definitions, as specified in §7.5, rather than the order definitions first execute. General lookup follows §8.1; record-name resolution follows §8.12.
7.2 Line patterns and captures
Pattern construction and parsing MUST follow appendix A.4, chapter 5, and grammar.ebnf. Matching MUST be byte-exact, case-sensitive, deterministic, and without backtracking. It MUST start at the remaining physical line after normal skipping. The matching text excludes LF and trailing line blanks. A successful selected match MUST consume through the physical line's end, including trailing blanks, leaving LF for subsequent normal skipping. A failed candidate MUST NOT commit captures or consume its line text; initial skipping has already occurred.
| Form | Required effect |
|---|---|
{x:t} |
Convert using reading type t; define scalar x when the complete match is selected. Named types follow chapter 6. |
{x} |
Read a word capture named x. |
{:int} |
Convert and validate an integer without defining an entry. Other permitted reading types can likewise be unnamed. |
{_} |
Convert a word and discard it. A typed _ capture MUST also validate and discard. |
{$x} |
Match the current integer scalar's decimal spelling or the current string scalar's exact bytes. This is a reference, not a capture; it MUST NOT define x. |
Every capture MUST be nonempty. Numeric conversion follows §§4.4–4.5; reading types follow §6.1. Capture boundaries MUST instead follow these rules:
- A word ends at a line blank or at the first byte of an immediately following literal item. Without such a literal, NUL is also a stopping byte. Matching MUST NOT try a later delimiter if the remaining literal fails at the first occurrence.
- A rest capture consumes the remaining matching text. It cannot leave text for a following nonempty literal.
- Numeric captures scan an optional leading
+or-, then the maximal run of ASCII digits forintand ranges. Forfloatandnum, the run also admits.,e,E, and a sign immediately aftereorE. Complete conversion of that candidate is required. Matching MUST NOT shorten it to accommodate a following literal. tokensandseparatorsaffect initial skipping, not interior capture boundaries. Vertical tab and form feed MAY occur inside a word capture.- Captures MUST NOT perform a declaration's default offset addition.
In particular,
floatandnumcaptures MUST retain negative zero.
Each run of pattern blanks MUST consume the maximal run of input line
blanks, possibly empty. A blank is required when both neighboring items
are word-like.
Captures and references are word-like. A literal is word-like on the
relevant side when its boundary byte is an ASCII letter, digit, or
underscore. Reaching the end of matching text satisfies a whitespace item,
but remaining items MUST still match. Thus A : {n:int} accepts A:7,
while a b rejects ab. Vertical tab and form feed MUST NOT satisfy pattern
whitespace. A terminal ... MUST accept any remaining text, including no
text; its source syntax remains appendix A.4's rule.
Implementations MUST select and commit a match in this order:
- Match literals, whitespace, references, and converted captures without defining captured names or checking capture ranges.
- After selecting a complete match, apply §4.10's UTF-8 checks to word/rest captures, including unnamed and discarded captures.
- Define all named, nondiscarded captures in capture order.
- Evaluate and check capture ranges in capture order. A range MAY use a capture later in the pattern. Named ranges use their saved bounds.
- Consume the line.
Conversion failure MUST mean that the candidate does not match. A selected
capture's invalid bounds or rejected range MUST be a hard error: find
MUST NOT continue searching and sections MUST NOT try another arm.
References MUST use values available during matching, before any capture
in that candidate is committed. An undefined reference MUST report
E-NAME-UNDEFINED when reached. A float, array, or other unacceptable
reference MUST report E-PATTERN-REFERENCE. An empty current string MUST
NOT match, even as an empty substitution.
The next examples show typed, default word, unnamed, discarded, and rest captures; float and num conversion; a named range; and signed zero.
id: structure-capturesoperation: read--- specline "v={x:int} {w} {:int} {_} {r:rest}"--- inputv=7 hi 8 gone tail words--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 7}, {"name": "w", "type": "string", "depth": 0, "value": "hi"}, {"name": "r", "type": "string", "depth": 0, "value": "tail words"}]}id: structure-capture-typesoperation: read--- specline "{a:float},{b:num},{c:word}!"--- input1.2345678901234567,2.5,yes!--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "float", "depth": 0, "value": 1.2345678901234567}, {"name": "b", "type": "float", "depth": 0, "value": 2.5}, {"name": "c", "type": "string", "depth": 0, "value": "yes"}]}id: structure-capture-named-typeoperation: read--- spectype t=1..3 line "{x:t}"--- input2--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 2}]}id: structure-capture-signed-zerooperation: read--- specline "{x:float} {y:num}"--- input-0.0 -0.0--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "float", "depth": 0, "value": -0.0}, {"name": "y", "type": "float", "depth": 0, "value": -0.0}]}References substitute existing values. The whitespace example combines
optional spacing beside punctuation, required word spacing, and ....
The range example uses a later capture as a bound. The find example
rejects an incomplete candidate before checking its captured range.
id: structure-pattern-referenceoperation: read--- speclet x=7 let s="hi" line "{$s}:{$x} {y:int}"--- inputhi:7 9--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 7}, {"name": "s", "type": "string", "depth": 0, "value": "hi"}, {"name": "y", "type": "int", "depth": 0, "value": 9}]}id: structure-pattern-whitespaceoperation: read--- specline "A : {n:int} x {m:int} ..."--- inputA:2 x 3more--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 0, "value": 2}, {"name": "m", "type": "int", "depth": 0, "value": 3}]}id: structure-pattern-range-peersoperation: read--- specline "{x:0..n} {n:int}"--- input2 3--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 2}, {"name": "n", "type": "int", "depth": 0, "value": 3}]}id: structure-find-speculationoperation: read--- specfind "A {x:1..2} Z"--- inputA 9 NOA 2 Z--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 2}]}An unclosed capture fails during parsing, before any input is read.
id: structure-pattern-unclosed-captureoperation: parse--- specline "{x:int"--- expected error{"code": "E-SPEC-PATTERN", "message": "unterminated capture in pattern \"{x:int\"", "input_location": null, "spec_location": {"origin": "spec.iris", "line": 1, "column": null}, "context": null, "record_path": null}Compiling a pattern MUST first check its whole structure: capture
delimiters, capture names, {$x} reference names, and the position of
.... Only then MUST it lex and parse each capture's reading type, in
capture order. A structural failure anywhere in the pattern is therefore
reported before a type failure in an earlier capture. Here the second
capture's name is rejected although the first capture's type is invalid.
id: structure-pattern-structure-before-typesoperation: parse--- specline "{x:@}{bad name}"--- expected error{"code": "E-SPEC-PATTERN", "message": "capture name 'bad name' in pattern \"{x:@}{bad name}\" is not a name", "input_location": null, "spec_location": {"origin": "spec.iris", "line": 1, "column": null}, "context": null, "record_path": null}7.3 Line, find, skip, and expect
line MUST require the rest of the current line, after normal skipping,
to match its pattern (§7.2). If that line is exhausted, normal skipping
seeks subsequent data, passing blank and comment lines. A final LF is
not required. Mismatch or absent input MUST report E-LINE-MATCH.
The examples show a current-line remainder, a subsequent line, and input
without a final LF.
id: structure-line-current-and-nextoperation: read--- specn:int line "X {x:int}" line "Y {y:int}"--- input1 X 2
Y 3--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 0, "value": 1}, {"name": "x", "type": "int", "depth": 0, "value": 2}, {"name": "y", "type": "int", "depth": 0, "value": 3}]}id: structure-line-final-lfoperation: read--- specline "A {x:int}"--- input no-final-newlineA 7--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 7}]}find MUST try the current remaining line first, then discard each
unsuccessful candidate line until one matches. It MUST consume the matching
line too. If no remaining line matches, it MUST report E-FIND-MATCH.
Reference errors and committed-capture errors remain hard errors (§7.2).
id: structure-findoperation: read--- specn:int find "X {x:int}" y:int--- input1 noisenoX 23--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 0, "value": 1}, {"name": "x", "type": "int", "depth": 0, "value": 2}, {"name": "y", "type": "int", "depth": 0, "value": 3}]}skip line MUST act as skip 1 lines. skip e lines MUST evaluate its
count once as an integer scalar in 0..2147483647, otherwise reporting
E-COUNT. Each requested skip MUST first perform normal skipping, then
discard the remaining physical line. Blank and comment lines passed during
normal skipping MUST NOT count separately; a partially consumed line MUST
count. Zero skips MUST consume nothing. Missing requested data MUST report
E-SKIP-EOF. These examples show both skip forms, comments and blank lines,
a partially consumed line, and zero skips.
id: structure-skipoperation: read--- speccomment "#" n:int skip line skip 2 lines y:int--- input1 tail
# commentonetwo7--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 0, "value": 1}, {"name": "y", "type": "int", "depth": 0, "value": 7}]}id: structure-skip-zerooperation: read--- specskip 0 lines n:int--- input7--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 0, "value": 7}]}expect MUST consume its literal strings sequentially as exact ordinary
tokens, with normal skipping before each. Its strings are a sequence;
repeat and sections terminating strings are alternatives. Token and
separator settings MUST apply. Mismatch MUST report E-EXPECTED-TOKEN at
the offending token's beginning; absent input MUST report the current EOF
position. expect MUST NOT create entries. Here tokens makes the colon
an ordinary token between the two words.
id: structure-expectoperation: read--- spectokens ":" expect "A" ":" "B" x:int--- inputA:B7--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 7}]}7.4 Repeat forms and termination
A counted repeat MUST evaluate its count once, before the first iteration,
as an integer scalar in 0..2147483647; otherwise it MUST report E-COUNT.
Reassigning the count's name in the body MUST NOT change the admitted
count. Zero MUST execute no body but MUST still construct empty columns
for its possible definitions (§7.5). The counted example redefines n in
each iteration and shows that only its final local value is collected.
The zero example shows declaration, array, list, and let fallbacks.
id: structure-repeat-countoperation: read--- speclet n=2 repeat n {index 1 x:int let n=1} y:int--- input1 7 2 8 9--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 1, "value": [1, 1]}, {"name": "x", "type": "int", "depth": 1, "value": [7, 8]}, {"name": "y", "type": "int", "depth": 0, "value": 9}]}id: structure-repeat-zerooperation: read--- specrepeat 0 {x:int a[2]:float list b:word until eol let c="s"}--- input--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 1, "value": []}, {"name": "a", "type": "float", "depth": 2, "value": []}, {"name": "b", "type": "string", "depth": 2, "value": []}, {"name": "c", "type": "int", "depth": 1, "value": []}]}repeat until with strings MUST test the next complete ordinary token
before each iteration, after normal skipping. Any listed exact token MUST
terminate the repeat and be consumed. The strings are alternatives. A
terminator encountered during body execution has no special status; the
body MAY read it as data. EOF before a required terminator MUST report
E-REPEAT-TERMINATOR.
store MUST define the matched token as a string scalar in the enclosing
frame, outside this repeat's iteration frames. It MUST store neither the
line nor an iteration count, and MUST define its name only when a token
matches. That stored value MAY become a column of an enclosing repeat.
store _ defines an entry named _ at top level; §7.5 excludes _ from
an enclosing repeat's discovered columns, so such an iteration-local entry
is discarded with its frame.
These examples show alternative terminators and store, immediate termination with empty columns, and a terminator read within a body.
id: structure-repeat-until-storeoperation: read--- specrepeat until "END" "STOP" store ending {x:int} tail:int--- input1 2 STOP 9--- expected data{"iris": "0.1", "entries": [ {"name": "ending", "type": "string", "depth": 0, "value": "STOP"}, {"name": "x", "type": "int", "depth": 1, "value": [1, 2]}, {"name": "tail", "type": "int", "depth": 0, "value": 9}]}id: structure-repeat-immediateoperation: read--- specrepeat until "END" store ending {x:int}--- inputEND--- expected data{"iris": "0.1", "entries": [ {"name": "ending", "type": "string", "depth": 0, "value": "END"}, {"name": "x", "type": "int", "depth": 1, "value": []}]}id: structure-repeat-body-terminatoroperation: read--- specrepeat until "END" {a:word b:word}--- inputx END END--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "string", "depth": 1, "value": ["x"]}, {"name": "b", "type": "string", "depth": 1, "value": ["END"]}]}repeat until eof MUST perform normal skipping before each iteration and
end when no data remains, including after applicable comment/stop handling.
id: structure-repeat-eofoperation: read--- specrepeat until eof {x:int}--- input1 2--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 1, "value": [1, 2]}]}An indefinite repeat body MUST advance the input cursor on each iteration.
Otherwise it MUST report E-NO-PROGRESS before column collection. Counted
repeats MAY consume no input, subject to
§10.2. Their idle charge MUST occur after
body execution and before column collection. Nested repeats MUST apply
these rules independently and add array levels to their field columns.
7.5 Column construction and missing rows
Before executing a repeat, implementations MUST discover its possible
field names by traversing its body in source order and retaining the first
occurrence of each nondiscarded name. They MUST traverse both if branches,
all sections arms, and nested repeat bodies even when execution would not
reach them. In sections, visit its store name first, then each arm's
captures before its body definitions. A nested repeat contributes its
store name, then its body's fields with one additional depth. Discovery
MUST exclude type definitions, record aliases, _, and implementation
bookkeeping.
The first syntactic occurrence MUST supply the fallback metadata below. A first actual local value MUST instead determine the actual local depth and initial value type.
| Definition | Fallback value type and depth before this repeat's extra level |
|---|---|
| Declaration | Reading type's value type, with num represented as int; number of dimensions |
| List | Reading type's value type, with num represented as int; depth 1 |
| Capture | Reading type's value type, with num represented as int; depth 0 |
let or param |
int; depth 0 |
| Terminator store | string; depth 0 |
| Nested repeat field | Recursively determined fallback, plus one depth |
Each iteration MUST contribute its final local value for each discovered name, or a missing row. A scalar becomes one element of a depth-1 column. A depth-d local array becomes one row of a depth-(d+1) column. Implementations MUST preserve iteration order as row order and element order within each local value. Rows MAY have different lengths.
id: structure-repeat-raggedoperation: read--- specrepeat 2 {n:int a[n]:int}--- input2 4 5 1 6--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 1, "value": [2, 1]}, {"name": "a", "type": "int", "depth": 2, "value": [[4, 5], [6]]}]}A missing array-valued row MUST become an empty outer row at the established local depth. Implementations MUST NOT invent lower rows or scalar leaves. When no actual row exists, the fallback determines depth and type. These examples show missing rows before and after an actual array, and entirely missing depth-2 local arrays.
id: structure-repeat-missing-rowsoperation: read--- specrepeat 3 {k:int if k {list a:int until eol}}--- input01 4 50--- expected data{"iris": "0.1", "entries": [ {"name": "k", "type": "int", "depth": 1, "value": [0, 1, 0]}, {"name": "a", "type": "int", "depth": 2, "value": [[], [4, 5], []]}]}id: structure-repeat-all-missing-rowsoperation: read--- specrepeat 2 {k:int if k {a[2][3]:float}}--- input0 0--- expected data{"iris": "0.1", "entries": [ {"name": "k", "type": "int", "depth": 1, "value": [0, 0]}, {"name": "a", "type": "float", "depth": 3, "value": [[], []]}]}A scalar MUST NOT be missing in a nonempty repeat. If a missing row precedes
the first actual scalar, implementations MUST fail when that scalar arrives.
If a missing row follows an actual scalar, they MUST fail then. If no local
value is ever produced and the fallback is scalar, they MUST fail when
finishing the column. All three failures MUST report E-REPEAT-MISSING.
Looking up an enclosing scalar does not supply a local row (§7.1).
A zero-iteration repeat MUST produce an empty column at fallback depth plus
one, even for scalar fallbacks. Column collection and missing-row filling
add no independent idle charges.
An entirely unexecuted repeat outside another repeat MUST create nothing.
All actual local rows of a column
MUST have the same depth, including empty arrays; otherwise implementations
MUST report E-REPEAT-DEPTH.
Nonempty scalar leaves MUST determine type consistency. Integer and float
leaves MUST combine by numeric promotion using
§6.2's binary64 conversion.
Mixing nonempty string and numeric leaves MUST report E-VALUE-TYPE-MIX.
Empty arrays alone MUST NOT force promotion or introduce a string/number
conflict. If all actual values are empty, the first actual value's type
MUST be retained. If no actual value exists, the fallback MUST be used.
id: structure-repeat-promotionoperation: read--- specrepeat 2 {x:num}--- input1 2.5--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "float", "depth": 1, "value": [1, 2.5]}]}id: structure-repeat-empty-typeoperation: read--- specrepeat 2 {k:int if k {a[0]:word} else {a[1]:int}}--- input1 0 7--- expected data{"iris": "0.1", "entries": [ {"name": "k", "type": "int", "depth": 1, "value": [1, 0]}, {"name": "a", "type": "int", "depth": 2, "value": [[], [7]]}]}Columns MUST be published in discovered name order. Replacing an enclosing entry MUST retain its existing position under §3.1. A terminating store executes before column publication, so a new store entry can precede the columns. Outside repeat collection, sections definitions follow actual arm execution order (§7.8). Here the unexecuted array definition places z before a and supplies its missing-row fallback.
id: structure-repeat-orderoperation: read--- specrepeat 1 {if 0 {z[1]:word} a:int}--- input7--- expected data{"iris": "0.1", "entries": [ {"name": "z", "type": "string", "depth": 2, "value": [[]]}, {"name": "a", "type": "int", "depth": 1, "value": [7]}]}7.6 Logical records and names
Each repeat iteration MUST form one logical record. Its ordinary fields MUST be represented by the corresponding column entries (§7.5). Nested repeats contribute lists of nested records and deeper field columns. Implementations MUST retain each record's start position for later check diagnostics independently of its field values. This MUST be the position normal skipping would reach at iteration entry, including EOF when no data remains. Saving it MUST NOT itself advance the input cursor.
repeat ... as rows { each row ... } names the list of logical records
rows and each individual record row. Either name MAY be supplied
independently. each MUST consume no input; it is a structural naming
statement whose placement follows
§5.11.
A record or record-list denotation is not a value and MUST NOT itself
create a data entry. Explicit as and each names MUST NOT appear as
entries. Ordinary fields still appear as their column entries; a field
name's additional role as a record name in a check MUST NOT create another
entry. The examples show both names, a list name alone, and an individual
record name alone. The first also computes a field in the current record.
id: structure-record-namesoperation: read--- specrepeat 2 as rows {each row a:int let b=row.a+1}--- input4 5--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "int", "depth": 1, "value": [4, 5]}, {"name": "b", "type": "int", "depth": 1, "value": [5, 6]}]}id: structure-record-list-onlyoperation: read--- specrepeat 1 as rows {a:int}--- input7--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "int", "depth": 1, "value": [7]}]}id: structure-record-each-onlyoperation: read--- specrepeat 1 {each row a:int}--- input7--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "int", "depth": 1, "value": [7]}]}In the nested example, each job has n and a steps list; each step has x. The data contains n at depth 1 and x at depth 2. It contains no objects or entries named jobs, job, steps, or step. §8.12 defines field selection, record quantification, name conflicts, and scope rules; §8.10 defines comprehensions, and §8.13 defines checks.
id: structure-nested-recordsoperation: read--- specrepeat 2 as jobs {each job n:int repeat n as steps {each step x:int}}--- input2 4 5 1 6--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 1, "value": [2, 1]}, {"name": "x", "type": "int", "depth": 2, "value": [[4, 5], [6]]}]}7.7 Index statements
index b MUST read an integer token equal to the innermost active repeat's
iteration number counted from zero plus b. It MUST evaluate b on each
execution, before reading the token, as an integer scalar in
0..2147483647. It MUST consume its token and MUST NOT produce an entry.
It imposes no physical row or line boundary. The nested example uses the
innermost repeat's index; the second example reevaluates b each time.
id: structure-index-nestedoperation: read--- specrepeat 2 {index 1 repeat 2 {index 0 x:int}}--- input1 0 4 1 5 2 0 6 1 7--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 2, "value": [[4, 5], [6, 7]]}]}id: structure-index-baseoperation: read--- specrepeat 2 {b:int index b x:int}--- input1 1 7 5 6 8--- expected data{"iris": "0.1", "entries": [ {"name": "b", "type": "int", "depth": 1, "value": [1, 5]}, {"name": "x", "type": "int", "depth": 1, "value": [7, 8]}]}Executing index without an active repeat MUST report E-INDEX-CONTEXT
before evaluating b. Invalid b MUST report E-COUNT. Failed integer
conversion MUST report E-EXPECTED-VALUE. A different integer MUST report
E-INDEX-MISMATCH at the token's beginning.
7.8 Sections
Sections MUST process input with the following loop and completion rules:
- Perform normal skipping. EOF ends sections, even when
untilwas supplied. - Test terminating tokens before patterns. On a match, consume only the terminating token, store it when requested, and end sections. A same-line suffix remains available.
- Try arms in declaration order and select the first complete match. Failed candidates MUST NOT commit captures. A reached current-value reference's error MUST remain a hard error.
- If no arm matches, discard the remaining line when
skip otheris enabled; otherwise reportE-SECTIONS-UNEXPECTED. - Reject a second match of the selected arm with
E-SECTIONS-REPEATED. An optional arm is still permitted at most once. In ordinary reading, this failure MUST precede committing its second captures or executing its second body. - Commit captures and consume the matching line under §7.2, then execute that arm's body in the current frame. The body MAY read subsequent input.
- A
lastarm ends sections after its body. It MUST NOT make that arm optional or waive other required arms. - On EOF, a terminating token, or a
lastarm, check unseen required arms in declaration order. A missing arm MUST reportE-LINE-MATCH, shared withline; there is no separate missing-sections code.
An ordinary arm MUST match exactly once; optional permits zero or one
match. Arms MUST execute in input order. Sections MUST produce only its
captures, body definitions, and an actually matched terminator store.
EOF and last MUST NOT populate store. An unset store inside a repeat
can therefore cause a missing scalar (§7.5). Repeated options MUST follow
§5.7.
The first example combines alternative terminators, store, skip other,
an optional arm, and an optional last arm. B executes before A. The second
executes a required last arm. The third ends at EOF despite an until/store
option and creates no ending entry.
id: structure-sections-optionsoperation: read--- specsections until "END" "STOP" store ending skip other {"A {a:int}" "B {b:int}" optional "DONE" optional last} tail:int--- inputnoiseB 2A 1STOP 7--- expected data{"iris": "0.1", "entries": [ {"name": "b", "type": "int", "depth": 0, "value": 2}, {"name": "a", "type": "int", "depth": 0, "value": 1}, {"name": "ending", "type": "string", "depth": 0, "value": "STOP"}, {"name": "tail", "type": "int", "depth": 0, "value": 7}]}id: structure-sections-lastoperation: read--- specsections {"A {a:int}" "DONE" last} tail:int--- inputA 1DONE7--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "int", "depth": 0, "value": 1}, {"name": "tail", "type": "int", "depth": 0, "value": 7}]}id: structure-sections-eofoperation: read--- specsections until "END" store ending {"A {a:int}"}--- inputA 1--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "int", "depth": 0, "value": 1}]}These examples show declaration-order selection, an unsuccessful arm that commits no captures, and an arm body reading values after its matching line.
id: structure-sections-first-armoperation: read--- specsections {"A {x:int}" "A {y:int}" optional}--- inputA 7--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 7}]}id: structure-sections-speculationoperation: read--- specsections {"A {x:int} BAD" optional "A {y:int}"}--- inputA 7--- expected data{"iris": "0.1", "entries": [ {"name": "y", "type": "int", "depth": 0, "value": 7}]}id: structure-sections-bodyoperation: read--- specsections {"N {n:int}" "DATA" {a[n]:int}}--- inputN 2DATA4 5--- expected data{"iris": "0.1", "entries": [ {"name": "n", "type": "int", "depth": 0, "value": 2}, {"name": "a", "type": "int", "depth": 1, "value": [4, 5]}]}A terminating token takes priority over an arm and leaves its same-line suffix for the next statement. Repeated options accumulate terminators and use the last store name under §5.7, as the second example shows.
id: structure-sections-token-priorityoperation: read--- specsections until "END" store ending {"END" optional} tail:int--- inputEND 7--- expected data{"iris": "0.1", "entries": [ {"name": "ending", "type": "string", "depth": 0, "value": "END"}, {"name": "tail", "type": "int", "depth": 0, "value": 7}]}id: structure-sections-repeated-optionsoperation: read--- specsections until "X" store old skip other until "Y" store ending {"A" optional}--- inputX--- expected data{"iris": "0.1", "entries": [ {"name": "ending", "type": "string", "depth": 0, "value": "X"}]}A missing-arm diagnostic MUST locate the terminating token's beginning, the last arm's matching-line beginning, or EOF. It MUST NOT move to the position after the last arm's body. It MUST retain the active reader-spec line: normally the sections statement's line, but when ending through a last arm with an executed body, the body's last executed statement line. Deferred instance rejection and its EOF exception are defined only by §9.5 and §11.2.1.
7.9 Conditional execution and let
An if MUST evaluate its condition once and execute exactly the selected
body. With a false condition and no else, it MUST execute no body.
Else-if is nested conditional execution. Branches MUST NOT introduce
a frame. Unselected bodies MUST NOT read input or define values, although
repeat field discovery MUST still visit their possible definitions (§7.5).
§8.3 defines truth
conversion; §8.14
defines expression errors. This example shows if, else-if, else, let, and
replacement without changing entry order.
id: structure-if-and-letoperation: read--- speclet x=1 if 0 {let x=9} else if 1 {let y="yes"} else {let y="no"} let x=2--- input--- expected data{"iris": "0.1", "entries": [ {"name": "x", "type": "int", "depth": 0, "value": 2}, {"name": "y", "type": "string", "depth": 0, "value": "yes"}]}A let MUST evaluate its expression at that statement and define or replace
its name in the current frame without reading input. It MAY retain a scalar
or an array result. It MUST NOT automatically range over records; chapter 8
defines expression and record-name interpretation. Replacement MUST retain
entry position. In a repeat, its final local result MUST be collected under
§7.5. The example retains an array result.
id: structure-let-arrayoperation: read--- speca[2]:int let b=a--- input4 5--- expected data{"iris": "0.1", "entries": [ {"name": "a", "type": "int", "depth": 1, "value": [4, 5]}, {"name": "b", "type": "int", "depth": 1, "value": [4, 5]}]}7.10 Errors and execution context
Implementations MUST use the error registry and its location profiles, without introducing statement-specific variants. Every read-time error below uses profile R: input origin, line and column; active reader-spec origin and line with null column; current context when present; and null record_path. Shared errors retain the definitions in their owning chapters.
| Code | Structural use; primary input position |
|---|---|
E-LINE-MATCH |
Failed line or missing required sections arm; candidate line start, terminator start, last matching-line start, or EOF (§§7.3, 7.8). |
E-FIND-MATCH |
No remaining matching line; EOF (§7.3). |
E-SKIP-EOF |
No data for a requested skip; EOF (§7.3). |
E-EXPECTED-TOKEN |
Failed expect; offending token start or EOF (§7.3). |
E-COUNT |
Invalid repeat count, line count, or index base; current cursor when evaluated (§§7.3–7.4, 7.7). |
E-INDEX-CONTEXT |
No active repeat; current cursor (§7.7). |
E-INDEX-MISMATCH |
Wrong index; token start (§7.7). |
E-PATTERN-REFERENCE, E-NAME-UNDEFINED |
Reached unacceptable or undefined reference; candidate line start (§7.2). |
E-EXPECTED-VALUE |
Selected capture outside its range, or failed index conversion; capture/token start (§§7.2, 7.7). Speculative conversion failure is only a mismatch. |
E-RANGE-BOUNDS |
Invalid selected capture bounds; candidate line start (§7.2). |
E-REPEAT-TERMINATOR |
EOF without a required terminating token (§7.4). |
E-NO-PROGRESS |
No cursor advance by an indefinite body; current cursor, before collection (§7.4). |
E-REPEAT-MISSING |
Missing scalar; cursor at collection or column completion (§7.5). |
E-REPEAT-DEPTH, E-VALUE-TYPE-MIX |
Inconsistent column rows; cursor at collection (§7.5). |
E-SECTIONS-REPEATED, E-SECTIONS-UNEXPECTED |
Repeated selected arm or unhandled line; candidate line start (§7.8). |
Malformed patterns and forbidden alphabet captures MUST report
E-SPEC-PATTERN with profile P under
appendix A.4.
Iris-only E-UTF8 applies to selected
captures. §10.6
defines E-BUDGET, including post-body idle exhaustion locations.
Execution context MUST retain outer-to-inner text: finite repeats use
repeat [r of n], indefinite repeats use repeat [r], sections arm bodies
use section "PATTERN", and active value reads add their names.
Human-facing repeat numbers start at 1. Explicit as and each names
MUST NOT replace the literal repeat label. Capture commitment occurs before
entering an arm-body context. Collection failures retain the active
iteration context; final all-missing failures occur after that context is
removed. Nested statements MUST retain their own reader-spec lines.
Execution context MUST NOT be converted to a check witness path. §3.7 defines record_path; ordinary structural errors have null paths. §8.13 defines check witnesses, which can omit surrounding active iteration indices.
The following failures cover the chapter's error codes, missing scalar timing, and the distinction between a failed pattern and a committed range. Each expected part is a complete error document.
id: structure-line-mismatchoperation: read--- specline "A"--- inputB--- expected error{ "code": "E-LINE-MATCH", "message": "expected a line matching \"A\"", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-line-eofoperation: read--- specline "A"--- input--- expected error{ "code": "E-LINE-MATCH", "message": "expected a line matching \"A\", found end of input", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-find-eofoperation: read--- specfind "A"--- inputB--- expected error{ "code": "E-FIND-MATCH", "message": "no line matching \"A\" found", "input_location": { "origin": "input.bin", "line": 2, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-skip-eofoperation: read--- specskip 2 lines--- inputa--- expected error{ "code": "E-SKIP-EOF", "message": "cannot skip a line at end of input", "input_location": { "origin": "input.bin", "line": 2, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-skip-countoperation: read--- specskip -1 lines--- input--- expected error{ "code": "E-COUNT", "message": "number of lines is negative (-1)", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-expect-tokenoperation: read--- specexpect "A" "B"--- inputA C--- expected error{ "code": "E-EXPECTED-TOKEN", "message": "expected 'B', found 'C'", "input_location": { "origin": "input.bin", "line": 1, "column": 3 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-expect-eofoperation: read--- specexpect "A"--- input--- expected error{ "code": "E-EXPECTED-TOKEN", "message": "expected 'A', found end of input", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-repeat-count-typeoperation: read--- specrepeat 1.0 {}--- input--- expected error{ "code": "E-COUNT", "message": "repeat count must be an integer", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-repeat-count-negativeoperation: read--- specrepeat -1 {}--- input--- expected error{ "code": "E-COUNT", "message": "repeat count is negative (-1)", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-repeat-count-largeoperation: read--- specrepeat 2147483648 {}--- input--- expected error{ "code": "E-COUNT", "message": "repeat count is too large (2147483648)", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-index-contextoperation: read--- specindex 1--- input--- expected error{ "code": "E-INDEX-CONTEXT", "message": "'index' must be inside a repeat", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-index-base-negativeoperation: read--- specrepeat 1 {index -1}--- input0--- expected error{ "code": "E-COUNT", "message": "index base is negative (-1)", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [1 of 1]", "record_path": null}id: structure-index-mismatchoperation: read--- specrepeat 2 {index 1 x:int}--- input1 53 6--- expected error{ "code": "E-INDEX-MISMATCH", "message": "expected index 2, found 3", "input_location": { "origin": "input.bin", "line": 2, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [2 of 2]", "record_path": null}id: structure-index-valueoperation: read--- specrepeat 1 {index 1}--- inputbad--- expected error{ "code": "E-EXPECTED-VALUE", "message": "expected int, found 'bad'", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [1 of 1]", "record_path": null}id: structure-pattern-ref-typeoperation: read--- speclet x=1.0 line "{$x}"--- input1--- expected error{ "code": "E-PATTERN-REFERENCE", "message": "'x' must be a string or an integer", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-pattern-ref-undefinedoperation: read--- specline "{$x}"--- inputx--- expected error{ "code": "E-NAME-UNDEFINED", "message": "'x' is not defined yet", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-pattern-range-failureoperation: read--- specline "{x:0..n} {n:int}"--- input4 3--- expected error{ "code": "E-EXPECTED-VALUE", "message": "expected an int in 0..3, found '4'", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-pattern-range-boundsoperation: read--- specline "{x:0..1.0}"--- input1--- expected error{ "code": "E-RANGE-BOUNDS", "message": "the bounds of a range must be integers", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-repeat-missing-endoperation: read--- specrepeat until "END" "STOP" {x:int}--- input1--- expected error{ "code": "E-REPEAT-TERMINATOR", "message": "expected 'END' or 'STOP' before end of input", "input_location": { "origin": "input.bin", "line": 2, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-repeat-progressoperation: read--- specrepeat until eof {let x=1}--- input1--- expected error{ "code": "E-NO-PROGRESS", "message": "the body of a repeat until read no input", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [1]", "record_path": null}id: structure-repeat-missing-scalaroperation: read--- specrepeat 2 {k:int if k {x:int}}--- input1 40--- expected error{ "code": "E-REPEAT-MISSING", "message": "'x' is defined in only some repetitions", "input_location": { "origin": "input.bin", "line": 2, "column": 2 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [2 of 2]", "record_path": null}id: structure-repeat-missing-firstoperation: read--- specrepeat 2 {k:int if k {x:int}}--- input01 4--- expected error{ "code": "E-REPEAT-MISSING", "message": "'x' is defined in only some repetitions", "input_location": { "origin": "input.bin", "line": 2, "column": 4 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [2 of 2]", "record_path": null}id: structure-repeat-all-missing-scalaroperation: read--- specrepeat 2 {k:int if k {x:int}}--- input0 0--- expected error{ "code": "E-REPEAT-MISSING", "message": "'x' is defined in no repetition", "input_location": { "origin": "input.bin", "line": 1, "column": 4 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-repeat-depthoperation: read--- specrepeat 2 {k:int if k {x:int} else {x[1]:int}}--- input1 4 0 5--- expected error{ "code": "E-REPEAT-DEPTH", "message": "'x' has rows of different depths", "input_location": { "origin": "input.bin", "line": 1, "column": 8 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [2 of 2]", "record_path": null}id: structure-sections-repeatedoperation: read--- specsections {"A {x:int}" optional}--- inputA 1A 2--- expected error{ "code": "E-SECTIONS-REPEATED", "message": "a second line matching \"A {x:int}\" (the first is on line 1)", "input_location": { "origin": "input.bin", "line": 2, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-sections-unexpectedoperation: read--- specsections {"A"}--- inputB--- expected error{ "code": "E-SECTIONS-UNEXPECTED", "message": "unexpected line; expected one of \"A\"", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-sections-missingoperation: read--- specsections until "END" {"A" "B"}--- inputAEND--- expected error{ "code": "E-LINE-MATCH", "message": "expected a line matching \"B\" before 'END'", "input_location": { "origin": "input.bin", "line": 2, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-value-type-mixoperation: read--- specrepeat 2 {k:int if k {list a:word until eol} else {list a:int until eol}}--- input1 hi0 2--- expected error{ "code": "E-VALUE-TYPE-MIX", "message": "'a' mixes strings and numbers", "input_location": { "origin": "input.bin", "line": 2, "column": 4 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": "repeat [2 of 2]", "record_path": null}id: structure-pattern-word-gapoperation: read--- specline "a b"--- inputab--- expected error{ "code": "E-LINE-MATCH", "message": "expected a line matching \"a b\"", "input_location": { "origin": "input.bin", "line": 1, "column": 1 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-find-range-committedoperation: read--- specfind "A {x:1..2} Z"--- inputA 9 ZA 2 Z--- expected error{ "code": "E-EXPECTED-VALUE", "message": "expected an int in 1..2, found '9'", "input_location": { "origin": "input.bin", "line": 1, "column": 3 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}id: structure-sections-range-selectedoperation: read--- specsections {"A {x:1..2}" optional "A {y:int}"}--- inputA 9--- expected error{ "code": "E-EXPECTED-VALUE", "message": "expected an int in 1..2, found '9'", "input_location": { "origin": "input.bin", "line": 1, "column": 3 }, "spec_location": { "origin": "spec.iris", "line": 1, "column": null }, "context": null, "record_path": null}