members[S], and json::read[T] as a grammar entryOpen and answer this issue on GitHub.
language, ready, wip · opened 2026-10-30 · updated 2026-10-31 · 1 comment
ADR-344 D3 specified json::read[T] as a library function over T::fields. It could not be written: nothing in the language builds a struct from field values (field.set is NK1180), and the decoder would have been a scanner outside the grammar. ADR-grammar-2 (accepted at 0.0.1314) makes the typed read a grammar entry that takes the type. The compiler emits one parser per type and builds the value itself.
ADR-grammar-2 D1-D13; Part II 10.8 (type parameters on rules, match on a kind in a pattern, members[S], the mapping rule, refuse, @stream), 10.5, 10.7; Part I 4.7 (NK1233 narrowed); Part III 17.3 and C.3 (NK1250 widened, NK1310-NK1312 new).
#859 steps 1-4 are built. Step 6 is blocked on #520 (a grammar exported by a package). Built by the tools lane (docs/briefs/tools/).
Each step comes with its tests, as .nika files in tests/compiler/.
Value__Contact). A bound on a parameter is allowed. A value where a type belongs is NK1230. Copies count against the build budget.match on a kind in a rule body, with only the taken arm emitted; refuse("…"). A type no arm covers is NK1250, naming the field path and the rule. A match on a value not known while building is NK1310.members[S](key, value, sep; name:, null:, unknown:), lowered to a fold over per-field slots, with the mapping rule:
null in a T?, else an error;null only into a T? or a field with a default;unknown: skips it.Also: NK1311 and NK1312. Field names and indices go on the ParseError stack as the path. Strings without escapes are views of the input.
Split (architect, 2026-10-31):
meta::Field, a function over a reflected field (Part II 10.3, ADR-grammar-2 D4 amended; NK1320 elsewhere). Built by j3, 7fe9f88 (one copy per field; comptime f: meta::Field does not parse, every comptime value parameter is refused: #906 step 1, probe there).members[S](key, value, sep; null:, unknown:) with the mapping rule; left out, name: is the field’s name. Built by j4, 20a79c9 (helper rules __nikaia_members_N*, key dispatch by nikaia_std::grammar::member_index; match T::kind takes Struct(_); the null:/unknown: rule names are not yet checked by the checker).comptime binding reads a reflected field and calls a function over it, and T? crosses from the build (#1087, decided by the architect: Part II 10.2). Brief j5-1019-comptime-field.md. Run 1, 417abcf: f.name and shout(f.name) in a turn. Left: wire(f) through a generic value function in the build-time sub-program (waits on #1095, brief j7-1095-sync-bracket-call.md), T? crossing, f.attribute(X)?.value and f"..{f.name}.." in a turn: j5 run 2. Built: #1095 by j7, db6e787; j5 run 2, 974b9ac (Fixes #1087). Its Vec/.remove(0) in the turn function was a way around #1108 (a sync fn that may panic() refused at build time): j9 (j9-1108-panic-at-build-time.md) fixes #1108 and removes it.name: evaluated per field while building (needs 3a and 3b’), NK1311, NK1312. Probe (architect, 69ea78d): fn names[T: Struct]() -> Vec[String?] sync { for f in T::fields { out.push(wire_name(f)) } } with wire_name(f: meta::Field) -> String? runs at build time per field (attribute and null both right); only the holder is refused (comptime NAMES: Vec[String?] is NK1167, Array[String?, N] asked for). Brief after j8/j9: how the grammar copy (grammar_types.nika) gets those names before it emits the key dispatch is to be traced first. A fieldless attribute (@Skip) is #1086 (bug lane).ParseError stack: at $.contacts[1].mail after the excerpt, the __nikaia_* frames not listed, recorded on the failure’s return path only (architect, 2026-10-31, obvious: Part II 10.8 already says it). Brief j6-1019-error-path.md. Run 1 blocked (right): winnow-grammar 4f8e6691 drops the item N frame on a Cut. Decided (architect, obvious): the backend numbers a Cut error in a repetition as it numbers a Backtrack one (j8, j8-1019-cut-item-frame.md, in a local clone; the main session pushes it upstream). Built by j8: winnow-grammar e9de602c (local, patch docs/briefs/tools/j8-1019-cut-item-frame.patch, main session pushes it); the backend fails its own clippy gate at src/rt.rs:711 since 4f8e6691 (j8 Found), fixed by j10 (j10-1019-backend-clippy.md) before the push. Then j6 run 2 moves the pin, field marker rules, list markers __nikaia_list_first/__nikaia_list_elem read with the item k frame outside them.NK1233 narrowed: no warning where the brackets alone name the type.benches/json/, modelled on benches/brc/:
Vec of a struct with String, i64, f64, bool, a T? and a nested list;serde_json::from_slice with derive, both release builds with opt-level = 3 and the same LTO setting, the same output byte for byte;json::parse plus a conversion by hand.pub grammar Json in packages/json/ with the entries read[T] and parse; json::read[T](input; comptime schema: …) as a forwarding function; enum variants without a payload by name; the hand-written reader retired. The existing package tests keep passing.json::write[T] over T::kind and T::fields, sharing wire_name.@stream entries (lowered to the engine’s #[stream]) and par_fold over a generic @frame rule.NK1251).yaml’s serde layer (#928) and toml.The commit that builds the last step says Fixes #N; #604 closes with it.
Generated by Claude Code
The blocker on the compiler side is built (ee22551, 0.0.1321): path::f[T](…) fills the type parameter of a function of another package or std the same way f[T](…) does, with warning[NK1233] only where a value argument already says the type (ADR-grammar-2 D11). So a json::read[Contact](bytes)-shaped call now reaches a one-parameter generic function. What is left for this issue: json::read itself. A path callee with more than one type parameter is #1058.
Generated by Claude Code