Answer prose round 1: plainer linkTemplates, ownScope, resolveReads, compileArm and settleRecords docs, recordOf naming its three entry points, a plainer engine decision, and item 117 read as a path's last step

This commit is contained in:
2026-10-05 22:42:30 +02:00
committed by Lillem4n
parent b0cd3fea16
commit 338358c70e
8 changed files with 28 additions and 28 deletions
+4 -4
View File
@@ -827,16 +827,16 @@ Valid while names are read only inside the category binding them.
## comprehension floor: every dimension and the overall at 7.0 or above; item 5 scores the restructure, which ships unscored: every `{…}` drawing afresh, the moves into `internal/`, cutting a template's pointer to its table, the move of a table's rows into `internal/rows`, and linking in steps that each return what they build ## comprehension floor: every dimension and the overall at 7.0 or above; item 5 scores the restructure, which ships unscored: every `{…}` drawing afresh, the moves into `internal/`, cutting a template's pointer to its table, the move of a table's rows into `internal/rows`, and linking in steps that each return what they build
2026-10-04, Lilleman auf Larv. Serves goal 3. The nine-seat panels in `todo.md`'s git history, 2026-09-22 to 2026-09-30, held the score between 5.2 and 6.1, with Locality lowest at every seat. The hardest units were the draw fences, which the change making every `{…}` draw afresh deleted. The restructure deletes or moves what the earlier chunks of the below-floor round touched. It is three things: the change making every `{…}` draw afresh; the moves of the draw state, the grammar, `DataType`, what a proof knows of a value, the builtins and the reading of data files into `internal/`; and the untangling of the engine's own structs, which is the change cutting a template's pointer to its table, the move of a table's rows into `internal/rows`, and linking in steps that each return what they build, with a name scope created beside its owner and each record's columns settled at load. The maintainer approved these three, and `AGENTS.md`'s comprehension rule lets them merge below the floor. A scoring run before the link restructure merged would have scored code about to go; for that reason the calc change accepting a name as an operand shipped unscored on 2026-10-05. No scoring run or panel runs until item 5, which runs the nine-seat panel and larv-review's scoring run. The scoring run writes this entry's first scores. Valid until `todo.md` item 5 runs. 2026-10-04, Lilleman auf Larv. Serves goal 3. The nine-seat panels in `todo.md`'s git history, 2026-09-22 to 2026-09-30, held the score between 5.2 and 6.1, with Locality lowest at every seat. The hardest units were the draw fences, which the change making every `{…}` draw afresh deleted. The restructure deletes or moves what the earlier chunks of the below-floor round touched. It is three things: the change making every `{…}` draw afresh; the moves of the draw state, the grammar, `DataType`, what a proof knows of a value, the builtins and the reading of data files into `internal/`; and the untangling of the engine's own structs, which is the change cutting a template's pointer to its table, the move of a table's rows into `internal/rows`, and linking in steps that each return what they build, with a name scope created beside its owner and each record's columns settled at load. The maintainer approved these three, and `AGENTS.md`'s comprehension rule lets them merge below the floor. A scoring run before linking in steps merged would have scored code about to go; for that reason the calc change accepting a name as an operand shipped unscored on 2026-10-05. No scoring run or panel runs until item 5, which runs the nine-seat panel and larv-review's scoring run. The scoring run writes this entry's first scores. Valid until `todo.md` item 5 runs.
## The template engine stays the root package until item 107; what reads no engine type, a table's rows included, sits in `internal/` ## The template engine stays the root package until item 107; what reads no engine type, a table's rows included, sits in `internal/`
2026-10-05, Lilleman auf Larv. Serves goals 3.2 and 3.4. Two architect reviews found the 2026-10-05, Lilleman auf Larv. Serves goals 3.2 and 3.4. Two architect reviews found the
engine's types, `template`, `table`, `arm`, `op` and `nameBinding`, bound in one cycle engine's types, `template`, `table`, `arm`, `op` and `nameBinding`, bound in one cycle
because several passes each write part of the same structs; the template language's because several passes each write part of the same structs; the template language's
recursion reads few of those types. Moving the engine before the link gave each value recursion reads few of those types. Moving the engine while several link passes still
one writer would have spread that knot across packages, behind forwarding calls from every wrote each value would have spread that knot across packages, behind forwarding calls from
public method. The draw state, the grammar, `DataType`, what a proof knows of a every public method. The draw state, the grammar, `DataType`, what a proof knows of a
value, the builtins, the reading of data files and a table's rows read no engine type, so value, the builtins, the reading of data files and a table's rows read no engine type, so
each sits in a package under `internal/`, behind the import list `imports_test.go` checks. each sits in a package under `internal/`, behind the import list `imports_test.go` checks.
A rows table holds the engine table owning it as a value of a type parameter, so A rows table holds the engine table owning it as a value of a type parameter, so
+3 -3
View File
@@ -83,7 +83,7 @@ func checkReads(t grammar.Token, fields map[string]node, operands bool) ([]unbou
if _, _, err := grammar.RefShape(name); err != nil { if _, _, err := grammar.RefShape(name); err != nil {
return nil, fmt.Errorf("token {%s}: %w", t.Body, err) return nil, fmt.Errorf("token {%s}: %w", t.Body, err)
} }
continue // its target is checked at New (see linkCategories) continue // resolveLink checks its target once the tree is assembled
} }
missing, err := checkArm(name, fields, !operands && len(names) == 1) missing, err := checkArm(name, fields, !operands && len(names) == 1)
if err == nil { if err == nil {
@@ -316,8 +316,8 @@ func (t *template) compileArms(names []string, targets map[*nameBinding]nameTarg
return arms, nil return arms, nil
} }
// compileArm compiles one read: a path from the head it names, or from what the name it reads // compileArm compiles one read into a path: from the head it names, or, for a read through a
// through binds. // name, from what that name binds.
func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) { func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) {
a := splitArm(name, t.link.refs) a := splitArm(name, t.link.refs)
if !t.isName(a.head) { if !t.isName(a.head) {
+5 -5
View File
@@ -7,7 +7,7 @@ import (
"github.com/larvit/fejkdata/internal/grammar" "github.com/larvit/fejkdata/internal/grammar"
) )
// linkSite is a template to link, with the folder and category it sits in, "" for an inline // linkSite is a template to link, with its folder and category, both empty for an inline
// template, and eachNode's label for it. // template, and eachNode's label for it.
type linkSite struct { type linkSite struct {
t *template t *template
@@ -61,10 +61,10 @@ type linkedNames struct {
uses map[*nameBinding][]nameUse uses map[*nameBinding][]nameUse
} }
// linkTemplates links ts in steps, each over every template before the next starts, each // linkTemplates links ts in steps, each over every template before the next starts. Each step
// returning what it builds: the references and the compiled formats, kept on each template, the // returns what it builds. The references, the compiled format and the column read are kept on each
// names' targets, keys and reads, passed to the steps after as arguments, and the column each // template. The names' targets, keys and reads pass to later steps as arguments, and the bindings
// format reads. The bindings take their targets and keys once every check has passed. // take their targets and keys only once every check has passed.
func linkTemplates(ts []linkSite, root map[string]node) error { func linkTemplates(ts []linkSite, root map[string]node) error {
for _, s := range ts { for _, s := range ts {
link, err := s.t.resolveLink(s.folder, s.label, s.category, root) link, err := s.t.resolveLink(s.folder, s.label, s.category, root)
+6 -6
View File
@@ -141,8 +141,8 @@ func (sc *nameScope) spelled() string {
return "in one repeat" return "in one repeat"
} }
// ownScope is the scope t renders a frame of, nil where that scope binds no name: a category's, // ownScope is the scope t renders a frame of: a category's, once per render of its root, or a
// on its root, or a repeat's, per iteration. // repeat's, once per iteration. It is nil where t owns no scope, or its scope binds no name.
func (t *template) ownScope() *nameScope { func (t *template) ownScope() *nameScope {
if sc := t.nameScope; sc != nil && sc.owner == node(t) && len(sc.order) > 0 { if sc := t.nameScope; sc != nil && sc.owner == node(t) && len(sc.order) > 0 {
return sc return sc
@@ -165,10 +165,10 @@ func eachContained(n node, where string, fn func(c node, where string) error) er
return nil return nil
} }
// resolveReads answers each unbound read under root with a name its template sees, fields before // resolveReads answers each unbound read under root with a name its template sees, and returns
// the format as compile reports them, returning every binding read, and refuses a field spelling a // the set of bindings some read answers to. It refuses a field spelling a name, fields before the
// name. scopes are every scope of the category, which a refusal searches for a name bound where // format, as compile reports them. A refusal searches scopes, every scope of the category, for a
// the read cannot see it. // name bound where the read cannot see it.
func resolveReads(root node, scopes []*nameScope) (map[*nameBinding]bool, error) { func resolveReads(root node, scopes []*nameScope) (map[*nameBinding]bool, error) {
read := map[*nameBinding]bool{} read := map[*nameBinding]bool{}
var resolve func(n node, where string) error var resolve func(n node, where string) error
+3 -3
View File
@@ -64,9 +64,9 @@ func rendersInside(nodes []node, t *template) bool {
} }
// checkUses refuses a binding whose uses read it once at a spot the bound spelling can stand: // checkUses refuses a binding whose uses read it once at a spot the bound spelling can stand:
// that spelling draws the same way without the name. A bound field can stand in only where the reading // that spelling draws the same way without the name. A bound field can stand in only where the
// template reaches the binder through fields. Where the spelling would be a CLI argument or tag // reading template reaches the binder through fields. Where the spelling would be a CLI argument
// of one reference alone, that entry point's own refusal then names the bare path. // or tag of one reference alone, that entry point's own refusal then names the bare path.
func (b *nameBinding) checkUses(uses []nameUse) error { func (b *nameBinding) checkUses(uses []nameUse) error {
if len(uses) == 0 { if len(uses) == 0 {
panic(invariant.Broken("name %q has no read at link, though its compile found one", b.name)) panic(invariant.Broken("name %q has no read at link, though its compile found one", b.name))
+1 -1
View File
@@ -66,7 +66,7 @@ type template struct {
// Filled by `linkTemplates`, from the assembled tree: // Filled by `linkTemplates`, from the assembled tree:
link templateLink link templateLink
compiled formatOps compiled formatOps
readsColumn *columnRead // set when the format is one reference or name read alone reading a record's column readsColumn *columnRead // set when the format only reads one reference or name, and that read is a record's column
// Filled by `settleRecords`, once the load refused every cycle: // Filled by `settleRecords`, once the load refused every cycle:
columns []recordColumn // a record's, where it has fields, in name order columns []recordColumn // a record's, where it has fields, in name order
+4 -4
View File
@@ -214,8 +214,8 @@ func (f *Generator) FakeRecordTemplate(input string) (*Record, error) {
// the record to write instead. // the record to write instead.
var ErrNoColumns = errors.New("has no fields, so no columns") var ErrNoColumns = errors.New("has no fields, so no columns")
// recordOf is the fence both record entry points pass: the record template n is, whose columns // recordOf is the record template n is, the fence FakeRecord, NewRecordTemplate and FakeStruct
// settleRecords fixed at load for every draw. // pass; settleRecords fixed its columns at load.
func recordOf(n node) (*template, error) { func recordOf(n node) (*template, error) {
if tb, isTable := n.(*table); isTable { if tb, isTable := n.(*table); isTable {
n = tb.formatTemplate n = tb.formatTemplate
@@ -246,8 +246,8 @@ type recordColumn struct {
boundWhole *nameBinding boundWhole *nameBinding
} }
// settleRecords fixes the columns of every record in scope, whose datatypes a cycle would walk // settleRecords fixes the columns of every record in scope. bind runs it after checkNoCycles: a
// forever, so bind refuses the scope's cycles first. // column's datatype walks the column, and a cycle would walk forever.
func settleRecords(scope nodeScope) { func settleRecords(scope nodeScope) {
_ = scope(func(_ string, n node) error { _ = scope(func(_ string, n node) error {
if t, isTemplate := n.(*template); isTemplate { if t, isTemplate := n.(*template); isTemplate {
+2 -2
View File
@@ -85,7 +85,7 @@
| 88 | 0.2.0 | | **Add a 12-digit `YYYYMMDDNNNC` form of `sv_SE.personnummer` and `sv_SE.samordningsnummer`, the form Skatteverket's testpersonnummer series and many systems store.** | 2 | 3 | 6 | 6 | 4 | 19.4 | | 88 | 0.2.0 | | **Add a 12-digit `YYYYMMDDNNNC` form of `sv_SE.personnummer` and `sv_SE.samordningsnummer`, the form Skatteverket's testpersonnummer series and many systems store.** | 2 | 3 | 6 | 6 | 4 | 19.4 |
| 41 | 0.2.0 | decision | **Key every table by one key column, as the decision "A table selects by one key column, a code, never a free-form name" states.** | 6 | 7 | 7 | 8 | 5.5 | 19.1 | | 41 | 0.2.0 | decision | **Key every table by one key column, as the decision "A table selects by one key column, a code, never a free-form name" states.** | 6 | 7 | 7 | 8 | 5.5 | 19.1 |
| 52 | 0.2.0 | | **Name the file a link error comes from beside its category path, `sub.x (/d/a/sub/x.json): …`.** | 1 | 2 | 5 | 7 | 6 | 19.0 | | 52 | 0.2.0 | | **Name the file a link error comes from beside its category path, `sub.x (/d/a/sub/x.json): …`.** | 1 | 2 | 5 | 7 | 6 | 19.0 |
| 117 | 0.2.0 | | **List the fields a category has when a path names one it lacks: `{/word as a}{a.zz}` reports `no field "zz"; word has w`.** | 1 | 2 | 5 | 7 | 6 | 19.0 | | 117 | 0.2.0 | | **List the fields a path's last step reached when it names one that step lacks: `{/word as a}{a.zz}` reports `no field "zz"; word has w`.** | 1 | 2 | 5 | 7 | 6 | 19.0 |
| 28 | 0.2.0 | decision | **Accept a middle name, and draw a `personnummer` whose sex matches a sex selected through a named pick.** | 4 | 4 | 6 | 7 | 4.1 | 18.9 | | 28 | 0.2.0 | decision | **Accept a middle name, and draw a `personnummer` whose sex matches a sex selected through a named pick.** | 4 | 4 | 6 | 7 | 4.1 | 18.9 |
| 24 | 0.2.0 | defect | **Stop `misc.territory[EH].tld` rendering `.eh`, the one shipped TLD `misc.tld` does not hold.** | 1 | 1 | 4 | 7 | 4 | 18.6 | | 24 | 0.2.0 | defect | **Stop `misc.territory[EH].tld` rendering `.eh`, the one shipped TLD `misc.tld` does not hold.** | 1 | 1 | 4 | 7 | 4 | 18.6 |
| 92 | 0.2.0 | | **Name the hyphenated field in a `calc`'s refusal, and say to rename it: today `{calc(postal-code * 2)}` reports `no field "postal"`.** | 1 | 2 | 5 | 6 | 6 | 17.5 | | 92 | 0.2.0 | | **Name the hyphenated field in a `calc`'s refusal, and say to rename it: today `{calc(postal-code * 2)}` reports `no field "postal"`.** | 1 | 2 | 5 | 6 | 6 | 17.5 |
@@ -304,7 +304,7 @@ A system that stores the 12-digit form never meets the `+`, so it needs a fixtur
With several `--data-path` layers the author has to work out which directory won, while a parse error on the same file names it. With several `--data-path` layers the author has to work out which directory won, while a parse error on the same file names it.
### 117. List the fields a category has when a path names one it lacks: `{/word as a}{a.zz}` reports `no field "zz"; word has w`. ### 117. List the fields a path's last step reached when it names one that step lacks: `{/word as a}{a.zz}` reports `no field "zz"; word has w`.
Today the refusal names only the missing field, so a data author fixing a typo opens the category to find the spelling. Item 92 covers the hyphenated `calc` operand only. Today the refusal names only the missing field, so a data author fixing a typo opens the category to find the spelling. Item 92 covers the hyphenated `calc` operand only.