ADR-366: A Grammar Reads Bytes as Readily as Text; What Becomes Text Is Checked Where It Becomes Text

Status: Accepted Date: October 18, 2026 Answers: whether a grammar can parse input that is not text, and what a view cut from such input is Related: ADR-016 D1 (a view of a mapping is text because the mapping was checked), §4 (fusing the check into the parse, deferred); winnow-grammar ADR 25 (input bytes;, the backend this lowers to); Part I 2.6 (text and Bytes), Part II 10.7, 10.8

1. Question

grammar Record {
    entry rule record -> Entry = len:dec[i64](digit+) ":" body:bytes(any*) { … }
}

let raw = fs::read("data.bin", root)      // Bytes
let e = Record::record(raw)               // refused today: an entry takes text

An entry takes text, so a format that is not UTF-8 - a binary record, a length-prefixed field, a file whose encoding the grammar decides - cannot be described. A program has to prove the whole input to be text before the first rule runs, and the one place that may do so (fs::read_to_string, fs::map) refuses the file instead.

2. Options

Option Why it lost See
A grammar declares its input (input bytes in the grammar) the same grammar is wanted over both; the argument already says which it is D1
A Bytes input is checked whole, then parsed as text it is what a text input already is, and it still refuses a binary format D2
A view cut from Bytes is ref String unchecked a ref String that is not UTF-8 is undefined behaviour in code written safely (ADR-016 D1) D2
A separate bytes_grammar two vocabularies for a difference of one input type D1

3. Decision

D1 — An entry takes a String or a Bytes; the argument decides.

The same grammar is parsed over either. Record::record(raw) with a Bytes parses its bytes; with a String it parses the text. Nothing in the grammar says which, and a grammar called with both is compiled for both. Every element of 10.8 means the same over both inputs; the differences are D2 and D3.

D2 — What becomes text from Bytes is checked as UTF-8 where it becomes text.

Over a Bytes input, an element that yields text (until, text, string, raw_ident, intern, ident) checks the bytes it covered, and one that yields a scalar (any, char) checks what it decodes. What is matched by comparison - a literal, digit, hex_digit, the whitespace, a frame boundary, dec[T] - needs no check: no byte it accepts can be anything but what was compared. A view that passed the check is a view of the input, as over text; nothing is copied.

Over a String the same elements check nothing, because the text was already checked when it was made.

D3 — A byte that is not UTF-8 is a parse error at its own offset.

The parse stops there. No enclosing alternative is retried, because none that reads the same bytes as text can succeed, and the error must name the byte rather than the last alternative tried. The entry throws ParseError as for any other failure, with the message invalid UTF-8 at byte N: 0xXX (the byte in hex), where N counts from the start of the whole input, in pieces of a par_fold too.

D4 — bytes(p) hands back the bytes p covered.

A view of the input’s bytes, as b[a..<b] is (Part I 2.6), unchecked, over either input. It is how a grammar keeps a field that is not text.

D5 — Text used only as a map’s key is checked where the map misses.

Over a Bytes, text that an element yields and the program uses only to look up or insert a key of a map of text is checked as UTF-8 only where the map does not hold it. Bytes equal to a key the map holds are that key’s bytes, which were checked when it went in, so the check would answer the same. A name that is not UTF-8 equals no key, so it misses and is checked at its own offset as D3 says: the error and what the program printed before it are unchanged. In 1BRC the check per row cost 115.6 instructions of 338.1 when the mapping was parsed as Bytes; on a miss it runs once a station.

Decided by the supervising session on the owner’s direction (October 20, 2026: decide what is needed for the goal), from #664’s measurement.

4. Rejected and deferred

This site is open source. Improve this page.