Rename arm.key to head, give renderScope.row its own type, and value.go to valueproof.go

This commit is contained in:
2026-09-29 10:11:12 +02:00
committed by Lillem4n
parent 30d0f84ea4
commit a7d77bd3ad
17 changed files with 88 additions and 86 deletions
+1 -1
View File
@@ -896,7 +896,7 @@ checksum.go the check characters a derivation appends, and the IBAN they sit
transform.go the builtins that rewrite an operand's value, and the ASCII folding
calc.go the {calc()} arithmetic evaluator: parser, eval, validation
datatype.go column datatypes: DataType, where datatype and null may sit, a column's datatype
value.go the value proof: what a typed column or calc operand holds, checked at load
valueproof.go the value proof: what a typed column or calc operand holds, checked at load
data.go the load path: fs.FS folders/files -> merged namespace tree, and the bind pipeline every entry point runs
cmd/fejkdata/ the fejkdata CLI
data/ shipped data (JSON, and a TSV per table), embedded at build: locale folders, geo, misc
+1 -1
View File
@@ -77,7 +77,7 @@ func checkColumns(s nodeScope) error {
items, _ := columnItems(column)
for _, it := range items {
if r := it.link.readsColumn; r != nil {
if err := check(r.a.key[1:], r.a.tail[0], r.column); err != nil {
if err := check(r.a.head[1:], r.a.tail[0], r.column); err != nil {
return err
}
}
+2 -2
View File
@@ -90,7 +90,7 @@ func checkOwnFamily(t *template) error {
seen[n] = true
for _, e := range renderEdges(n) {
if e.readsRef() {
if head, isTable := n.(*template).head(e.read.key).(*table); isTable && head.familyRoot() == own.familyRoot() {
if head, isTable := n.(*template).head(e.read.head).(*table); isTable && head.familyRoot() == own.familyRoot() {
return e, head, true
}
}
@@ -183,7 +183,7 @@ func readsTable(n node, a arm) bool {
if !isTemplate {
return false
}
_, isTable := t.head(a.key).(*table)
_, isTable := t.head(a.head).(*table)
return isTable
}
+33 -33
View File
@@ -246,25 +246,25 @@ func checkArm(name string, fields map[string]node, wholeToken bool) error {
if err := checkSegments(a); err != nil {
return err
}
field, ok := fields[a.key]
field, ok := fields[a.head]
if !ok {
if a.key == "" {
if a.head == "" {
return fmt.Errorf("a name is never empty, so this token can name no field")
}
if isOption(a.key) {
return fmt.Errorf("%q is an option and can never be a field", a.key)
if isOption(a.head) {
return fmt.Errorf("%q is an option and can never be a field", a.head)
}
if len(fields) == 0 {
hint := ""
if wholeToken && hintableRef(name) {
hint = fmt.Sprintf(" — write {/%s} to reference the data", name)
}
return fmt.Errorf("no field %q; a token names a sibling field, and this template has none%s", a.key, hint)
return fmt.Errorf("no field %q; a token names a sibling field, and this template has none%s", a.head, hint)
}
return fmt.Errorf("no field %q", a.key)
return fmt.Errorf("no field %q", a.head)
}
if err := checkPath(field, a.tail, a.key); err != nil {
return fmt.Errorf("field %q: %w", a.key, err)
if err := checkPath(field, a.tail, a.head); err != nil {
return fmt.Errorf("field %q: %w", a.head, err)
}
return nil
}
@@ -302,45 +302,45 @@ func checkNoRepeatedArm(body string, names []string) error {
return nil
}
// arm is one alternative of a {a|b} token or one operand, split into the key
// arm is one alternative of a {a|b} token or one operand, split into the head
// `template.head` resolves and the tail of a dotted path into it. A non-empty tail
// is what makes the arm a held draw: its head is drawn once per expansion (see
// compileOps).
type arm struct {
spelling string // as written, for messages
key string
head string
tail []string
levels []string // the key at each level the tail passes through, the arm's key first
path string // key and tail, the one path every way of writing this read shares
levels []string // the path at each level the tail passes through, the head first
path string // head and tail, the one path every way of writing this read shares
}
// splitArm splits one name into key and tail. refs maps a reference to what
// splitArm splits one name into head and tail. refs maps a reference to what
// linkRefs resolved it to; before linking, a reference is whole.
func splitArm(name string, refs map[string]refBinding) arm {
if isRef(name) {
b, linked := refs[name]
if !linked || len(b.tail) == 0 {
key := name
head := name
if linked {
key = b.key
head = b.key
}
return arm{spelling: name, key: key, path: key}
return arm{spelling: name, head: head, path: head}
}
return pathArm(name, b.key, b.tail)
}
segs, err := splitPath(name)
if err != nil || len(segs) == 1 {
return arm{spelling: name, key: name, path: name}
return arm{spelling: name, head: name, path: name}
}
return pathArm(name, segs[0], segs[1:])
}
func pathArm(name, key string, segs []string) arm {
levels := []string{key}
func pathArm(name, head string, segs []string) arm {
levels := []string{head}
for i := 0; i < len(segs)-1; i++ {
levels = append(levels, key+"."+strings.Join(segs[:i+1], "."))
levels = append(levels, head+"."+strings.Join(segs[:i+1], "."))
}
return arm{spelling: name, key: key, tail: segs, levels: levels, path: key + "." + strings.Join(segs, ".")}
return arm{spelling: name, head: head, tail: segs, levels: levels, path: head + "." + strings.Join(segs, ".")}
}
// checkSegments rejects an unfinished path: "{a.}" and "{a..b}" each have a
@@ -350,7 +350,7 @@ func checkSegments(a arm) error {
if len(a.tail) == 0 {
return nil
}
if a.key == "" {
if a.head == "" {
return fmt.Errorf("path has an empty segment")
}
for _, seg := range a.tail {
@@ -379,13 +379,13 @@ type op struct {
}
// formatOps is a compiled format: its ops, the size of its literal text, and the
// names it holds. pathKeys maps each key a path starts from to the first such path;
// held is every such key plus the fields an operand reads; holder maps each held
// names it holds. pathHeads maps each head a path starts from to the first such path;
// held is every such head plus the fields an operand reads; holder maps each held
// name to the first reader holding it, for error messages.
type formatOps struct {
ops []op
grow int
pathKeys map[string]string
pathHeads map[string]string
held map[string]bool
holder map[string]string
heldLocal bool // some held name is not a reference path, so expand makes a hold
@@ -396,19 +396,19 @@ func (c *formatOps) holdName(a arm, label string) {
c.held = map[string]bool{}
c.holder = map[string]string{}
}
c.held[a.key] = true
if !isRef(a.key) || len(a.tail) == 0 {
c.held[a.head] = true
if !isRef(a.head) || len(a.tail) == 0 {
c.heldLocal = true
}
if _, named := c.holder[a.key]; !named {
c.holder[a.key] = label
if _, named := c.holder[a.head]; !named {
c.holder[a.head] = label
}
if len(a.tail) > 0 {
if c.pathKeys == nil {
c.pathKeys = map[string]string{}
if c.pathHeads == nil {
c.pathHeads = map[string]string{}
}
if _, named := c.pathKeys[a.key]; !named {
c.pathKeys[a.key] = a.spelling
if _, named := c.pathHeads[a.head]; !named {
c.pathHeads[a.head] = a.spelling
}
}
}
+2 -2
View File
@@ -109,7 +109,7 @@ type renderEdge struct {
operand string
}
func (e renderEdge) readsRef() bool { return isRef(e.read.key) }
func (e renderEdge) readsRef() bool { return isRef(e.read.head) }
// reached names an edge as the author spelled it.
func (e renderEdge) reached() string {
@@ -133,7 +133,7 @@ func renderEdges(n node) []renderEdge {
case *template:
var es []renderEdge
add := func(a arm, operand string) {
c := n.head(a.key)
c := n.head(a.head)
if c == nil {
return
}
+9 -4
View File
@@ -41,7 +41,12 @@ type holdSet struct {
type renderScope struct {
set *holdSet
group string
row tablePin
row renderedRow
}
type renderedRow struct {
t *table
index int
}
// in is the scope t renders in: its draw group where it names one, else its caller's.
@@ -57,9 +62,9 @@ func (sc renderScope) in(t *template) renderScope {
func (sc renderScope) at(n node, pins *pinSet) renderScope {
switch n := n.(type) {
case *tableRow:
sc.row = tablePin{n.t, pins.mustRow(n.t)}
sc.row = renderedRow{n.t, pins.mustRow(n.t)}
case *tableColumn:
sc.row = tablePin{n.t, pins.mustRow(n.t)}
sc.row = renderedRow{n.t, pins.mustRow(n.t)}
}
return sc
}
@@ -69,7 +74,7 @@ func (sc renderScope) rowOf(t *table) int {
if sc.row.t != t {
panic(internalError("a column of %s renders in a scope holding no row of it", t.segment))
}
return sc.row.row
return sc.row.index
}
func (sc renderScope) groupHold() *groupHold {
+1 -1
View File
@@ -761,7 +761,7 @@ func TestRepeatedBareTokenOfAHeldNameIsRejected(t *testing.T) {
}
func TestReadFieldPanicsOnAPathItCannotHold(t *testing.T) {
a := arm{spelling: "w.x", key: "w", tail: []string{"x"}, path: "w.x"}
a := arm{spelling: "w.x", head: "w", tail: []string{"x"}, path: "w.x"}
for name, format := range map[string]string{
"unheld arm with a path": `{"format":"{w}","w":{"format":"{x}","x":"1"}}`,
"held arm with no hold": `{"format":"{w.x}","w":{"format":"{x}","x":"1"}}`,
+19 -19
View File
@@ -15,9 +15,9 @@ func heldCheck(path string, n node) error {
if !ok || len(t.compiled.held) == 0 {
return nil
}
readers := pathKeyReaders(t.compiled.ops, t.compiled.pathKeys)
readers := pathHeadReaders(t.compiled.ops, t.compiled.pathHeads)
for _, name := range heldNames(t) {
if _, isPath := t.compiled.pathKeys[name]; isPath && isRef(name) {
if _, isPath := t.compiled.pathHeads[name]; isPath && isRef(name) {
continue // held for the render: drawFence compares every read of it, by path, across the render and its groups
}
if err := checkNameHeld(t, name, readers); err != nil {
@@ -36,8 +36,8 @@ func heldNames(t *template) []string {
names = append(names, name)
}
sort.Slice(names, func(i, j int) bool {
_, pi := t.compiled.pathKeys[names[i]]
_, pj := t.compiled.pathKeys[names[j]]
_, pi := t.compiled.pathHeads[names[i]]
_, pj := t.compiled.pathHeads[names[j]]
if pi != pj {
return !pi
}
@@ -51,12 +51,12 @@ func heldNames(t *template) []string {
// produces.
func heldNodes(t *template, name string, readers []reader) map[node]bool {
held := map[node]bool{}
if _, isPath := t.compiled.pathKeys[name]; !isPath {
if _, isPath := t.compiled.pathHeads[name]; !isPath {
operandDraw(t.head(name), held)
return held
}
for _, r := range readers {
if r.a.key == name {
if r.a.head == name {
coverPath(t.head(name), r.a.tail, held)
}
}
@@ -71,10 +71,10 @@ func checkNameHeld(t *template, name string, readers []reader) error {
if len(held) == 0 {
return nil // a fixed head holds nothing to reach
}
reader, isPath := t.compiled.pathKeys[name]
reader, isPath := t.compiled.pathHeads[name]
seen := map[node]bool{}
for _, e := range renderEdges(t) {
if e.read.key == name || !renders(e.to, held, seen) {
if e.read.head == name || !renders(e.to, held, seen) {
continue
}
if isPath {
@@ -89,7 +89,7 @@ func checkNameHeld(t *template, name string, readers []reader) error {
func operandReader(t *template, name string) string {
for _, o := range t.compiled.ops {
for _, a := range o.operands {
if a.key == name {
if a.head == name {
return o.fn
}
}
@@ -175,8 +175,8 @@ func renders(n node, want, seen map[node]bool) bool {
// disagree. Reads are compared in sorted order, so which pair is reported does not
// depend on where the tokens sit.
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
func checkNoOverlap(ops []op, pathKeys map[string]string) error {
names := pathKeyReaders(ops, pathKeys)
func checkNoOverlap(ops []op, pathHeads map[string]string) error {
names := pathHeadReaders(ops, pathHeads)
// Stable over one format-order scan, so two readers of one name (a token and a
// calc operand both naming "p") are reported as the format writes them.
sort.SliceStable(names, func(i, j int) bool { return names[i].a.path < names[j].a.path })
@@ -190,25 +190,25 @@ func checkNoOverlap(ops []op, pathKeys map[string]string) error {
return nil
}
// reader is one way a format reaches a path key, and how to name it.
// reader is one way a format reaches a path head, and how to name it.
type reader struct {
a arm
label string
}
// pathKeyReaders lists every way a format reaches a sibling path key, in the order the
// pathHeadReaders lists every way a format reaches a sibling path head, in the order the
// format writes them. An operand renders its field, so it names a level exactly
// as a token does; one scan finds both, which is what puts them in one order.
func pathKeyReaders(ops []op, pathKeys map[string]string) []reader {
func pathHeadReaders(ops []op, pathHeads map[string]string) []reader {
var names []reader
for _, o := range ops {
for _, a := range o.operands {
if _, isPathKey := pathKeys[a.key]; isPathKey && !isRef(a.key) {
if _, isPathHead := pathHeads[a.head]; isPathHead && !isRef(a.head) {
names = append(names, reader{a, fmt.Sprintf("%s operand %q", o.fn, a.spelling)})
}
}
for _, a := range o.arms {
if _, isPathKey := pathKeys[a.key]; isPathKey && !isRef(a.key) {
if _, isPathHead := pathHeads[a.head]; isPathHead && !isRef(a.head) {
names = append(names, reader{a, "token {" + a.spelling + "}"})
}
}
@@ -224,11 +224,11 @@ func checkNoRepeatedRead(c formatOps) error {
count := map[string]int{}
for _, o := range c.ops {
for _, a := range o.arms {
if len(a.tail) > 0 || !c.held[a.key] {
if len(a.tail) > 0 || !c.held[a.head] {
continue
}
if count[a.key]++; count[a.key] > 1 {
return fmt.Errorf("token {%s} is repeated, and %s holds %q to one draw per expansion; write {%s} once", a.spelling, c.holder[a.key], a.key, a.spelling)
if count[a.head]++; count[a.head] > 1 {
return fmt.Errorf("token {%s} is repeated, and %s holds %q to one draw per expansion; write {%s} once", a.spelling, c.holder[a.head], a.head, a.spelling)
}
}
}
+6 -6
View File
@@ -81,12 +81,12 @@ type templateLink struct {
func (*template) isNode() {}
// head is the node an arm's key names: a sibling field, or a reference's category.
func (t *template) head(key string) node {
if isRef(key) {
return t.link.refHeads[key]
// head is the node an arm's head names: a sibling field, or a reference's category.
func (t *template) head(name string) node {
if isRef(name) {
return t.link.refHeads[name]
}
return t.fields[key]
return t.fields[name]
}
// compile converts parsed JSON — a category or an inline template — into a node tree,
@@ -229,7 +229,7 @@ func (t *template) fixedText() (string, bool) {
// compiled reads.
func compileFormat(toks []formatToken, refs map[string]refBinding) (formatOps, error) {
c := compileOps(toks, refs)
if err := checkNoOverlap(c.ops, c.pathKeys); err != nil {
if err := checkNoOverlap(c.ops, c.pathHeads); err != nil {
return c, err
}
return c, checkNoRepeatedRead(c)
+1 -1
View File
@@ -143,7 +143,7 @@ func (s *readSurvey) walk(n node, at surveyAt) {
func (s *readSurvey) edge(from node, e renderEdge, at surveyAt) {
if e.readsRef() {
a := e.read
tr := tableReadOf(from.(*template).head(a.key), a, e.to)
tr := tableReadOf(from.(*template).head(a.head), a, e.to)
if k := (readKey{at.group, a.path, s.pinsID(&at.pins), s.pinsID(&at.wholePins)}); !s.read[k] {
s.read[k] = true
s.reads = append(s.reads, pathRead{at, a, tr})
+1 -1
View File
@@ -35,7 +35,7 @@ func wantGathered(t *testing.T, tables map[string]*table, label string, gathered
}
seen := map[traceKey]bool{}
trace := func(group, table string, row int, a arm) {
if !isRef(a.key) {
if !isRef(a.head) {
return
}
r := pathRead{at: surveyAt{group: group}, a: a}
+1 -1
View File
@@ -146,7 +146,7 @@ func columnReadOf(t *template, link templateLink) *columnRead {
return nil
}
a := splitArm(name, link.refs)
target, isTemplate := link.refHeads[a.key].(*template)
target, isTemplate := link.refHeads[a.head].(*template)
if !isTemplate || !target.isRecord || len(a.tail) != 1 {
return nil
}
+6 -6
View File
@@ -62,7 +62,7 @@ func render(s *session, n node, sc renderScope) string {
case *nullItem:
return ""
case *table:
sc.row = tablePin{n, n.drawRow(s)}
sc.row = renderedRow{n, n.drawRow(s)}
return expand(s, n.formatTemplate, sc)
case *tableRow:
return expand(s, n.t.formatTemplate, sc)
@@ -159,17 +159,17 @@ func expand(s *session, t *template, sc renderScope) string {
// read one row, either read twice gives one value, and a shown operand is the operand
// computed. Every other name is drawn afresh, so {word} {word} still draws twice.
func readField(s *session, t *template, held *hold, sc renderScope, a arm) readValue {
if isRef(a.key) && len(a.tail) > 0 {
if isRef(a.head) && len(a.tail) > 0 {
return readReference(s, t, sc, a)
}
if sc.set.trace != nil {
traceRead(sc.set.trace, t, sc, a)
}
if !t.compiled.held[a.key] {
if !t.compiled.held[a.head] {
if len(a.tail) > 0 {
panic(internalError("%q reads a path into %q, which the expansion does not hold", a.spelling, a.key))
panic(internalError("%q reads a path into %q, which the expansion does not hold", a.spelling, a.head))
}
return readValue{text: render(s, t.head(a.key), sc)}
return readValue{text: render(s, t.head(a.head), sc)}
}
if held == nil {
panic(internalError("%q reads a name the expansion holds, with no hold to keep it in", a.spelling))
@@ -191,7 +191,7 @@ func readHeld(s *session, t *template, held *hold, pins *pinSet, sc renderScope,
if r, done := held.value[a.path]; done {
return r
}
leaf := drawPath(t.head(a.key), a.tail, a.key, &pathDraw{s: s, held: held, pins: pins, a: &a})
leaf := drawPath(t.head(a.head), a.tail, a.head, &pathDraw{s: s, held: held, pins: pins, a: &a})
if pins != nil {
sc = sc.at(leaf, pins)
}
+2 -2
View File
@@ -330,8 +330,8 @@ func (t *table) compileRowFormat(format string) error {
}
for _, name := range tok.names {
a := splitArm(name, nil)
if _, ok := t.col[a.key]; !ok && !isRef(a.key) && a.key != "" {
return fmt.Errorf("format names no column %q of %s; the columns are %v", a.key, t.file, t.header)
if _, ok := t.col[a.head]; !ok && !isRef(a.head) && a.head != "" {
return fmt.Errorf("format names no column %q of %s; the columns are %v", a.head, t.file, t.header)
}
}
}
+2 -2
View File
@@ -1063,7 +1063,7 @@ func TestProbeReportsTheTablesADrawPins(t *testing.T) {
{"region[12].name", "region=region[12]"},
} {
a := splitArm(c.path, nil)
head := f.root.children[a.key].(*table)
head := f.root.children[a.head].(*table)
tr := tableReadOf(head, a, nil)
var sels []string
for _, s := range tr.sels {
@@ -1075,7 +1075,7 @@ func TestProbeReportsTheTablesADrawPins(t *testing.T) {
probed, drawn := tr.pins, tr.drawn
for i := 0; i < 20; i++ {
var pins pinSet
drawPath(head, a.tail, a.key, &pathDraw{s: f.rand, pins: &pins})
drawPath(head, a.tail, a.head, &pathDraw{s: f.rand, pins: &pins})
want := map[*table]bool{}
pins.each(func(tbl *table, _ int) {
if _, selected := probed.pinned(tbl); !selected {
-3
View File
@@ -28,9 +28,6 @@ ranking `readSurvey.walk` hardest and all seven the unit they would least modify
This round, in order:
- Rename `arm.key` to `head`, as `template.head` reads it, leaving `key` the table's key
column; give `renderScope.row` a type that is not `tablePin`, since it pins nothing;
and name `value.go` by the `valueProof` it holds.
- Delete the prose the seats found empty: "kept whole on purpose"
(`format.go`, `calc.go`), the second copy of the concrete-session rationale
(`table.drawRow`, `pick`), the doc comments restating `isSelector`, `selectorOf`,
+1 -1
View File
@@ -164,7 +164,7 @@ func (p *valueProof) proveTemplate(t *template) proven {
case o.kind == nameRead:
var leaves []node
for _, a := range o.arms {
leaves = append(leaves, pathLeaves(t.head(a.key), a.tail)...)
leaves = append(leaves, pathLeaves(t.head(a.head), a.tail)...)
}
return p.proveUnion(leaves)
case name == "calc":