Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
+36
-25
@@ -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
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err := t.compileFormat(); err != nil {
|
compiled, err := compileFormat(t.tokens, link.refs)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user