Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
17 changed files with 120 additions and 118 deletions
Showing only changes of commit e257dc0e6e - Show all commits
+1 -1
View File
@@ -249,7 +249,7 @@ func checkRenders(s nodeScope) error {
fence := &drawCheck{} fence := &drawCheck{}
refs := false refs := false
if err := s(func(path string, n node) error { if err := s(func(path string, n node) error {
if t, ok := n.(*template); ok && len(t.refs) > 0 { if t, ok := n.(*template); ok && len(t.link.refs) > 0 {
refs = true refs = true
} }
return fence.checkDrawGroup(path, n) return fence.checkDrawGroup(path, n)
+3 -3
View File
@@ -76,7 +76,7 @@ func checkColumns(s nodeScope) error {
checked[column] = true checked[column] = true
items, _ := columnItems(column) items, _ := columnItems(column)
for _, it := range items { for _, it := range items {
if r := it.readsColumn; r != nil { 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.key[1:], r.a.tail[0], r.column); err != nil {
return err return err
} }
@@ -119,8 +119,8 @@ func columnDatatype(n node) DataType {
// itemDatatype is the datatype a column item declares, else that of the column it reads. // itemDatatype is the datatype a column item declares, else that of the column it reads.
func itemDatatype(t *template) DataType { func itemDatatype(t *template) DataType {
if t.datatype == DataTypeString && t.readsColumn != nil { if t.datatype == DataTypeString && t.link.readsColumn != nil {
return columnDatatype(t.readsColumn.column) return columnDatatype(t.link.readsColumn.column)
} }
return t.datatype return t.datatype
} }
+2 -2
View File
@@ -24,9 +24,9 @@ package fejkdata
// start one each, `FakeStruct` one per record, and `expandAnew` one per // start one each, `FakeStruct` one per record, and `expandAnew` one per
// iteration of a `template.repeat`. // iteration of a `template.repeat`.
// - hold — keeping one draw of a name, so every route to it reads that value: // - hold — keeping one draw of a name, so every route to it reads that value:
// `template.held`, `hold`, `groupHold`, `readField`. // `formatOps.held`, `hold`, `groupHold`, `readField`.
// - draw group — reference draws held apart inside one render: // - draw group — reference draws held apart inside one render:
// `template.drawGroup` as the data spells it, `template.drawGroupKey` as a // `template.drawGroup` as the data spells it, `templateLink.drawGroupKey` as a
// render reads it, `holdSet`, `renderScope`. // render reads it, `holdSet`, `renderScope`.
// - pin — fixing which row of a table the render uses, which the draw fences replay: // - pin — fixing which row of a table the render uses, which the draw fences replay:
// `pinSet`, `pinSet.pin`, `table.drawIn`. // `pinSet`, `pinSet.pin`, `table.drawIn`.
+17 -13
View File
@@ -83,7 +83,7 @@ func (c *drawCheck) checkDraws(path string, n node) error {
func checkOwnFamily(t *template) error { func checkOwnFamily(t *template) error {
own := t.table own := t.table
if own == nil { if own == nil {
own = t.cellOf own = t.cell.table
} }
if own == nil { if own == nil {
return nil return nil
@@ -116,7 +116,7 @@ func checkOwnFamily(t *template) error {
func renderDraws(t *template) *drawWalk { func renderDraws(t *template) *drawWalk {
w := newDrawWalk() w := newDrawWalk()
for _, e := range renderEdges(t) { for _, e := range renderEdges(t) {
w.edge(t, e, drawAt{group: t.drawGroupKey, route: drawRoute{e.reached(), e.label}}) w.edge(t, e, drawAt{group: t.link.drawGroupKey, route: drawRoute{e.reached(), e.label}})
} }
return w return w
} }
@@ -189,7 +189,7 @@ func repeats(n node) bool {
func grouped(n node) bool { func grouped(n node) bool {
t, isTemplate := n.(*template) t, isTemplate := n.(*template)
return isTemplate && t.drawGroupKey != "" return isTemplate && t.link.drawGroupKey != ""
} }
// readsTable reports whether a reference of n names a table, whose draw the render's // readsTable reports whether a reference of n names a table, whose draw the render's
@@ -209,7 +209,7 @@ func refRead(n node, label string) (arm, bool) {
if !isTemplate { if !isTemplate {
return arm{}, false return arm{}, false
} }
a := splitArm(label, t.refs) a := splitArm(label, t.link.refs)
return a, isRef(a.key) return a, isRef(a.key)
} }
@@ -219,7 +219,7 @@ func checkColumnDraws(t *template, columns []string) error { return columnDraws(
func columnDraws(t *template, columns []string) *drawWalk { func columnDraws(t *template, columns []string) *drawWalk {
w := newDrawWalk() w := newDrawWalk()
for _, name := range columns { for _, name := range columns {
w.walk(t.fields[name], drawAt{group: t.drawGroupKey, route: drawRoute{spelling: fmt.Sprintf("column %q", name)}}) w.walk(t.fields[name], drawAt{group: t.link.drawGroupKey, route: drawRoute{spelling: fmt.Sprintf("column %q", name)}})
} }
return w return w
} }
@@ -311,25 +311,29 @@ func (w *drawWalk) walk(n node, at drawAt) {
if repeats(n) { if repeats(n) {
return return
} }
if t, isTemplate := n.(*template); isTemplate && t.drawGroupKey != "" { if t, isTemplate := n.(*template); isTemplate && t.link.drawGroupKey != "" {
at.group = t.drawGroupKey at.group = t.link.drawGroupKey
} }
if t, isTable := n.(*table); isTable { if t, isTable := n.(*table); isTable {
at.wholeTable = t at.wholeTable = t
} }
for _, e := range renderEdges(n) { for _, e := range renderEdges(n) {
cell, isCell := e.to.(*template) to, isTemplate := e.to.(*template)
cell := cellSite{}
if isTemplate {
cell = to.cell
}
switch { switch {
case !isCell || cell.cellOf == nil: case cell.table == nil:
w.edge(n, e, at) w.edge(n, e, at)
case cell.cellOf == at.wholeTable: case cell.table == at.wholeTable:
in := at in := at
in.wholePins = at.wholePins.clone() in.wholePins = at.wholePins.clone()
in.wholePins.add(cell.cellOf, cell.cellRow) in.wholePins.add(cell.table, cell.row)
w.edge(n, e, in) w.edge(n, e, in)
case at.pins.clash(cell.cellOf, cell.cellRow) == nil: case at.pins.clash(cell.table, cell.row) == nil:
in := at in := at
in.pins = at.pins.entered(cell.cellOf, cell.cellRow) in.pins = at.pins.entered(cell.table, cell.row)
w.edge(n, e, in) w.edge(n, e, in)
} }
} }
+1 -1
View File
@@ -136,7 +136,7 @@ func renderEdges(n node) []renderEdge {
es = append(es, renderEdge{leaf, a.spelling, operand}) es = append(es, renderEdge{leaf, a.spelling, operand})
} }
} }
for _, o := range n.ops { for _, o := range n.compiled.ops {
for _, a := range o.operands { for _, a := range o.operands {
add(a, o.fn) add(a, o.fn)
} }
+5 -5
View File
@@ -37,7 +37,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) readV
if s.trace != nil { if s.trace != nil {
traceRead(s.trace, t, sc, a) traceRead(s.trace, t, sc, a)
} }
if !t.held[a.key] { if !t.compiled.held[a.key] {
if len(a.tail) > 0 { if len(a.tail) > 0 {
panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.spelling, a.key)) panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.spelling, a.key))
} }
@@ -73,8 +73,8 @@ func traceRead(trace *renderTrace, t *template, sc renderScope, a arm) {
var table string var table string
var row int var row int
switch { switch {
case t.cellOf != nil: case t.cell.table != nil:
table, row = t.cellOf.path, t.cellRow table, row = t.cell.table.path, t.cell.row
case t.table != nil: case t.table != nil:
table, row = t.table.path, sc.rowOf(t.table) table, row = t.table.path, sc.rowOf(t.table)
} }
@@ -89,8 +89,8 @@ func renderLeaf(s *session, n node, sc renderScope) readValue {
case *nullItem: case *nullItem:
return readValue{null: true} return readValue{null: true}
case *template: case *template:
if leaf.readsColumn != nil { if leaf.link.readsColumn != nil {
return readField(s, leaf, nil, sc.in(leaf), leaf.readsColumn.a) return readField(s, leaf, nil, sc.in(leaf), leaf.link.readsColumn.a)
} }
} }
return readValue{text: render(s, n, sc)} return readValue{text: render(s, n, sc)}
+12 -12
View File
@@ -12,12 +12,12 @@ import (
// docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences // docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences
func heldCheck(path string, n node) error { func heldCheck(path string, n node) error {
t, ok := n.(*template) t, ok := n.(*template)
if !ok || len(t.held) == 0 { if !ok || len(t.compiled.held) == 0 {
return nil return nil
} }
readers := pathKeyReaders(t.ops, t.pathKeys) readers := pathKeyReaders(t.compiled.ops, t.compiled.pathKeys)
for _, name := range heldNames(t) { for _, name := range heldNames(t) {
if _, isPath := t.pathKeys[name]; isPath && isRef(name) { if _, isPath := t.compiled.pathKeys[name]; isPath && isRef(name) {
continue // held for the render: drawCheck compares every read of it, by path, across the render and its groups continue // held for the render: drawCheck compares every read of it, by path, across the render and its groups
} }
if err := checkNameHeld(t, name, readers); err != nil { if err := checkNameHeld(t, name, readers); err != nil {
@@ -31,13 +31,13 @@ func heldCheck(path string, n node) error {
// in name order: a level read both ways is reported by the operand's fence, and // in name order: a level read both ways is reported by the operand's fence, and
// which overlap is reported does not vary. // which overlap is reported does not vary.
func heldNames(t *template) []string { func heldNames(t *template) []string {
names := make([]string, 0, len(t.held)) names := make([]string, 0, len(t.compiled.held))
for name := range t.held { for name := range t.compiled.held {
names = append(names, name) names = append(names, name)
} }
sort.Slice(names, func(i, j int) bool { sort.Slice(names, func(i, j int) bool {
_, pi := t.pathKeys[names[i]] _, pi := t.compiled.pathKeys[names[i]]
_, pj := t.pathKeys[names[j]] _, pj := t.compiled.pathKeys[names[j]]
if pi != pj { if pi != pj {
return !pi return !pi
} }
@@ -51,12 +51,12 @@ func heldNames(t *template) []string {
// produces. // produces.
func heldNodes(t *template, name string, readers []reader) map[node]bool { func heldNodes(t *template, name string, readers []reader) map[node]bool {
held := map[node]bool{} held := map[node]bool{}
if _, isPath := t.pathKeys[name]; !isPath { if _, isPath := t.compiled.pathKeys[name]; !isPath {
operandDraw(t.head(name), held) operandDraw(t.head(name), held)
return held return held
} }
for _, r := range readers { for _, r := range readers {
if a := splitArm(r.spelling, t.refs); a.key == name { if a := splitArm(r.spelling, t.link.refs); a.key == name {
coverPath(t.head(name), a.tail, held) coverPath(t.head(name), a.tail, held)
} }
} }
@@ -71,10 +71,10 @@ func checkNameHeld(t *template, name string, readers []reader) error {
if len(held) == 0 { if len(held) == 0 {
return nil // a fixed head holds nothing to reach return nil // a fixed head holds nothing to reach
} }
reader, isPath := t.pathKeys[name] reader, isPath := t.compiled.pathKeys[name]
seen := map[node]bool{} seen := map[node]bool{}
for _, e := range renderEdges(t) { for _, e := range renderEdges(t) {
if splitArm(e.label, t.refs).key == name || !renders(e.to, held, seen) { if splitArm(e.label, t.link.refs).key == name || !renders(e.to, held, seen) {
continue continue
} }
if isPath { if isPath {
@@ -87,7 +87,7 @@ func checkNameHeld(t *template, name string, readers []reader) error {
// operandReader names the builtin whose operand holds name. // operandReader names the builtin whose operand holds name.
func operandReader(t *template, name string) string { func operandReader(t *template, name string) string {
for _, o := range t.ops { for _, o := range t.compiled.ops {
for _, a := range o.operands { for _, a := range o.operands {
if a.key == name { if a.key == name {
return o.fn return o.fn
+2 -2
View File
@@ -42,8 +42,8 @@ func expandAnew(s *session, t *template) string {
// in is the scope t renders in: its draw group where it names one, else its caller's. // in is the scope t renders in: its draw group where it names one, else its caller's.
func (sc renderScope) in(t *template) renderScope { func (sc renderScope) in(t *template) renderScope {
if t.drawGroupKey != "" { if t.link.drawGroupKey != "" {
sc.group = t.drawGroupKey sc.group = t.link.drawGroupKey
} }
return sc return sc
} }
+1 -2
View File
@@ -168,7 +168,6 @@ func linkNodeRefs(scope nodeScope, root map[string]node) error {
if !ok { if !ok {
return nil return nil
} }
t.keyDrawGroup("")
for _, name := range refTokens(t.tokens) { for _, name := range refTokens(t.tokens) {
sigil, rest, err := refShape(name) sigil, rest, err := refShape(name)
if err != nil { if err != nil {
@@ -178,6 +177,6 @@ func linkNodeRefs(scope nodeScope, root map[string]node) error {
return fmt.Errorf("%s: reference {%s}: an inline template has no folder; write {/%s}", path, name, rest) return fmt.Errorf("%s: reference {%s}: an inline template has no folder; write {/%s}", path, name, rest)
} }
} }
return linkTemplateRefs(nil, path, "", t, root) return linkTemplate(nil, path, "", t, root)
}) })
} }
+22 -33
View File
@@ -41,9 +41,10 @@ func (*nullItem) isNode() {}
// template renders a format string, substituting {tokens} from fields. A bare // template renders a format string, substituting {tokens} from fields. A bare
// JSON string is a template with no fields. repeat (default 1) renders that format // JSON string is a template with no fields. repeat (default 1) renders that format
// that many times and joins the results with separator (default ""), each render // that many times and joins the results with separator (default ""), each render
// an independent pick. Every format compiles in `linkTemplateRefs`. // an independent pick. Every format compiles in `linkTemplate`.
type template struct { type template struct {
// Filled by `compileString`, `compileTemplate` and `table.compileRowFormat`: // Filled by `compileString`, `compileTemplate`, `table.checkCells` and
// `table.compileRowFormat`:
format string format string
tokens []formatToken tokens []formatToken
fields map[string]node fields map[string]node
@@ -54,24 +55,21 @@ type template struct {
fromString bool // written as a JSON string rather than an object fromString bool // written as a JSON string rather than an object
isRecord bool // compiled at the top without a repeat, so its fields are record columns isRecord bool // compiled at the top without a repeat, so its fields are record columns
table *table // the table whose format this is, whose columns are the fields table *table // the table whose format this is, whose columns are the fields
cell cellSite
// Filled by `checkCells`: // Filled by `linkTemplate`, from the assembled tree:
cellOf *table // the table whose cell this is link templateLink
cellRow int // the row the cell sits in compiled formatOps
}
// Filled by `template.compileFormat` at link, from `template.tokens` and `template.refs`: // cellSite is the table cell a template is compiled from; table is nil for any other template.
ops []op // what expand walks type cellSite struct {
grow int // minimum output size, to size the render buffer table *table
// pathKeys maps each key a dotted path in the format starts from to one path token row int
// reading it, which is the half of an overlap the fences name. nil when the }
// format takes no path.
pathKeys map[string]string
// held is every name drawn once per expansion: the path keys above, plus the
// siblings a {calc()} reads. nil when the format holds nothing (see expand).
held map[string]bool
heldLocal bool // some held name is kept by the expansion itself, so expand makes its hold
// Filled by `linkTemplateRefs` and `keyDrawGroup`, from the assembled tree: // templateLink is what a template's references resolve to in the assembled tree.
type templateLink struct {
refs map[string]refBinding // each reference the format reads -> what it resolves to refs map[string]refBinding // each reference the format reads -> what it resolves to
refHeads map[string]node // each refBinding.key -> the category it names refHeads map[string]node // each refBinding.key -> the category it names
readsColumn *columnRead // set when the format is one reference alone reading a record's column readsColumn *columnRead // set when the format is one reference alone reading a record's column
@@ -83,7 +81,7 @@ func (*template) isNode() {}
// head is the node an arm's key names: a sibling field, or a reference's category. // head is the node an arm's key names: a sibling field, or a reference's category.
func (t *template) head(key string) node { func (t *template) head(key string) node {
if isRef(key) { if isRef(key) {
return t.refHeads[key] return t.link.refHeads[key]
} }
return t.fields[key] return t.fields[key]
} }
@@ -220,15 +218,14 @@ func (t *template) fixedText() (string, bool) {
return "", false return "", false
} }
// compileFormat compiles the format into ops, and applies the fences that need the // compileFormat compiles a format into ops, and applies the fences that need the
// compiled reads. // compiled reads.
func (t *template) compileFormat() error { func compileFormat(toks []formatToken, refs map[string]refBinding) (formatOps, error) {
c := compileOps(t.tokens, t.refs) c := compileOps(toks, refs)
t.ops, t.grow, t.pathKeys, t.held, t.heldLocal = c.ops, c.grow, c.pathKeys, c.held, c.heldLocal if err := checkNoOverlap(c.ops, c.pathKeys); err != nil {
if err := checkNoOverlap(t.ops, t.pathKeys); err != nil { return c, err
return err
} }
return checkNoRepeatedRead(c) return c, checkNoRepeatedRead(c)
} }
func compileChoice(items []any, pos position) (node, error) { func compileChoice(items []any, pos position) (node, error) {
@@ -442,14 +439,6 @@ func drawGroupOf(m map[string]any, repeat int) (string, error) {
return name, nil return name, nil
} }
// keyDrawGroup keys t's draw group by the category t sits in, "" for an inline template, so a name
// is local to its category.
func (t *template) keyDrawGroup(category string) {
if t.drawGroup != "" {
t.drawGroupKey = category + "/" + t.drawGroup
}
}
// weightOf reads a node's "weight" (default 1) from its raw JSON form. Only // weightOf reads a node's "weight" (default 1) from its raw JSON form. Only
// template objects carry weight; a present one must be finite and positive. // template objects carry weight; a present one must be finite and positive.
func weightOf(raw any) (float64, error) { func weightOf(raw any) (float64, error) {
+1 -1
View File
@@ -94,7 +94,7 @@ func TestEveryFormatCompilesAtLink(t *testing.T) {
compiled := map[string]bool{} compiled := map[string]bool{}
if err := eachNode(n, "t", func(path string, m node) error { if err := eachNode(n, "t", func(path string, m node) error {
if tm, ok := m.(*template); ok { if tm, ok := m.(*template); ok {
compiled[path+" "+tm.format] = tm.ops != nil compiled[path+" "+tm.format] = tm.compiled.ops != nil
} }
return nil return nil
}); err != nil { }); err != nil {
+35 -24
View File
@@ -74,52 +74,63 @@ func refSegments(name string, folder []string) ([]string, error) {
func linkRefs(root map[string]node) error { func linkRefs(root map[string]node) error {
return eachTemplate(root, func(folder []string, path string, t *template) error { return eachTemplate(root, func(folder []string, path string, t *template) error {
category := strings.Join(strings.Split(path, ".")[:len(folder)+1], ".") category := strings.Join(strings.Split(path, ".")[:len(folder)+1], ".")
t.keyDrawGroup(category) return linkTemplate(folder, path, category, t, root)
return linkTemplateRefs(folder, path, category, t, root)
}) })
} }
func linkTemplateRefs(folder []string, path, category string, t *template, root map[string]node) error { // linkTemplate resolves t's references in category, "" for an inline template, and
if t.cellOf != nil { // compiles its format against them.
path = fmt.Sprintf("%s, line %d", path, t.cellRow+2) func linkTemplate(folder []string, path, category string, t *template, root map[string]node) error {
if t.cell.table != nil {
path = fmt.Sprintf("%s, line %d", path, t.cell.row+2)
} }
if names := refTokens(t.tokens); len(names) > 0 { link, err := t.resolveLink(folder, path, category, root)
if err := t.bindRefs(folder, path, category, names, root); err != nil { if err != nil {
return err return err
} }
} compiled, err := compileFormat(t.tokens, link.refs)
if err := t.compileFormat(); err != nil { if err != nil {
return fmt.Errorf("%s: %w", path, err) return fmt.Errorf("%s: %w", path, err)
} }
t.readsColumn = columnReadOf(t) t.link, t.compiled = link, compiled
return nil return nil
} }
// bindRefs fills refs and refHeads, refusing a reference to t's own category: // resolveLink binds every reference t reads, refusing one to t's own category, and keys
// t's draw group by that category, so a name is local to it:
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load // docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
func (t *template) bindRefs(folder []string, path, category string, names []string, root map[string]node) error { func (t *template) resolveLink(folder []string, path, category string, root map[string]node) (templateLink, error) {
t.refs = make(map[string]refBinding, len(names)) var link templateLink
t.refHeads = make(map[string]node, len(names)) if t.drawGroup != "" {
link.drawGroupKey = category + "/" + t.drawGroup
}
names := refTokens(t.tokens)
if len(names) == 0 {
return link, nil
}
link.refs = make(map[string]refBinding, len(names))
link.refHeads = make(map[string]node, len(names))
for _, name := range names { for _, name := range names {
segments, err := refSegments(name, folder) segments, err := refSegments(name, folder)
if err != nil { if err != nil {
return fmt.Errorf("%s: reference {%s}: %w", path, name, err) return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err)
} }
categorySegs, target, tail, err := resolveCategory(root, segments) categorySegs, target, tail, err := resolveCategory(root, segments)
if err != nil { if err != nil {
return fmt.Errorf("%s: reference {%s}: %w", path, name, err) return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err)
} }
key := "/" + strings.Join(categorySegs, ".") key := "/" + strings.Join(categorySegs, ".")
if category != "" && key == "/"+category { if category != "" && key == "/"+category {
return fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", path, name) return link, fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", path, name)
} }
if err := checkPath(target, tail, key); err != nil { if err := checkPath(target, tail, key); err != nil {
return fmt.Errorf("%s: reference {%s}: %w", path, name, err) return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err)
} }
t.refHeads[key] = target link.refHeads[key] = target
t.refs[name] = refBinding{key, tail} link.refs[name] = refBinding{key, tail}
} }
return nil link.readsColumn = columnReadOf(t, link)
return link, nil
} }
// columnRead is a record's column read by a format of that one reference alone, which is the // columnRead is a record's column read by a format of that one reference alone, which is the
@@ -129,13 +140,13 @@ type columnRead struct {
column node column node
} }
func columnReadOf(t *template) *columnRead { func columnReadOf(t *template, link templateLink) *columnRead {
name, lone := loneRef(t.tokens) name, lone := loneRef(t.tokens)
if !lone || t.repeat != 1 { if !lone || t.repeat != 1 {
return nil return nil
} }
a := splitArm(name, t.refs) a := splitArm(name, link.refs)
target, isTemplate := t.head(a.key).(*template) target, isTemplate := link.refHeads[a.key].(*template)
if !isTemplate || !target.isRecord || len(a.tail) != 1 { if !isTemplate || !target.isRecord || len(a.tail) != 1 {
return nil return nil
} }
+7 -7
View File
@@ -75,7 +75,7 @@ func render(s *session, n node, sc renderScope) string {
return expand(s, n, sc) return expand(s, n, sc)
} }
var b strings.Builder var b strings.Builder
b.Grow(n.repeat * (n.grow + len(n.separator))) b.Grow(n.repeat * (n.compiled.grow + len(n.separator)))
for i := 0; i < n.repeat; i++ { for i := 0; i < n.repeat; i++ {
if i > 0 { if i > 0 {
b.WriteString(n.separator) b.WriteString(n.separator)
@@ -104,19 +104,19 @@ func pick(s *session, c *choice) node {
// cannot fail. // cannot fail.
func expand(s *session, t *template, sc renderScope) string { func expand(s *session, t *template, sc renderScope) string {
var b strings.Builder var b strings.Builder
b.Grow(t.grow) b.Grow(t.compiled.grow)
// One draw per held name, for this expansion only: a nested template and each // One draw per held name, for this expansion only: a nested template and each
// repeat iteration get their own, since each is its own expansion. A reference // repeat iteration get their own, since each is its own expansion. A reference
// path reads the render's hold in sc instead. // path reads the render's hold in sc instead.
var held *hold var held *hold
if t.heldLocal { if t.compiled.heldLocal {
held = &hold{ held = &hold{
variant: make(map[string]node, len(t.held)), variant: make(map[string]node, len(t.compiled.held)),
value: make(map[string]readValue, len(t.held)), value: make(map[string]readValue, len(t.compiled.held)),
} }
} }
for i := range t.ops { for i := range t.compiled.ops {
o := &t.ops[i] o := &t.compiled.ops[i]
switch o.kind { switch o.kind {
case literalRun: case literalRun:
b.WriteString(o.lit) b.WriteString(o.lit)
+2 -2
View File
@@ -40,8 +40,8 @@ func TestGrowIsALowerBound(t *testing.T) {
} }
for i := 0; i < 50; i++ { for i := 0; i < 50; i++ {
var set holdSet var set holdSet
if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.grow { if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.compiled.grow {
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow) t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.compiled.grow)
} }
} }
} }
+1 -1
View File
@@ -101,7 +101,7 @@ func readsFact(n node) string {
collect(cell) collect(cell)
} }
case *template: case *template:
for _, b := range n.refs { for _, b := range n.link.refs {
set[strings.TrimPrefix(b.key, "/")] = true set[strings.TrimPrefix(b.key, "/")] = true
} }
for _, field := range n.fields { for _, field := range n.fields {
+2 -3
View File
@@ -308,15 +308,14 @@ func (t *table) checkCells() error {
if strings.IndexByte(cell, '{') < 0 && strings.IndexByte(cell, '}') < 0 { if strings.IndexByte(cell, '{') < 0 && strings.IndexByte(cell, '}') < 0 {
continue continue
} }
n, err := compileString(cell) toks, err := parseChecked(cell, nil)
if err != nil { if err != nil {
return fmt.Errorf("line %d, %s: %w", row+2, t.header[col], err) return fmt.Errorf("line %d, %s: %w", row+2, t.header[col], err)
} }
if t.cellTemplates == nil { if t.cellTemplates == nil {
t.cellTemplates = map[int]*template{} t.cellTemplates = map[int]*template{}
} }
t.cellTemplates[i] = n.(*template) t.cellTemplates[i] = &template{format: cell, tokens: toks, repeat: 1, fromString: true, cell: cellSite{t, row}}
t.cellTemplates[i].cellOf, t.cellTemplates[i].cellRow = t, row
} }
return nil return nil
} }
+5 -5
View File
@@ -34,7 +34,7 @@ func (p *valueProof) checkDatatype(path string, n node) error {
if !ok || t.datatype == DataTypeString { if !ok || t.datatype == DataTypeString {
return nil return nil
} }
if r := t.readsColumn; r != nil && columnDatatype(r.column) == t.datatype { if r := t.link.readsColumn; r != nil && columnDatatype(r.column) == t.datatype {
return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, t.datatype) return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, t.datatype)
} }
if reason := p.proveColumnItem(t).not[t.datatype]; reason != "" { if reason := p.proveColumnItem(t).not[t.datatype]; reason != "" {
@@ -45,10 +45,10 @@ func (p *valueProof) checkDatatype(path string, n node) error {
// proveColumnItem proves a column item: what it renders, or, when it is a column it reads, that column. // proveColumnItem proves a column item: what it renders, or, when it is a column it reads, that column.
func (p *valueProof) proveColumnItem(t *template) proven { func (p *valueProof) proveColumnItem(t *template) proven {
if t.readsColumn == nil { if t.link.readsColumn == nil {
return p.prove(t) return p.prove(t)
} }
return p.proveColumn(t.readsColumn.column) return p.proveColumn(t.link.readsColumn.column)
} }
// proveColumn proves a column over what its items draw, a null item marking it null rather than // proveColumn proves a column over what its items draw, a null item marking it null rather than
@@ -149,12 +149,12 @@ func (p *valueProof) proveTemplate(t *template) proven {
return unproven(fmt.Sprintf("%q carries a repeat, which composes text rather than one value", t.format)) return unproven(fmt.Sprintf("%q carries a repeat, which composes text rather than one value", t.format))
case fixed: case fixed:
return literalValue(lit) return literalValue(lit)
case len(t.ops) != 1: case len(t.compiled.ops) != 1:
v := unproven(notOneValue(t.format, typedCalls)) v := unproven(notOneValue(t.format, typedCalls))
v.notOperand = notOneValue(t.format, operandCalls) v.notOperand = notOneValue(t.format, operandCalls)
return v return v
} }
o := t.ops[0] o := t.compiled.ops[0]
body, name, args := o.body, o.fn, o.args body, name, args := o.body, o.fn, o.args
switch _, isTransform := transforms[name]; { switch _, isTransform := transforms[name]; {
case o.kind == fieldAlternation: case o.kind == fieldAlternation: