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
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/`
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
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
one writer would have spread that knot across packages, behind forwarding calls from every
public method. The draw state, the grammar, `DataType`, what a proof knows of a
recursion reads few of those types. Moving the engine while several link passes still
wrote each value would have spread that knot across packages, behind forwarding calls from
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
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
+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 {
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)
if err == nil {
@@ -316,8 +316,8 @@ func (t *template) compileArms(names []string, targets map[*nameBinding]nameTarg
return arms, nil
}
// compileArm compiles one read: a path from the head it names, or from what the name it reads
// through binds.
// compileArm compiles one read into a path: from the head it names, or, for a read through a
// name, from what that name binds.
func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) {
a := splitArm(name, t.link.refs)
if !t.isName(a.head) {
+5 -5
View File
@@ -7,7 +7,7 @@ import (
"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.
type linkSite struct {
t *template
@@ -61,10 +61,10 @@ type linkedNames struct {
uses map[*nameBinding][]nameUse
}
// linkTemplates links ts in steps, each over every template before the next starts, each
// returning what it builds: the references and the compiled formats, kept on each template, the
// names' targets, keys and reads, passed to the steps after as arguments, and the column each
// format reads. The bindings take their targets and keys once every check has passed.
// linkTemplates links ts in steps, each over every template before the next starts. Each step
// returns what it builds. The references, the compiled format and the column read are kept on each
// template. The names' targets, keys and reads pass to later steps as arguments, and the bindings
// take their targets and keys only once every check has passed.
func linkTemplates(ts []linkSite, root map[string]node) error {
for _, s := range ts {
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"
}
// ownScope is the scope t renders a frame of, nil where that scope binds no name: a category's,
// on its root, or a repeat's, per iteration.
// ownScope is the scope t renders a frame of: a category's, once per render of its root, or a
// repeat's, once per iteration. It is nil where t owns no scope, or its scope binds no name.
func (t *template) ownScope() *nameScope {
if sc := t.nameScope; sc != nil && sc.owner == node(t) && len(sc.order) > 0 {
return sc
@@ -165,10 +165,10 @@ func eachContained(n node, where string, fn func(c node, where string) error) er
return nil
}
// resolveReads answers each unbound read under root with a name its template sees, fields before
// the format as compile reports them, returning every binding read, and refuses a field spelling a
// name. scopes are every scope of the category, which a refusal searches for a name bound where
// the read cannot see it.
// resolveReads answers each unbound read under root with a name its template sees, and returns
// the set of bindings some read answers to. It refuses a field spelling a name, fields before the
// format, as compile reports them. A refusal searches scopes, every scope of the category, for a
// name bound where the read cannot see it.
func resolveReads(root node, scopes []*nameScope) (map[*nameBinding]bool, error) {
read := map[*nameBinding]bool{}
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:
// that spelling draws the same way without the name. A bound field can stand in only where the reading
// template reaches the binder through fields. Where the spelling would be a CLI argument or tag
// of one reference alone, that entry point's own refusal then names the bare path.
// that spelling draws the same way without the name. A bound field can stand in only where the
// reading template reaches the binder through fields. Where the spelling would be a CLI argument
// or tag of one reference alone, that entry point's own refusal then names the bare path.
func (b *nameBinding) checkUses(uses []nameUse) error {
if len(uses) == 0 {
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:
link templateLink
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:
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.
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
// settleRecords fixed at load for every draw.
// recordOf is the record template n is, the fence FakeRecord, NewRecordTemplate and FakeStruct
// pass; settleRecords fixed its columns at load.
func recordOf(n node) (*template, error) {
if tb, isTable := n.(*table); isTable {
n = tb.formatTemplate
@@ -246,8 +246,8 @@ type recordColumn struct {
boundWhole *nameBinding
}
// settleRecords fixes the columns of every record in scope, whose datatypes a cycle would walk
// forever, so bind refuses the scope's cycles first.
// settleRecords fixes the columns of every record in scope. bind runs it after checkNoCycles: a
// column's datatype walks the column, and a cycle would walk forever.
func settleRecords(scope nodeScope) {
_ = scope(func(_ string, n node) error {
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 |
| 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 |
| 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 |
| 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 |
@@ -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.
### 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.