Status: Accepted
Date: September 22, 2026
Closes: #197 (the escape set is
Rust’s, and no page says so)
Related: ADR-300 (the source map that puts a backend refusal
on the right .nika line), ADR-017 (escaping in a template, a
different question one word away), ADR-287 D6 (the build-time
decoder that reads these escapes)
The parser’s STR_CHAR takes \ and any character and keeps both; the
emitter writes a .nika string literal into the generated Rust verbatim. So
what a \ means is decided by rustc.
The set itself is not the problem. A literal that goes into the generated file as it stands has the language below’s escapes whether anybody writes that down or not, and writing it down is describing — the same bargain Part I 2.2 already makes about numbers, where the widths are the machine’s and the page says so.
What an unknown escape is told is the problem. Left to rustc,
println("a\qb") is refused on the right line — ADR-300’s source
map holds — but in rustc’s vocabulary, ending for more information, visit
doc.rust-lang.org/reference/tokens.html#literals. A Nikaia program sent to the
Rust reference to find out what it may write is
Part III C.2’s in the compiler’s own words not met,
and C.1’s class besides.
\n \r \t \0 \\ \' \" \xNN \u{…}, in a "…", an f"…", a '…' and a
template alike. \xNN names a byte below \x80, because that is what the
language below allows inside a string; a character above it is \u{…}.
Describing and not deciding. Borrowing the backend’s escapes costs one sentence and keeps the two readings of a literal — this compiler’s and the generated file’s — from ever parting company. Deciding a set of our own would mean decoding every literal and re-encoding it, which is ADR-287 D6’s work done twice for no reader’s benefit.
build_time::one_escape is the table. decoded reads it to turn a literal into
a value while the program is built; an_escape_nothing_names reads it to say
which escape a literal has that the set does not name.
Two walks would be two sets. A second copy of the table would drift — and the one that drifted open would refuse
a literal the other decodes, which is a correct program refused
(C.4). The refusal is safe precisely because every
escape it refuses is one the decoder cannot read and rustc refuses one file
later: what moves is who says it, and in whose vocabulary.
NK1184, in the checker, naming the set.At every literal that keeps its body as written — Expr::LitStr,
Expr::LitInterpolated and Expr::LitChar. The message names the escape as
written, one clause saying what is wrong with it, and the set.
In the checker and not in the parser. A fail arm inside a lexical rule is
not fatal in this parser — an error that got further still wins, progress
before priority — so where the rest of the file parses, as it does here, the
message would be discarded with the attempt. The literal reaches the checker
with its body intact, which is all this question needs.
A malformed \x or \u{…} was meant as a character, so the help names the
form it should have had: \u{80}, \u{1F600}. Anything else is a backslash
that was meant literally, and the help writes the literal back with its
backslashes doubled.
C.2’s rule, applied rather than quoted: a way out
that cannot be taken is not one, and telling someone who wrote \x80 to
double the backslash would be one.
dsl html { … } is markup and its
escaping is ADR-017’s, a different question one word away; what
this record covers is the string literal a program writes.r"…" does not exist in this language, and a program
that wants a backslash writes \\. Whether the regular expressions in
Part I 2.5’s own example deserve one is a question
nobody has asked twice.