R4 — the syntactic half (4b): verified-lexer numeric core #
StableHLOParse.lean closes the structural core of syntactic faithfulness:
parse (toToks (skel a)) = some (skel a) — the op-graph is recovered exactly
from its token serialization. The remaining trusted edge is the lexical
one: that the emitted .mlir text (the bytes iree-compile reads) is a
faithful, recoverable rendering of those tokens — parse (lex (pretty g)) = some (skel g).
This file lands the numeric keystone of that lexer: the decimal
Nat ⟷ String round-trip. Every per-op recognizer must read shapes (784,
10, …) back out of tensor<784x10xf32> type annotations, and emitTok
renders every such shape with toString (Lean's decimal Nat.repr). So
parseNat (toString n) = n is the one lemma the whole lexer is built on.
A List Char codec, and core already has it #
A 2026-06-27 probe established that Lean-core string parsing primitives —
String.toNat?, String.splitOn — are kernel-opaque: they do not reduce
under decide/rfl (they fold over String.Pos/Substring iterators), so a
concrete decide-the-instance shortcut is impossible and a verified lexer has to
work at the List Char level with structurally-recursive functions. Core ships
exactly that level's decimal codec: Nat.ofDigitChars is a List.foldl Horner
step (so it reduces), and Nat.ofDigitChars_ten_toDigits is its round-trip
against Nat.toDigits, which is what toString renders. parseNat below is that
fold. (Rendering — toString, ty, ++ — reduces by rfl, so the emit side
needs nothing.)
Honest scope of the remaining lexer (corrects the planning doc) #
Three sub-problems remain above this keystone; the planning doc
(tier23_float_and_syntactic_faithfulness.md §B) under-modeled the first two:
- Per-op recognizers (volume). ~90
Tokconstructors, each emitting a fixed-shape multi-line block (0 lines foroperand, up to ~20 for the BN-γ SGD op). Many blocks share a leadingstablehlo.constant dense<0.0>line, so recognition needs block-delimiting + lookahead, not a 1-line-per-token map. Each needs a recognizer + anemitTok_lexTokinverse lemma. This is the bulk. - Operand re-synthesis.
emitTok (.operand nm _) = ("", nm :: st)— operand tokens emit the empty string; the name appears only as a reference inside a later op's line. ButtoToks (skel g)contains operand tokens andparseconsumes them. So the doc's step-2 targetlex (pretty g) = toToks (skel g)is false as written:lexmust regenerate operand tokens from operand references (distinguishing leaf names from fresh%v{k}results —freshatStableHLO.lean:2138). The correct end target is the compositeparse (lex (pretty g)) = some (skel g). - The
ty-string parser.ty dims = "tensor<" ++ intercalate "x" (…) ++ ">"inverts to aList Charsplitter on'x'+parseNatper field — needs asplit (intercalate)round-trip at theList Charlevel (coresplitOnis kernel-opaque, see above). ReusesparseNat.
Effort for the full lexer is therefore large / multi-session (volume + the two design subtleties), not the "medium finite case-split" the doc billed.
Status: deliberate STOP, not work-in-progress (decided 2026-06-27) #
The full lexer is not being pursued — low ROI: the CI drift guard (proofs.yml)
already byte-for-byte diffs every committed verified_mlir/<net>_train_step.mlir
against the renderer, so the practical risk is caught; and a finished lexer would
close only the lexical edge, leaving the spec/IREE/float32≈ℝ edges trusted
anyway. This file is kept as a small proven down-payment and as the record of
why Part B is a poor target (the three findings above), so the scoping is not
re-discovered from scratch. See planning/archive/tier23_float_and_syntactic_faithfulness.md
(Part B VERDICT block).
Residue (unchanged, state wherever cited) #
Even the full parse (lex (pretty g)) = some (skel g) closes only the lexer.
It does not close per-op StableHLO spec conformance, IREE lowering, or
float32 ≈ ℝ — those stay validated by iree-compile + the GPU runs.
The lexer's numeric core: decode a string's chars as a big-endian decimal — core's
Nat.ofDigitChars, a List.foldl, so it still reduces under decide.
(Non-validating — it only ever sees emit's output, which is always digits;
rejecting non-digit input is a robustness property, not a faithfulness one.)
Equations
- Proofs.StableHLO.parseNat s = Nat.ofDigitChars 10 s.toList 0
Instances For
Decimal round-trip keystone. Parsing the rendered decimal of any n
recovers n — the one lemma the whole verified lexer rests on. toString n is
Nat.repr n, whose chars are Nat.toDigits 10 n, and core's
Nat.ofDigitChars_ten_toDigits inverts exactly that.