Draw every {…} afresh, keep a pick only by name, and delete drawGroup with the hold and draw fences
This commit is contained in:
@@ -1015,22 +1015,19 @@ doc.go the package doc, and the vocabulary the package is written in
|
||||
fejkdata.go Generator, New, options, List
|
||||
shipped.go the embedded data set, and loading a shipped category on the first call reaching it
|
||||
shippedindex.go the index of shipped categories, generated from data/
|
||||
node.go the node model and JSON -> node compilation, with its fence on a nested drawGroup
|
||||
node.go the node model and JSON -> node compilation
|
||||
table.go tables: the rows TSV, its options and links, row selection and draws
|
||||
name.go names: {ref as n} bindings, the scopes they live in, and the reads they answer
|
||||
name.go names: {x as n} bindings, the scopes they live in, and the reads they answer
|
||||
namefence.go the load fences over names
|
||||
pick.go a render's named picks: the frames of the name scopes rendering, and the reads kept under a name
|
||||
path.go the dotted-path walks with their selectors, and proving a path resolves
|
||||
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
|
||||
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
|
||||
struct.go structs: FakeStruct, fake tags, and a field's Go type as its column's datatype
|
||||
struct.go structs: FakeStruct, fake tags, the one pick a struct's path tags into one category read, and a field's Go type as its column's datatype
|
||||
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link
|
||||
format.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
|
||||
draws.go the memo a hold, draw group or named pick keeps its draws in; and a render's draws and the scope it reads its reference paths and names through
|
||||
holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways
|
||||
drawfence.go the load fences over reference draws and their refusals: a drawGroup's, a render's and a record's columns judged on what they read, and a table reaching its own family
|
||||
reads.go the load-time fold of what a render reads, and what a path reads of a table family
|
||||
pins.go the pin set: the table rows a render fixes, which the draw fences replay
|
||||
draws.go the memo a named pick keeps its draws in, and the scope a render reads names and rows through
|
||||
pins.go the pin set: the table rows one path or one named pick fixes
|
||||
reference.go reference sigils, and resolving references across the tree
|
||||
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
|
||||
builtins.go the {name()} function registry and its implementations
|
||||
|
||||
@@ -259,9 +259,11 @@ func (b binding) bind() error {
|
||||
if err := b.link(); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.scopeFence != nil {
|
||||
if err := b.scopeFence(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !b.typedByGo {
|
||||
if err := checkColumns(b.scope); err != nil {
|
||||
return err
|
||||
@@ -280,29 +282,5 @@ func checkNodeFences(s nodeScope) error {
|
||||
if err := s(func(path string, n node) error { return repeatCheck(path, n, mem) }); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s(heldCheck); err != nil {
|
||||
return err
|
||||
}
|
||||
fence := &drawFence{fold: newReadFold()}
|
||||
refs := false
|
||||
if err := s(func(path string, n node) error {
|
||||
if t, ok := n.(*template); ok && len(t.link.refs) > 0 {
|
||||
refs = true
|
||||
}
|
||||
return fence.checkDrawGroup(path, n)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if refs {
|
||||
if err := s(checkOwnFamily); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s(fence.checkDraws); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s(fence.checkRecordDraws); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return s((&valueProof{}).checkDatatype)
|
||||
}
|
||||
|
||||
@@ -17,30 +17,19 @@ package fejkdata
|
||||
// Vocabulary
|
||||
//
|
||||
// - draw — one pick from a choice, or one row taken from a table: `pick`,
|
||||
// `resolveChoice`, `table.drawRow`.
|
||||
// `resolveChoice`, `table.drawRow`. Every {…} draws afresh, bar a read of a name.
|
||||
// - expansion — one render of one format: `expand`.
|
||||
// - render — what owns one `renderDraws`, and so what one reference draw spans:
|
||||
// - render — one walk of the tree from an entry point, over one `frameStack`:
|
||||
// `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake`
|
||||
// start one each, `FakeStruct` one per record, and `expandAnew` one per
|
||||
// iteration of a `template.repeat`.
|
||||
// - hold — keeping one draw of a field, so every route to it reads that value:
|
||||
// an expansion holds a sibling path's head and an operand's field, its draw
|
||||
// group a reference path's: `formatOps.held` and `heldCheck` an expansion's,
|
||||
// `readReference` a draw group's.
|
||||
// start one each, and `FakeStruct` one per record.
|
||||
// - named pick — the draw a token such as {/person as p} binds to a name, drawn
|
||||
// on its first read and kept while its scope renders: a category, or one
|
||||
// repeat iteration: `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
|
||||
// - memo — what a hold, a draw group or a named pick keeps its draws in, the
|
||||
// variant drawn at each level and the value each path read: `drawMemo`,
|
||||
// `readMemo`.
|
||||
// - draw group — reference draws kept apart inside one render:
|
||||
// `template.drawGroup` as the data spells it, `templateLink.drawGroupKey` as a
|
||||
// render reads it, `renderDraws`, `groupDraws`, `renderScope`.
|
||||
// - pin — fixing which row of a table the render uses, which the fences replay:
|
||||
// - memo — what a named pick keeps its draws in, the variant drawn at each level
|
||||
// and the value each path read: `drawMemo`.
|
||||
// - pin — fixing which row of a table one path, or one named pick, uses:
|
||||
// `pinSet`, `pinSet.pin`, `table.drawIn`.
|
||||
// - family — a table and every table reaching it through a chain of
|
||||
// `table.parentT`, named by the root that chain ends at: `table.familyRoot`.
|
||||
// - whole — a read of a table with no selector and no descent, {/city}, landing on
|
||||
// the `table` itself, so it draws a row apart from every pin: `branches.wholePins`.
|
||||
// `table.parentT`.
|
||||
// - fence — a load-time check, run over each category, template or struct as it
|
||||
// loads or compiles, so rendering a compiled tree cannot fail.
|
||||
|
||||
-252
@@ -1,252 +0,0 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// drawFence fences each template of a scope as a render of its own, over one fold of what every
|
||||
// node reads.
|
||||
// docs/decisions.md#a-render-shares-one-reference-draw-per-category-per-group
|
||||
// docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences
|
||||
type drawFence struct {
|
||||
fold *readFold
|
||||
}
|
||||
|
||||
// readSurvey is what one render reads, gathered at load for check to compare.
|
||||
type readSurvey struct {
|
||||
reads []pathRead
|
||||
clash *pinClash
|
||||
}
|
||||
|
||||
// pinClash is the read, lowest by how it is named, whose selectors clash with a row its route pinned.
|
||||
type pinClash struct {
|
||||
route readRoute
|
||||
spelling string
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *pinClash) named() string { return c.route.spelled(c.spelling) }
|
||||
|
||||
func (c *pinClash) before(o *pinClash) bool {
|
||||
if c.named() != o.named() {
|
||||
return c.named() < o.named()
|
||||
}
|
||||
return c.err.Error() < o.err.Error()
|
||||
}
|
||||
|
||||
// survey is the fold's result as a render in group: every read not inside a nested draw group
|
||||
// draws in it, and one read gathered by several routes is kept once.
|
||||
func survey(reads []pathRead, group string) *readSurvey {
|
||||
s := &readSurvey{}
|
||||
for _, r := range reads {
|
||||
if r.clash != nil {
|
||||
if c := (&pinClash{r.route, r.a.spelling, r.clash}); s.clash == nil || c.before(s.clash) {
|
||||
s.clash = c
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r.group == "" {
|
||||
r.group = group
|
||||
}
|
||||
s.reads = append(s.reads, r)
|
||||
}
|
||||
s.reads = distinct(s.reads)
|
||||
return s
|
||||
}
|
||||
|
||||
// checkDrawGroup refuses a draw group that splits nothing: one whose render reads every reference
|
||||
// path inside a repeat or a nested draw group, which draw apart from it whatever it names.
|
||||
func (f *drawFence) checkDrawGroup(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || t.drawGroup == "" {
|
||||
return nil
|
||||
}
|
||||
for _, r := range f.fold.rootReads(t) {
|
||||
if r.group == "" && r.draws() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s: drawGroup %q splits nothing, since nothing it renders reads a reference path outside a repeat or a nested drawGroup; drop it", path, t.drawGroup)
|
||||
}
|
||||
|
||||
func (f *drawFence) checkDraws(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
reads := f.fold.rootReads(t)
|
||||
if !anyDraws(reads) {
|
||||
return nil
|
||||
}
|
||||
if err := survey(reads, t.link.drawGroupKey).check(); err != nil {
|
||||
return fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRecordDraws fences the columns of a record — a template compiled at the top without a
|
||||
// repeat — so a load proves the record view of it as well as the string view.
|
||||
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
|
||||
func (f *drawFence) checkRecordDraws(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || !t.isRecord {
|
||||
return nil
|
||||
}
|
||||
reads := f.fold.columnReads(t, sortedNames(t.fields))
|
||||
if !anyDraws(reads) {
|
||||
return nil
|
||||
}
|
||||
if err := survey(reads, t.link.drawGroupKey).check(); err != nil {
|
||||
return fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkOwnFamily refuses a table's format or cell that reads, however many templates
|
||||
// away and through a repeat or a draw group too, a table of its own family: a whole
|
||||
// read draws its row without pinning it, so the family would draw apart from the row
|
||||
// it renders, whichever draws the reaching template holds.
|
||||
// docs/decisions.md#a-table-never-reaches-its-own-family-by-any-route
|
||||
func checkOwnFamily(path string, n node) error {
|
||||
t, isTemplate := n.(*template)
|
||||
if !isTemplate || t.site.table == nil {
|
||||
return nil
|
||||
}
|
||||
own := t.site.table
|
||||
seen := map[node]bool{}
|
||||
var find func(n node) (renderEdge, *table, bool)
|
||||
find = func(n node) (renderEdge, *table, bool) {
|
||||
if seen[n] {
|
||||
return renderEdge{}, nil, false
|
||||
}
|
||||
seen[n] = true
|
||||
for _, e := range renderEdges(n) {
|
||||
if e.readsRef() {
|
||||
if familyTable, isTable := n.(*template).head(e.read.head).(*table); isTable && familyTable.familyRoot() == own.familyRoot() {
|
||||
return e, familyTable, true
|
||||
}
|
||||
}
|
||||
if e, familyTable, found := find(e.to); found {
|
||||
return e, familyTable, true
|
||||
}
|
||||
}
|
||||
return renderEdge{}, nil, false
|
||||
}
|
||||
if e, familyTable, found := find(t); found {
|
||||
return fmt.Errorf("%s: %s reads %s, a table of its own family, which a bare read of %s would draw apart from the row it renders; read the family from a template beside it, or add the value as a column", path, e.reached(), familyTable.segment, own.segment)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// draws reports whether a read's draw is its group's to answer for: a path, or a table read whole,
|
||||
// whose row the group draws.
|
||||
func (r pathRead) draws() bool { return len(r.a.tail) > 0 || r.tr != nil }
|
||||
|
||||
func anyDraws(reads []pathRead) bool {
|
||||
for _, r := range reads {
|
||||
if r.draws() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func repeats(n node) bool {
|
||||
t, isTemplate := n.(*template)
|
||||
return isTemplate && t.repeat > 1
|
||||
}
|
||||
|
||||
// check refuses what one draw per reference path cannot answer for: a read selecting a row its route
|
||||
// pinned another of, and any two reads that render together yet conflict.
|
||||
func (s *readSurvey) check() error {
|
||||
if c := s.clash; c != nil {
|
||||
return fmt.Errorf("%s: %w; read the family from a template beside it, or add the value as a column", c.named(), c.err)
|
||||
}
|
||||
sort.SliceStable(s.reads, func(i, j int) bool {
|
||||
if s.reads[i].group != s.reads[j].group {
|
||||
return s.reads[i].group < s.reads[j].group
|
||||
}
|
||||
return s.reads[i].a.path < s.reads[j].a.path
|
||||
})
|
||||
for i, a := range s.reads {
|
||||
for _, b := range s.reads[i+1:] {
|
||||
if !coRender(a, b) {
|
||||
continue
|
||||
}
|
||||
if err := conflict(a, b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// coRender reports whether two reads can render together: in one draw group, and under no two
|
||||
// rows of one table, since one read renders one row of it.
|
||||
func coRender(a, b pathRead) bool {
|
||||
return a.group == b.group && !a.branches.pins.differs(&b.branches.pins) && !a.branches.wholePins.differs(&b.branches.wholePins)
|
||||
}
|
||||
|
||||
// conflict refuses two reads rendering together, a before b by path, that one draw per reference
|
||||
// path cannot answer for. Pairs find every conflict a replay of all the reads would: a row clashes
|
||||
// with one pinned table, and the read that pinned it holds that pin itself, with its ancestors.
|
||||
func conflict(a, b pathRead) error {
|
||||
for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
|
||||
if err := drawsApart(pair[0], pair[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if a.tr == nil || b.tr == nil {
|
||||
return nil
|
||||
}
|
||||
d := a.tr.pins.clone()
|
||||
if err := b.tr.replay(&d); err != nil {
|
||||
return fmt.Errorf("%s: %w; select the same rows in every path into the family, or draw them apart with a drawGroup", b.route.spelled(b.a.spelling), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawsApart refuses x drawing apart from what y reads: a level beside a path y takes into it, a
|
||||
// table x reads whole beside a path y takes into its family, and a table x draws that y pins, since
|
||||
// which token renders first would then decide the row. A level landing on a row renders the row its
|
||||
// selectors pin, the one a path into it reads too.
|
||||
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
|
||||
func drawsApart(x, y pathRead) error {
|
||||
if strings.HasPrefix(y.a.path, x.a.path+".") && !(x.tr != nil && x.tr.landsRow) {
|
||||
return overlapError(x.route, x.a.spelling, y)
|
||||
}
|
||||
if x.tr == nil || y.tr == nil || x.tr.headTable.familyRoot() != y.tr.headTable.familyRoot() {
|
||||
return nil
|
||||
}
|
||||
if len(x.a.tail) == 0 && len(y.a.tail) > 0 {
|
||||
return overlapError(x.route, x.a.spelling, y)
|
||||
}
|
||||
drawn := x.tr.drawnOf(&y.tr.pins)
|
||||
if drawn == nil {
|
||||
return nil
|
||||
}
|
||||
if s, ok := y.tr.selected(x.tr.headTable); ok && len(x.tr.sels) == 0 {
|
||||
tail := x.a.tail
|
||||
if s.t != x.tr.headTable {
|
||||
tail = append([]string{x.tr.headTable.segment}, tail...)
|
||||
}
|
||||
return fmt.Errorf("%s draws %s, which %s selects a row of; write {%s}, or draw them apart with a drawGroup",
|
||||
x.route.spelled(x.a.spelling), drawn.segment, y.route.spelled(y.a.spelling), joinSegments(append([]string{s.spelling}, tail...)))
|
||||
}
|
||||
return fmt.Errorf("%s draws %s, which %s selects a row of; select that row in both, or draw them apart with a drawGroup",
|
||||
x.route.spelled(x.a.spelling), drawn.segment, y.route.spelled(y.a.spelling))
|
||||
}
|
||||
|
||||
func overlapError(route readRoute, ref string, into pathRead) error {
|
||||
apart := ""
|
||||
switch {
|
||||
case route.spelling == into.route.spelling:
|
||||
case isRef(into.route.label):
|
||||
apart = fmt.Sprintf(", or move {%s} into a field with a drawGroup", into.route.label)
|
||||
default:
|
||||
apart = fmt.Sprintf(", or give %s a drawGroup", into.route.spelling)
|
||||
}
|
||||
return fmt.Errorf("%s renders its own draw of what %s reads a path through; name the fields you want instead%s", route.spelled(ref), into.route.spelled(into.a.spelling), apart)
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package fejkdata
|
||||
|
||||
import "strings"
|
||||
|
||||
// drawMemo is what a hold, a draw group or a named pick has drawn: the variant each level was
|
||||
// drawn as, so every path under it reads one variant; the value each read produced, by its path,
|
||||
// so the same read written twice reads one value; the frames a read opens for the name
|
||||
// scopes around where it lands (enter); and the rows drawn by each step down after a "..", by level.
|
||||
// drawMemo is what a named pick has drawn: the variant each level was drawn as, so every path
|
||||
// under it reads one variant; the value each read produced, by its path, so the same read written
|
||||
// twice reads one value; the frames a read opens for the name scopes around where it lands
|
||||
// (enter); and the rows drawn by each step down after a "..", by level.
|
||||
type drawMemo struct {
|
||||
variant map[string]node
|
||||
value map[string]readValue
|
||||
@@ -30,38 +28,18 @@ func (m *drawMemo) stepDownPins(pins *pinSet, t *table, levels []string, at int)
|
||||
return p
|
||||
}
|
||||
|
||||
// groupDraws is what one render's draw group has drawn for its reference paths: a memo, and the
|
||||
// table rows those paths pinned.
|
||||
type groupDraws struct {
|
||||
memo drawMemo
|
||||
pins pinSet
|
||||
}
|
||||
|
||||
// readValue is what one read drew: its text, and whether it landed on a null.
|
||||
type readValue struct {
|
||||
text string
|
||||
null bool
|
||||
}
|
||||
|
||||
// renderTrace is a test's view of the fields a render reads; nil outside a test.
|
||||
type renderTrace func(group string, row renderedRow, a arm)
|
||||
|
||||
// renderDraws is one render's reference draws: the unnamed draw group's, and each named one's;
|
||||
// a test's trace of its reads; and the frames of the name scopes rendering.
|
||||
type renderDraws struct {
|
||||
unnamed groupDraws
|
||||
named map[string]*groupDraws
|
||||
trace renderTrace
|
||||
frameStack *frameStack
|
||||
}
|
||||
|
||||
// renderScope is where a render reads its reference paths: its reference draws, in the draw group of the
|
||||
// template rendering; and row, the row of the table rendering, which its columns read. It passes
|
||||
// by value, and what is read from it reaches a map key, an interface or a func value only as a
|
||||
// copy; else sc, and with it every render's draws, moves to the heap.
|
||||
// renderScope is where a render reads: the frames of the name scopes rendering; and row, the row
|
||||
// of the table rendering, which its columns read. It passes by value, and what is read from it
|
||||
// reaches a map key, an interface or a func value only as a copy; else sc, and with it every
|
||||
// render's frames, moves to the heap.
|
||||
type renderScope struct {
|
||||
draws *renderDraws
|
||||
group string
|
||||
frames *frameStack
|
||||
row renderedRow
|
||||
// base is the depth of the frame stack when the read rendering the category entered it,
|
||||
// below which no frame is the category's.
|
||||
@@ -77,14 +55,6 @@ type renderedRow struct {
|
||||
index int
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if t.link.drawGroupKey != "" {
|
||||
sc.group = t.link.drawGroupKey
|
||||
}
|
||||
return sc
|
||||
}
|
||||
|
||||
// at is the scope n, the leaf of a path that pinned its rows in pins, renders in: where n is a
|
||||
// table's row or column, at the row pins holds for that table.
|
||||
func (sc renderScope) at(n node, pins *pinSet) renderScope {
|
||||
@@ -104,18 +74,3 @@ func (sc renderScope) rowOf(t *table) int {
|
||||
}
|
||||
return sc.row.index
|
||||
}
|
||||
|
||||
func (sc renderScope) groupDraws() *groupDraws {
|
||||
if sc.group == "" {
|
||||
return &sc.draws.unnamed
|
||||
}
|
||||
d, drew := sc.draws.named[sc.group]
|
||||
if !drew {
|
||||
if sc.draws.named == nil {
|
||||
sc.draws.named = map[string]*groupDraws{}
|
||||
}
|
||||
d = &groupDraws{}
|
||||
sc.draws.named[strings.Clone(sc.group)] = d
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ func checkToken(t formatToken, fields map[string]node) ([]unboundRead, error) {
|
||||
}
|
||||
return checkReads(t, fields, true)
|
||||
case nameBind:
|
||||
return nil, checkBind(t)
|
||||
return nil, checkBind(t, fields)
|
||||
case nameRead:
|
||||
unbound, err := checkReads(t, fields, false)
|
||||
if err != nil {
|
||||
@@ -285,17 +285,14 @@ func checkReads(t formatToken, fields map[string]node, operands bool) ([]unbound
|
||||
return unbound, nil
|
||||
}
|
||||
|
||||
// checkBind proves a {ref as name} token is spelled once, binds a reference, and names a valid
|
||||
// name that is no option.
|
||||
func checkBind(t formatToken) error {
|
||||
// checkBind proves a {x as name} token is spelled once, binds a reference or a path into a
|
||||
// field, and names a valid name that is no option.
|
||||
func checkBind(t formatToken, fields map[string]node) error {
|
||||
ref, name := t.boundRef, t.bound
|
||||
if trimmed := strings.TrimSpace(ref) + asWord + strings.TrimSpace(name); trimmed != t.body {
|
||||
return fmt.Errorf("token {%s}: write {%s}", t.body, trimmed)
|
||||
}
|
||||
if !isRef(ref) {
|
||||
return fmt.Errorf("token {%s}: %q is no reference; a name binds a pick of a reference, which starts with /, . or ..", t.body, ref)
|
||||
}
|
||||
if _, _, err := refShape(ref); err != nil {
|
||||
if err := checkBound(ref, fields); err != nil {
|
||||
return fmt.Errorf("token {%s}: %w", t.body, err)
|
||||
}
|
||||
if name == "" {
|
||||
@@ -310,6 +307,20 @@ func checkBind(t formatToken) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkBound proves what a binding picks is a reference or a path into a field of the binding
|
||||
// template.
|
||||
func checkBound(ref string, fields map[string]node) error {
|
||||
if isRef(ref) {
|
||||
_, _, err := refShape(ref)
|
||||
return err
|
||||
}
|
||||
missing, err := checkArm(ref, fields, false)
|
||||
if missing {
|
||||
return fmt.Errorf("%w; a name binds a pick of a field, or of a reference, which starts with /, . or ..", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// checkArm validates one sibling name or path against a template's fields.
|
||||
// wholeToken says the name is the token's entire body, so {/name} would render
|
||||
// the same value and can be offered as the reference spelling. missing says no
|
||||
@@ -376,9 +387,7 @@ func checkNoRepeatedArm(body string, names []string) error {
|
||||
}
|
||||
|
||||
// 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
|
||||
// makes the arm a held draw: a reference path's for the render, any other for the
|
||||
// expansion.
|
||||
// `template.head` resolves and the tail of a dotted path into it.
|
||||
type arm struct {
|
||||
spelling string // as written, for messages
|
||||
head string
|
||||
@@ -388,7 +397,6 @@ type arm struct {
|
||||
path string // head and tail, the one path every way of writing this read shares
|
||||
steps []pathStep
|
||||
leaves []node // every node the path may land on, one per variant it passes
|
||||
cover node // what holding the path pins: the first choice it passes, else its leaf
|
||||
kind armKind
|
||||
named *nameBinding // namedRead: the binding of the name it reads through
|
||||
}
|
||||
@@ -398,15 +406,9 @@ type armKind uint8
|
||||
|
||||
const (
|
||||
freshRead armKind = iota // drawn afresh at every read
|
||||
heldRead // drawn once per expansion, kept in its hold
|
||||
refPathRead // drawn once per draw group, kept in its memo
|
||||
namedRead // read through a name, kept in its pick
|
||||
)
|
||||
|
||||
func (a arm) isRefPath() bool {
|
||||
return isRef(a.head) && len(a.tail) > 0
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -472,37 +474,10 @@ type op struct {
|
||||
operands []arm
|
||||
}
|
||||
|
||||
// formatOps is a compiled format: its ops, the size of its literal text, and the
|
||||
// names its expansion holds: every head a path that is not a reference starts from,
|
||||
// plus the fields an operand reads.
|
||||
// formatOps is a compiled format: its ops, and the size of its literal text.
|
||||
type formatOps struct {
|
||||
ops []op
|
||||
grow int
|
||||
held map[string]firstReach
|
||||
}
|
||||
|
||||
// firstReach is how a format first reaches a held name: the reader holding it, for
|
||||
// error messages, and the first path starting from it, "" where none does.
|
||||
type firstReach struct {
|
||||
holder string
|
||||
path string
|
||||
}
|
||||
|
||||
func (c *formatOps) holdName(a arm, label string) {
|
||||
if a.isRefPath() {
|
||||
return
|
||||
}
|
||||
if c.held == nil {
|
||||
c.held = map[string]firstReach{}
|
||||
}
|
||||
h, seen := c.held[a.head]
|
||||
if !seen {
|
||||
h.holder = label
|
||||
}
|
||||
if len(a.tail) > 0 && h.path == "" {
|
||||
h.path = a.spelling
|
||||
}
|
||||
c.held[a.head] = h
|
||||
}
|
||||
|
||||
func (c *formatOps) function(tok formatToken, refs map[string]refBinding, isName func(string) bool) {
|
||||
@@ -511,10 +486,7 @@ func (c *formatOps) function(tok formatToken, refs map[string]refBinding, isName
|
||||
a := splitArm(operand, refs)
|
||||
if isName(a.head) {
|
||||
a.kind = namedRead
|
||||
operands = append(operands, a)
|
||||
continue
|
||||
}
|
||||
c.holdName(a, fmt.Sprintf("%s operand %q", tok.fn, operand))
|
||||
operands = append(operands, a)
|
||||
}
|
||||
c.ops = append(c.ops, op{formatToken: tok, call: builtins[tok.fn].prep(tok.args), operands: operands})
|
||||
@@ -526,10 +498,6 @@ func (c *formatOps) field(tok formatToken, refs map[string]refBinding, isName fu
|
||||
arms[i] = splitArm(name, refs)
|
||||
if isName(arms[i].head) {
|
||||
arms[i].kind = namedRead
|
||||
continue
|
||||
}
|
||||
if len(arms[i].tail) > 0 {
|
||||
c.holdName(arms[i], "token {"+arms[i].spelling+"}")
|
||||
}
|
||||
}
|
||||
c.ops = append(c.ops, op{formatToken: tok, arms: arms})
|
||||
|
||||
@@ -2,6 +2,7 @@ package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
)
|
||||
|
||||
@@ -86,25 +87,10 @@ func sortedNames[V any](m map[string]V) []string {
|
||||
}
|
||||
|
||||
// renderEdge is a child a node renders into, labelled by what reaches it (a field
|
||||
// name, reference, or choice index) for a readable cycle report. read is the linked
|
||||
// arm a template's edge reads, zero on any other. operand names the builtin when the
|
||||
// label is its operand rather than a token, so an error can name it the way the
|
||||
// author wrote it.
|
||||
// name, reference, or choice index) for a readable cycle report.
|
||||
type renderEdge struct {
|
||||
to node
|
||||
label string
|
||||
read arm
|
||||
operand string
|
||||
}
|
||||
|
||||
func (e renderEdge) readsRef() bool { return isRef(e.read.head) }
|
||||
|
||||
// reached names an edge as the author spelled it.
|
||||
func (e renderEdge) reached() string {
|
||||
if e.operand != "" {
|
||||
return fmt.Sprintf("%s operand %q", e.operand, e.label)
|
||||
}
|
||||
return "{" + e.label + "}"
|
||||
}
|
||||
|
||||
// renderEdges lists the children rendering n recurses into, mirroring render: a
|
||||
@@ -120,17 +106,11 @@ func renderEdges(n node) []renderEdge {
|
||||
return es
|
||||
case *template:
|
||||
var es []renderEdge
|
||||
add := func(a arm, operand string) {
|
||||
for _, leaf := range a.leaves {
|
||||
es = append(es, renderEdge{leaf, a.spelling, a, operand})
|
||||
}
|
||||
}
|
||||
for _, o := range n.compiled.ops {
|
||||
for _, a := range o.operands {
|
||||
add(a, o.fn)
|
||||
for _, a := range slices.Concat(o.operands, o.arms) {
|
||||
for _, leaf := range a.leaves {
|
||||
es = append(es, renderEdge{leaf, a.spelling})
|
||||
}
|
||||
for _, a := range o.arms {
|
||||
add(a, "")
|
||||
}
|
||||
}
|
||||
return es
|
||||
|
||||
-243
@@ -1,243 +0,0 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// heldCheck rejects a format reading one held draw two ways, and every route to a held
|
||||
// name except the readers of its draw. An expansion holds one draw of that name;
|
||||
// anything else that renders it draws again, and the two disagree.
|
||||
// docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences
|
||||
func heldCheck(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || len(t.compiled.held) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := checkTemplateHold(t); err != nil {
|
||||
return fmt.Errorf("%s: %w", t.site.label(path), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTemplateHold(t *template) error {
|
||||
if err := checkNoOverlap(t.compiled); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkNoRepeatedRead(t.compiled); err != nil {
|
||||
return err
|
||||
}
|
||||
readers := pathHeadReaders(t.compiled)
|
||||
for _, name := range heldNames(t) {
|
||||
if err := checkNameHeld(t, name, readers); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// heldNames lists a template's held names, operands' first, then paths', each
|
||||
// in name order: a level read both ways is reported by the operand's fence.
|
||||
func heldNames(t *template) []string {
|
||||
names := make([]string, 0, len(t.compiled.held))
|
||||
for name := range t.compiled.held {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Slice(names, func(i, j int) bool {
|
||||
pi := t.compiled.held[names[i]].path != ""
|
||||
pj := t.compiled.held[names[j]].path != ""
|
||||
if pi != pj {
|
||||
return !pi
|
||||
}
|
||||
return names[i] < names[j]
|
||||
})
|
||||
return names
|
||||
}
|
||||
|
||||
// heldNodes is what the hold of name answers for: a path pins the levels it
|
||||
// passes through and the leaf it lands on, an operand exactly the value its render
|
||||
// produces.
|
||||
func heldNodes(t *template, name string, readers []reader) map[node]bool {
|
||||
held := map[node]bool{}
|
||||
if t.compiled.held[name].path == "" {
|
||||
operandDraw(t.head(name), held)
|
||||
return held
|
||||
}
|
||||
for _, r := range readers {
|
||||
if r.a.head == name {
|
||||
cover(r.a.cover, held, false)
|
||||
}
|
||||
}
|
||||
return held
|
||||
}
|
||||
|
||||
// checkNameHeld rejects every route to what name's hold pins except the readers
|
||||
// holding it. One seen set across the edges: a node that cannot reach the level
|
||||
// cannot reach it by another route either, so it is walked once.
|
||||
func checkNameHeld(t *template, name string, readers []reader) error {
|
||||
held := heldNodes(t, name, readers)
|
||||
if len(held) == 0 {
|
||||
return nil // a fixed head holds nothing to reach
|
||||
}
|
||||
path := t.compiled.held[name].path
|
||||
seen := map[node]bool{}
|
||||
for _, e := range renderEdges(t) {
|
||||
if e.read.head == name || !renders(e.to, held, seen) {
|
||||
continue
|
||||
}
|
||||
if path != "" {
|
||||
return fmt.Errorf("%s reaches %q by a second route, where {%s} holds it to one draw; name the fields you want instead", e.reached(), name, path)
|
||||
}
|
||||
call, operand := operandReader(t, name)
|
||||
if e.operand != "" {
|
||||
return fmt.Errorf("%s renders %q, which {%s} holds to one draw for this expansion; stop %s reaching %q", e.reached(), name, call, e.reached(), name)
|
||||
}
|
||||
return fmt.Errorf("%s renders %q, which {%s} holds to one draw for this expansion; drop %s, or write {%s} in this format to read that draw", e.reached(), name, call, e.reached(), operand)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// operandReader spells the call whose operand holds name, and the operand, as written.
|
||||
func operandReader(t *template, name string) (call, operand string) {
|
||||
for _, o := range t.compiled.ops {
|
||||
for _, a := range o.operands {
|
||||
if a.head == name && !a.isRefPath() {
|
||||
return o.body, a.spelling
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// cover collects a level and everything contained in it. A fixed string outside a
|
||||
// choice is left out — it cannot disagree with itself — but inside one each
|
||||
// variant carries its own, so there it counts.
|
||||
func cover(n node, into map[node]bool, inChoice bool) {
|
||||
if isFixed(n) && !inChoice {
|
||||
return
|
||||
}
|
||||
into[n] = true
|
||||
_, isChoice := n.(*choice)
|
||||
for _, c := range contained(n) {
|
||||
cover(c.node, into, inChoice || isChoice)
|
||||
}
|
||||
}
|
||||
|
||||
// operandDraw collects what one held draw of an operand answers for: the operand
|
||||
// and what rendering it settles inside itself. The builtin renders its operand
|
||||
// whole, so that draw fixes every value the render produced, and a second route to
|
||||
// any of them disagrees with it.
|
||||
//
|
||||
// The walk stops at a reference edge, which is where the operand's own value ends
|
||||
// and a shared source begins: two names referencing one category are two draws, the
|
||||
// same rule {word} {word} follows.
|
||||
func operandDraw(n node, into map[node]bool) {
|
||||
if isFixed(n) {
|
||||
return
|
||||
}
|
||||
if into[n] {
|
||||
return
|
||||
}
|
||||
into[n] = true
|
||||
for _, e := range renderEdges(n) {
|
||||
if e.readsRef() {
|
||||
continue
|
||||
}
|
||||
operandDraw(e.to, into)
|
||||
}
|
||||
}
|
||||
|
||||
// isFixed is a string that varies nothing: fixed text with no fields to read into.
|
||||
func isFixed(n node) bool {
|
||||
t, ok := n.(*template)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, fixed := t.fixedText()
|
||||
return fixed && len(t.fields) == 0
|
||||
}
|
||||
|
||||
// renders reports whether rendering n can reach anything in want, following the
|
||||
// same edges expand does.
|
||||
func renders(n node, want, seen map[node]bool) bool {
|
||||
if want[n] {
|
||||
return true
|
||||
}
|
||||
if seen[n] {
|
||||
return false
|
||||
}
|
||||
seen[n] = true
|
||||
for _, e := range renderEdges(n) {
|
||||
if renders(e.to, want, seen) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkNoOverlap rejects a format that both renders a sibling level and reads a path
|
||||
// into it — {p} beside {p.first}, {p.addr} beside {p.addr.city}. The path reads the
|
||||
// level's held draw while rendering the level expands it afresh, so their values would
|
||||
// disagree.
|
||||
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
|
||||
func checkNoOverlap(c formatOps) error {
|
||||
names := pathHeadReaders(c)
|
||||
// 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 })
|
||||
for i, level := range names {
|
||||
for _, path := range names[i+1:] {
|
||||
if strings.HasPrefix(path.a.path, level.a.path+".") {
|
||||
return fmt.Errorf("%s renders a level that {%s} reads a path into; name the fields you want instead", level.label, path.a.spelling)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reader is one way a format reaches a path head, and how to name it.
|
||||
type reader struct {
|
||||
a arm
|
||||
label string
|
||||
}
|
||||
|
||||
// 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 pathHeadReaders(c formatOps) []reader {
|
||||
var names []reader
|
||||
for _, o := range c.ops {
|
||||
for _, a := range o.operands {
|
||||
if c.held[a.head].path != "" {
|
||||
names = append(names, reader{a, fmt.Sprintf("%s operand %q", o.fn, a.spelling)})
|
||||
}
|
||||
}
|
||||
for _, a := range o.arms {
|
||||
if c.held[a.head].path != "" {
|
||||
names = append(names, reader{a, "token {" + a.spelling + "}"})
|
||||
}
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// checkNoRepeatedRead rejects a bare token repeated on a held name: {w} {w} beside
|
||||
// {uppercase(w)} would read one draw twice, where {w} {w} alone draws twice. The
|
||||
// error names the single-token spelling.
|
||||
// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held
|
||||
func checkNoRepeatedRead(c formatOps) error {
|
||||
count := map[string]int{}
|
||||
for _, o := range c.ops {
|
||||
for _, a := range o.arms {
|
||||
h, held := c.held[a.head]
|
||||
if len(a.tail) > 0 || !held {
|
||||
continue
|
||||
}
|
||||
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, h.holder, a.head, a.spelling)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -16,7 +16,7 @@ type Template struct {
|
||||
n node
|
||||
}
|
||||
|
||||
// Fake renders the template with one draw.
|
||||
// Fake renders the template once.
|
||||
func (t *Template) Fake() string {
|
||||
t.g.mu.Lock()
|
||||
defer t.g.mu.Unlock()
|
||||
@@ -159,12 +159,6 @@ func bindInline(root *folder, n node, label string, typedByGo bool) error {
|
||||
scope: scope,
|
||||
link: func() error { return linkNodeRefs(scope, root.children) },
|
||||
typedByGo: typedByGo,
|
||||
scopeFence: func() error {
|
||||
if t, isTemplate := n.(*template); isTemplate && t.drawGroup != "" {
|
||||
return fmt.Errorf("%s: drawGroup %q names nothing, since nothing can reference an inline template; drop it", label, t.drawGroup)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}.bind()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ type nameScope struct {
|
||||
|
||||
type nameBinding struct {
|
||||
name string
|
||||
ref string
|
||||
ref string // what it binds, as written: a reference, or a path into a field of binder
|
||||
field bool // ref is a path into a field, so a read of the name stays in the category
|
||||
body string
|
||||
where string // the fields reaching the binding template, as a compile error spells them
|
||||
scope *nameScope
|
||||
@@ -33,11 +34,12 @@ type nameBinding struct {
|
||||
addressed map[string]string
|
||||
}
|
||||
|
||||
// nameUse is one read of a name: the path it reads into the name, "" for the name itself; whether
|
||||
// a builtin reads it as an operand, and whether no reference can stand there; the draw group
|
||||
// its template renders in; and whether it sits in a repeat nested inside the name's scope.
|
||||
// nameUse is one read of a name: the path it reads into the name, "" for the name itself; the
|
||||
// template reading it; whether a builtin reads it as an operand, and whether no reference can
|
||||
// stand there; and whether it sits in a repeat nested inside the name's scope.
|
||||
type nameUse struct {
|
||||
tail, group string
|
||||
tail string
|
||||
in *template
|
||||
operand, noRef, nested bool
|
||||
}
|
||||
|
||||
@@ -86,7 +88,7 @@ func bindNames(root node) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := resolveReads(root, "", "", scopes); err != nil {
|
||||
if err := resolveReads(root, "", scopes); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sc := range scopes {
|
||||
@@ -109,7 +111,7 @@ func (sc *nameScope) bindAll(t *template, inChoice bool, where string) error {
|
||||
if b, twice := sc.bindings[tok.bound]; twice {
|
||||
return fmt.Errorf("%stoken {%s}: name %q is bound twice %s, by {%s} too; rename one", where, tok.body, tok.bound, sc.spelled(), b.body)
|
||||
}
|
||||
b := &nameBinding{name: tok.bound, ref: tok.boundRef, body: tok.body, where: where, scope: sc, index: len(sc.order), binder: t}
|
||||
b := &nameBinding{name: tok.bound, ref: tok.boundRef, field: !isRef(tok.boundRef), body: tok.body, where: where, scope: sc, index: len(sc.order), binder: t}
|
||||
if sc.bindings == nil {
|
||||
sc.bindings = map[string]*nameBinding{}
|
||||
}
|
||||
@@ -159,12 +161,9 @@ func eachContained(n node, where string, fn func(c node, where string) error) er
|
||||
// resolveReads answers each unbound read under n with a name its template sees, fields before the
|
||||
// format as compile reports them, 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.
|
||||
func resolveReads(n node, where, group string, scopes []*nameScope) error {
|
||||
func resolveReads(n node, where string, scopes []*nameScope) error {
|
||||
t, isTemplate := n.(*template)
|
||||
if isTemplate && t.drawGroup != "" {
|
||||
group = t.drawGroup
|
||||
}
|
||||
if err := eachContained(n, where, func(c node, where string) error { return resolveReads(c, where, group, scopes) }); err != nil {
|
||||
if err := eachContained(n, where, func(c node, where string) error { return resolveReads(c, where, scopes) }); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isTemplate {
|
||||
@@ -184,7 +183,7 @@ func resolveReads(n node, where, group string, scopes []*nameScope) error {
|
||||
if b == nil {
|
||||
return unresolved(where, u, t.nameScope, scopes)
|
||||
}
|
||||
b.uses = append(b.uses, nameUse{tail: u.tail, group: group, operand: u.operand, noRef: u.noRef, nested: t.nameScope != b.scope})
|
||||
b.uses = append(b.uses, nameUse{tail: u.tail, in: t, operand: u.operand, noRef: u.noRef, nested: t.nameScope != b.scope})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -221,15 +220,15 @@ func (t *template) isName(head string) bool {
|
||||
return !isRef(head) && t.fields[head] == nil && t.nameScope.lookup(head) != nil
|
||||
}
|
||||
|
||||
// linkBindings gives each binding t's tokens make the head and tail its reference resolved to.
|
||||
// linkBindings gives each binding t's tokens make the head and tail what it binds resolved to.
|
||||
func linkBindings(t *template) {
|
||||
for _, tok := range t.tokens {
|
||||
if tok.kind != nameBind {
|
||||
continue
|
||||
}
|
||||
b := t.nameScope.bindings[tok.bound]
|
||||
ref := t.link.refs[b.ref]
|
||||
b.head, b.tail = t.link.refHeads[ref.head], ref.tail
|
||||
a := splitArm(b.ref, t.link.refs)
|
||||
b.head, b.tail = t.head(a.head), a.tail
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,12 +256,22 @@ var namePasses = []func(path string, t *template) error{linkNames, checkNameRead
|
||||
// linkNames compiles t's reads of a name as paths from the head its binding's reference names, once
|
||||
// every template is linked, so each binder has resolved that reference.
|
||||
func linkNames(path string, t *template) error {
|
||||
return namedReads(t, func(o *op, a *arm) error {
|
||||
if err := namedReads(t, func(o *op, a *arm) error {
|
||||
if err := linkName(t, a); err != nil {
|
||||
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if ops := t.compiled.ops; t.repeat == 1 && len(ops) == 1 && ops[0].kind == nameRead && len(ops[0].arms) == 1 && ops[0].arms[0].kind == namedRead {
|
||||
a := ops[0].arms[0]
|
||||
full := append(a.named.tail[:len(a.named.tail):len(a.named.tail)], a.tail...)
|
||||
if column := recordColumn(a.named.head, full); column != nil {
|
||||
t.link.readsColumn = &columnRead{a: a, column: column}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkName(t *template, a *arm) error {
|
||||
@@ -275,7 +284,7 @@ func linkName(t *template, a *arm) error {
|
||||
return err
|
||||
}
|
||||
w := compilePath(b.head, full)
|
||||
a.named, a.steps, a.leaves, a.cover = b, w.steps, w.leaves, w.cover
|
||||
a.named, a.steps, a.leaves = b, w.steps, w.leaves
|
||||
a.levels = make([]string, len(full)+1)
|
||||
for i := range a.levels {
|
||||
a.levels[i] = joinSegments(full[:i])
|
||||
|
||||
+26
-56
@@ -3,6 +3,7 @@ package fejkdata
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// checkNameReads refuses each binding of t that checkUses refuses, and a read such as {n} whose
|
||||
@@ -26,63 +27,34 @@ func checkNameReads(path string, t *template) error {
|
||||
})
|
||||
}
|
||||
|
||||
// checkUses refuses a binding of a category read once whole where a reference could stand. It
|
||||
// refuses reads of b in two draw groups, or inside a repeat, where what b names reads a reference path.
|
||||
// checkUses refuses a binding read once at a spot the bound spelling can stand: that spelling
|
||||
// draws the same way without the name. A field stands only beside its binding.
|
||||
func (b *nameBinding) checkUses() error {
|
||||
if r := b.uses[0]; len(b.uses) == 1 && r.tail == "" && !r.nested && !r.noRef && len(b.tail) == 0 {
|
||||
r := b.uses[0]
|
||||
if len(b.uses) > 1 || r.nested || b.field && r.in != b.binder {
|
||||
return nil
|
||||
}
|
||||
spelling := b.ref
|
||||
switch {
|
||||
case strings.HasPrefix(r.tail, ".."):
|
||||
spelling += r.tail
|
||||
case r.tail != "":
|
||||
spelling += "." + r.tail
|
||||
}
|
||||
if r.noRef && !calcReads(spelling) {
|
||||
return nil
|
||||
}
|
||||
if !r.operand {
|
||||
spelling = "{" + spelling + "}"
|
||||
}
|
||||
return fmt.Errorf("name %q is read once, whole, which the bare reference draws the same way; write %s where it is read, and drop the token", b.name, spelling)
|
||||
}
|
||||
if !readsHeld(b.head, map[node]bool{}) {
|
||||
return nil
|
||||
}
|
||||
for _, u := range b.uses {
|
||||
if u.nested {
|
||||
return fmt.Errorf("name %q is read inside a repeat, and what it names reads a reference path, which each iteration draws apart; bind the name inside the repeat, or bind a second name there", b.name)
|
||||
}
|
||||
if u.group != b.uses[0].group {
|
||||
return fmt.Errorf("name %q is read in two draw groups, %s and %s, and what it names reads a reference path, which each draw group draws apart; read the name in one draw group, or bind a name in each", b.name, groupSpelling(b.uses[0].group), groupSpelling(u.group))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return fmt.Errorf("name %q is read once, so it keeps no pick for another read; write %s where it is read, and drop the token", b.name, spelling)
|
||||
}
|
||||
|
||||
func groupSpelling(group string) string {
|
||||
if group == "" {
|
||||
return "the unnamed one"
|
||||
}
|
||||
return fmt.Sprintf("%q", group)
|
||||
}
|
||||
|
||||
// readsHeld reports whether rendering n, or any field under it, can read a reference path.
|
||||
func readsHeld(n node, seen map[node]bool) bool {
|
||||
if seen[n] {
|
||||
return false
|
||||
}
|
||||
seen[n] = true
|
||||
if t, isTemplate := n.(*template); isTemplate {
|
||||
for _, o := range t.compiled.ops {
|
||||
for _, a := range slices.Concat(o.arms, o.operands) {
|
||||
if a.kind == refPathRead {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, c := range contained(n) {
|
||||
if readsHeld(c.node, seen) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, e := range renderEdges(n) {
|
||||
if readsHeld(e.to, seen) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
// calcReads reports whether a calc reads spelling as one operand.
|
||||
func calcReads(spelling string) bool {
|
||||
n, err := parseCalc(spelling)
|
||||
v, isVar := n.(calcVar)
|
||||
return err == nil && isVar && string(v) == spelling
|
||||
}
|
||||
|
||||
// checkOnce walks n, rendering at key under b's pick, into each level a read of b addresses.
|
||||
@@ -101,7 +73,7 @@ func (b *nameBinding) checkOnce(read string, n node, key string) error {
|
||||
}
|
||||
|
||||
func (b *nameBinding) checkTemplateOnce(read string, t *template, key string) error {
|
||||
fresh := map[string]int{}
|
||||
reads := map[string]int{}
|
||||
var into []arm
|
||||
for _, o := range t.compiled.ops {
|
||||
for _, a := range slices.Concat(o.arms, o.operands) {
|
||||
@@ -111,13 +83,11 @@ func (b *nameBinding) checkTemplateOnce(read string, t *template, key string) er
|
||||
if _, kept := b.addressed[join(key, a.path)]; kept {
|
||||
into = append(into, a)
|
||||
}
|
||||
if a.kind == freshRead {
|
||||
fresh[a.head]++
|
||||
reads[a.head]++
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, head := range sortedNames(fresh) {
|
||||
if by, kept := b.addressed[join(key, head)]; kept && fresh[head] > 1 {
|
||||
for _, head := range sortedNames(reads) {
|
||||
if by, kept := b.addressed[join(key, head)]; kept && reads[head] > 1 {
|
||||
return fmt.Errorf("{%s} renders field %q twice, so {%s} cannot say which draw it reads; drop {%s} or {%s}", read, head, by, read, by)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ type template struct {
|
||||
repeat int
|
||||
separator string
|
||||
datatype DataType
|
||||
drawGroup string // the draw group it draws in, as written; "" keeps its caller's
|
||||
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
|
||||
site tableSite
|
||||
@@ -81,10 +80,6 @@ func (s tableSite) isCell() bool {
|
||||
return s.table != nil && s.row != formatRow
|
||||
}
|
||||
|
||||
func (s tableSite) isFormat() bool {
|
||||
return s.table != nil && s.row == formatRow
|
||||
}
|
||||
|
||||
// label names the template at path in an error, a cell by its line in the rows file.
|
||||
func (s tableSite) label(path string) string {
|
||||
if s.isCell() {
|
||||
@@ -98,7 +93,6 @@ type templateLink struct {
|
||||
refs map[string]refBinding // each reference the format reads -> what it resolves to
|
||||
refHeads map[string]node // each refBinding.head -> the category it names
|
||||
readsColumn *columnRead // set when the format is one reference alone reading a record's column
|
||||
drawGroupKey string // its draw group keyed by its category: what a render reads its reference paths under
|
||||
}
|
||||
|
||||
func (*template) isNode() {}
|
||||
@@ -347,24 +341,20 @@ func compileTemplate(m map[string]any, pos position) (node, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(fields) == 0 && o.repeat == 1 && !o.weighted && o.datatype == DataTypeString && o.group == "" {
|
||||
if len(fields) == 0 && o.repeat == 1 && !o.weighted && o.datatype == DataTypeString {
|
||||
return nil, fmt.Errorf("an object holding only a format is a string; write %q", o.format)
|
||||
}
|
||||
toks, unbound, err := parseChecked(o.format, fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := checkNestedDrawGroup(fields, o.group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, drawGroup: o.group, isRecord: fieldPos == inColumn, unbound: unbound}, nil
|
||||
return &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, isRecord: fieldPos == inColumn, unbound: unbound}, nil
|
||||
}
|
||||
|
||||
// templateOptions is what a template object's option keys say.
|
||||
type templateOptions struct {
|
||||
datatype DataType
|
||||
format string
|
||||
group string
|
||||
repeat int
|
||||
separator string
|
||||
weighted bool
|
||||
@@ -385,9 +375,6 @@ func readOptions(m map[string]any, pos position) (templateOptions, error) {
|
||||
if o.datatype, err = datatypeOf(m, pos); err != nil {
|
||||
return o, err
|
||||
}
|
||||
if o.group, err = drawGroupOf(m, repeat); err != nil {
|
||||
return o, err
|
||||
}
|
||||
if sv, ok := m["separator"]; ok {
|
||||
if o.separator, ok = sv.(string); !ok {
|
||||
return o, fmt.Errorf("separator must be a string, got %T", sv)
|
||||
@@ -445,55 +432,6 @@ func repeatOf(m map[string]any) (int, error) {
|
||||
return int(r), nil
|
||||
}
|
||||
|
||||
// checkNestedDrawGroup refuses a template beneath one drawing in group that names group again,
|
||||
// short of a repeat or another draw group.
|
||||
func checkNestedDrawGroup(fields map[string]node, group string) error {
|
||||
if group == "" {
|
||||
return nil
|
||||
}
|
||||
var walk func(path string, n node) error
|
||||
walk = func(path string, n node) error {
|
||||
t, isTemplate := n.(*template)
|
||||
switch {
|
||||
case isTemplate && t.drawGroup == group:
|
||||
return fmt.Errorf("%q names drawGroup %q, the draw group this template draws in already; drop it", path, group)
|
||||
case isTemplate && (t.drawGroup != "" || t.repeat > 1):
|
||||
return nil
|
||||
}
|
||||
for _, c := range contained(n) {
|
||||
if err := walk(join(path, c.name), c.node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, name := range sortedNames(fields) {
|
||||
if err := walk(name, fields[name]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawGroupOf reads a template's "drawGroup" (default ""), which a repeat cannot carry: each
|
||||
// iteration renders in no draw group.
|
||||
func drawGroupOf(m map[string]any, repeat int) (string, error) {
|
||||
v, ok := m["drawGroup"]
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
name, ok := v.(string)
|
||||
switch {
|
||||
case !ok:
|
||||
return "", fmt.Errorf("drawGroup must be a string, got %T", v)
|
||||
case name == "":
|
||||
return "", fmt.Errorf(`drawGroup "" is the default, so it has no effect; drop it`)
|
||||
case repeat > 1:
|
||||
return "", fmt.Errorf("drawGroup %q on a repeat names nothing, since each iteration is a render of its own; drop it", name)
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func weightOf(raw any) (float64, error) {
|
||||
@@ -567,7 +505,7 @@ func checkPathNames(path string) error {
|
||||
|
||||
func isOption(name string) bool {
|
||||
switch name {
|
||||
case "datatype", "drawGroup", "format", "repeat", "separator", "weight":
|
||||
case "datatype", "format", "repeat", "separator", "weight":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -281,24 +281,18 @@ const (
|
||||
|
||||
// pathCheck proves a path resolves whichever way the draws go: every variant of a
|
||||
// choice carries the rest of it, and is walked, a selector names a row inside the
|
||||
// rows selected before it, and no level read carries a repeat or a drawGroup. It
|
||||
// compiles the steps a draw takes, every leaf the path may render, and as cover the
|
||||
// first choice it passes, else the leaf; level names the head in its errors.
|
||||
// rows selected before it, and no level read carries a repeat. It compiles the steps
|
||||
// a draw takes, and every leaf the path may render; level names the head in its errors.
|
||||
type pathCheck struct {
|
||||
pins pinSet
|
||||
level string
|
||||
tail []string
|
||||
steps []pathStep
|
||||
leaves []node
|
||||
cover node
|
||||
}
|
||||
|
||||
func (w *pathCheck) run(n node) (node, error) {
|
||||
leaf, err := w.walk(n, w.tail)
|
||||
if err == nil && w.cover == nil {
|
||||
w.cover = leaf
|
||||
}
|
||||
return leaf, err
|
||||
return w.walk(n, w.tail)
|
||||
}
|
||||
|
||||
func (w *pathCheck) walk(n node, tail []string) (node, error) {
|
||||
@@ -306,9 +300,6 @@ func (w *pathCheck) walk(n node, tail []string) (node, error) {
|
||||
var err error
|
||||
switch x := n.(type) {
|
||||
case *choice:
|
||||
if w.cover == nil {
|
||||
w.cover = x
|
||||
}
|
||||
return w.walkEvery(x, tail)
|
||||
case *table:
|
||||
var r tableRoute
|
||||
@@ -393,12 +384,9 @@ func (w *pathCheck) walkEvery(c *choice, tail []string) (node, error) {
|
||||
}
|
||||
|
||||
func (w *pathCheck) enter(t *template, rest []string) error {
|
||||
if t.repeat > 1 {
|
||||
name := join(w.level, strings.Join(w.tail[:len(w.tail)-len(rest)], "."))
|
||||
switch {
|
||||
case t.repeat > 1:
|
||||
return fmt.Errorf("the level %q carries a repeat, which a path reading one draw of it cannot apply", name)
|
||||
case t.drawGroup != "":
|
||||
return fmt.Errorf("the level %q carries a drawGroup, which a path reading into it cannot apply", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -439,8 +427,8 @@ func probePath(n node, tail []string, steps []pathStep) ([]pathStep, error) {
|
||||
|
||||
// drawSteps draws the rows and variants a path's steps read from n, pinning the rows in pins;
|
||||
// pins is nil for a sibling path, which never crosses a table, since a table is only a category.
|
||||
// For a memoized read, memo keeps the variant drawn at each of levels, so paths sharing a prefix
|
||||
// share it. It returns the leaf and the pins its row is in: a step down after a step up draws
|
||||
// For a read through a name, memo keeps the variant drawn at each of levels, so paths sharing a
|
||||
// prefix share it. It returns the leaf and the pins its row is in: a step down after a step up draws
|
||||
// afresh, into pins of its own, which memo shares with every path stepping down there.
|
||||
func drawSteps(s *generatorState, n node, steps []pathStep, pins *pinSet, memo *drawMemo, levels []string) (node, *pinSet) {
|
||||
var climbed *table
|
||||
|
||||
@@ -2,8 +2,7 @@ package fejkdata
|
||||
|
||||
// namedPick is one draw of a name: the variant drawn at each level a read of it addresses, the
|
||||
// value each read there produced, keyed by the path from the name, and the table rows they
|
||||
// pinned. A reference read under it draws in the draw group of the read of the name, which the
|
||||
// draw fences survey it in.
|
||||
// pinned.
|
||||
type namedPick struct {
|
||||
named *nameBinding
|
||||
memo drawMemo
|
||||
@@ -24,44 +23,30 @@ func newPickFrame(scope *nameScope) *pickFrame {
|
||||
return f
|
||||
}
|
||||
|
||||
// frameStack is the frames of the name scopes rendering, innermost last. A render's draws point
|
||||
// at it, and a repeat iteration's share it: holding the frames in the draws, or in a renderScope,
|
||||
// would move every render's draws to the heap.
|
||||
// frameStack is the frames of the name scopes rendering, innermost last. A render's scope points
|
||||
// at it, and a repeat iteration's shares it: holding the frames in a renderScope would move every
|
||||
// render's scope to the heap.
|
||||
type frameStack struct {
|
||||
frames []*pickFrame
|
||||
}
|
||||
|
||||
// pushFrame opens f until popFrames closes it, returning the mark popFrames takes.
|
||||
func (d *renderDraws) pushFrame(f *pickFrame) int {
|
||||
mark := d.depth()
|
||||
if d.frameStack == nil {
|
||||
d.frameStack = &frameStack{}
|
||||
}
|
||||
d.frameStack.frames = append(d.frameStack.frames, f)
|
||||
// push opens f until pop closes it, returning the mark pop takes.
|
||||
func (st *frameStack) push(f *pickFrame) int {
|
||||
mark := len(st.frames)
|
||||
st.frames = append(st.frames, f)
|
||||
return mark
|
||||
}
|
||||
|
||||
func (d *renderDraws) popFrames(mark int) {
|
||||
if d.frameStack != nil {
|
||||
d.frameStack.frames = d.frameStack.frames[:mark]
|
||||
}
|
||||
}
|
||||
func (st *frameStack) pop(mark int) { st.frames = st.frames[:mark] }
|
||||
|
||||
func (d *renderDraws) depth() int {
|
||||
if d.frameStack == nil {
|
||||
return 0
|
||||
}
|
||||
return len(d.frameStack.frames)
|
||||
}
|
||||
|
||||
// enter starts a read landing on n: the read sees no frame opened before it, and gets from memo a
|
||||
// frame for each name scope around n, so reads sharing memo read one pick of each name. It
|
||||
// returns the mark that closes those frames.
|
||||
// enter starts a read landing on n: the read sees no frame opened before it, and gets a frame for
|
||||
// each name scope around n, from memo where there is one, so reads sharing memo read one pick of
|
||||
// each name. It returns the mark that closes those frames.
|
||||
func (sc renderScope) enter(n node, memo *drawMemo) (renderScope, int) {
|
||||
sc = sc.entering()
|
||||
for scope := scopeAround(n); scope != nil; scope = scope.up {
|
||||
if len(scope.order) > 0 {
|
||||
sc.draws.pushFrame(memo.enteredFrame(scope))
|
||||
sc.frames.push(memo.enteredFrame(scope))
|
||||
}
|
||||
}
|
||||
return sc, sc.base
|
||||
@@ -69,7 +54,7 @@ func (sc renderScope) enter(n node, memo *drawMemo) (renderScope, int) {
|
||||
|
||||
// entering is sc as a read entering a category sees it: no frame opened before it, and no pick.
|
||||
func (sc renderScope) entering() renderScope {
|
||||
sc.base, sc.pick = sc.draws.depth(), nil
|
||||
sc.base, sc.pick = len(sc.frames.frames), nil
|
||||
return sc
|
||||
}
|
||||
|
||||
@@ -89,6 +74,9 @@ func scopeAround(n node) *nameScope {
|
||||
}
|
||||
|
||||
func (m *drawMemo) enteredFrame(scope *nameScope) *pickFrame {
|
||||
if m == nil {
|
||||
return newPickFrame(scope)
|
||||
}
|
||||
f, ok := m.enteredFrames[scope]
|
||||
if !ok {
|
||||
f = newPickFrame(scope)
|
||||
@@ -106,18 +94,16 @@ func (sc renderScope) renderFrame(t *template) int {
|
||||
if t.ownNameScope == nil || sc.frameOf(t.ownNameScope) != nil {
|
||||
return -1
|
||||
}
|
||||
return sc.draws.pushFrame(newPickFrame(t.ownNameScope))
|
||||
return sc.frames.push(newPickFrame(t.ownNameScope))
|
||||
}
|
||||
|
||||
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
|
||||
func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
|
||||
if stack := sc.draws.frameStack; stack != nil {
|
||||
for i := len(stack.frames) - 1; i >= sc.base; i-- {
|
||||
if f := stack.frames[i]; f.scope == scope {
|
||||
for i := len(sc.frames.frames) - 1; i >= sc.base; i-- {
|
||||
if f := sc.frames.frames[i]; f.scope == scope {
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -131,9 +117,12 @@ func readName(s *generatorState, sc renderScope, a arm) readValue {
|
||||
return r
|
||||
}
|
||||
leaf, pins := p.draw(s, a.named.head, a.steps, a.levels, a.path)
|
||||
if a.named.field {
|
||||
return p.renderAt(s, leaf, pins, a.path, sc)
|
||||
}
|
||||
sc, mark := sc.enter(leaf, &p.memo)
|
||||
r := p.renderAt(s, leaf, pins, a.path, sc)
|
||||
sc.draws.popFrames(mark)
|
||||
sc.frames.pop(mark)
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -167,7 +156,7 @@ func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue
|
||||
}
|
||||
|
||||
// underKey is the key of path under the pick key prefix. It never returns prefix itself, which a
|
||||
// memo would keep: anything a renderScope holds reaching the heap moves every render's draws there.
|
||||
// memo would keep: anything a renderScope holds reaching the heap moves every render's scope there.
|
||||
func underKey(prefix, path string) string {
|
||||
if prefix == "" {
|
||||
return path
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
type tablePin struct {
|
||||
t *table
|
||||
@@ -73,33 +68,6 @@ func (p *pinSet) nearestPinned(t *table) *table {
|
||||
return nil
|
||||
}
|
||||
|
||||
// each calls fn for every pinned row, in pin order, the spilled ones by path.
|
||||
func (p *pinSet) each(fn func(t *table, r int)) {
|
||||
for _, q := range p.inline[:p.used] {
|
||||
fn(q.t, q.row)
|
||||
}
|
||||
spilled := make([]*table, 0, len(p.spill))
|
||||
for t := range p.spill {
|
||||
spilled = append(spilled, t)
|
||||
}
|
||||
sort.Slice(spilled, func(i, j int) bool { return spilled[i].path < spilled[j].path })
|
||||
for _, t := range spilled {
|
||||
fn(t, p.spill[t])
|
||||
}
|
||||
}
|
||||
|
||||
// key spells the pinned rows, so two sets pinning the same rows share one.
|
||||
func (p *pinSet) key() string {
|
||||
var pins []tablePin
|
||||
p.each(func(t *table, r int) { pins = append(pins, tablePin{t, r}) })
|
||||
sort.Slice(pins, func(i, j int) bool { return pins[i].t.path < pins[j].t.path })
|
||||
var b strings.Builder
|
||||
for _, q := range pins {
|
||||
fmt.Fprintf(&b, "%q%d", q.t.path, q.row)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// clash is the table whose pinned row keeps row r of t out: t itself pinned to another row, or the
|
||||
// nearest ancestor pinned to a row r is not inside; nil where none does.
|
||||
func (p *pinSet) clash(t *table, r int) *table {
|
||||
@@ -164,27 +132,3 @@ func (p *pinSet) above(t *table) *pinSet {
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// clone is a copy of p that pins apart from it; a plain copy shares the spill map.
|
||||
func (p pinSet) clone() pinSet {
|
||||
p.spill = maps.Clone(p.spill)
|
||||
return p
|
||||
}
|
||||
|
||||
// entered is p with row r of t pinned. p is left as it was, since a fence walk branches.
|
||||
func (p pinSet) entered(t *table, r int) pinSet {
|
||||
p = p.clone()
|
||||
p.pin(t, r)
|
||||
return p
|
||||
}
|
||||
|
||||
// differs reports whether p and q hold different rows of one table.
|
||||
func (p *pinSet) differs(q *pinSet) bool {
|
||||
found := false
|
||||
p.each(func(t *table, r int) {
|
||||
if qr, in := q.pinned(t); in && qr != r {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
package fejkdata
|
||||
|
||||
import "fmt"
|
||||
|
||||
// readRoute is how a render reaches a read: as its author spells it, and the root edge's label.
|
||||
type readRoute struct{ spelling, label string }
|
||||
|
||||
// branches is the rows a read sits under, where two reads under different rows of one table never
|
||||
// render together. pins holds the rows pinned above it with their ancestors, which a pair of reads
|
||||
// needs to find what a replay of all of them does; wholePins the rows of whole draws whose cells it
|
||||
// sits in, without ancestors, since each whole draw draws apart from every other.
|
||||
type branches struct {
|
||||
pins pinSet
|
||||
wholePins pinSet
|
||||
}
|
||||
|
||||
// pathRead is one reference a render reads, a path or a bare reference with no tail: in its draw
|
||||
// group ("" until the root names one), by the route the render's root reached it, under its
|
||||
// branches.
|
||||
type pathRead struct {
|
||||
group string
|
||||
route readRoute
|
||||
branches branches
|
||||
a arm
|
||||
tr *tableRead // set where the reference names a table
|
||||
clash error // set where its selectors clash with a row its route pinned
|
||||
}
|
||||
|
||||
type readKey struct {
|
||||
group string
|
||||
path string
|
||||
pins string
|
||||
wholePins string
|
||||
clash bool
|
||||
}
|
||||
|
||||
// readFold gathers, once per node, the reference reads rendering it makes.
|
||||
type readFold struct {
|
||||
memo map[node][]pathRead
|
||||
}
|
||||
|
||||
func newReadFold() *readFold { return &readFold{memo: map[node][]pathRead{}} }
|
||||
|
||||
// rootReads is what rendering t as a render of its own reads, each read named by the edge of t
|
||||
// that reaches it, and in "" where no template below t names a draw group.
|
||||
func (f *readFold) rootReads(t *template) []pathRead {
|
||||
var reads []pathRead
|
||||
for _, e := range renderEdges(t) {
|
||||
reads = append(reads, f.viaEdge(t, e, readRoute{e.reached(), e.label})...)
|
||||
}
|
||||
return reads
|
||||
}
|
||||
|
||||
// columnReads is what rendering t's columns as one record reads.
|
||||
func (f *readFold) columnReads(t *template, columns []string) []pathRead {
|
||||
var reads []pathRead
|
||||
for _, name := range columns {
|
||||
reads = append(reads, routed(f.reads(t.fields[name]), readRoute{spelling: fmt.Sprintf("column %q", name)})...)
|
||||
}
|
||||
return reads
|
||||
}
|
||||
|
||||
// reads is what rendering n reads, short of a repeat, which renders over draws of its own: each
|
||||
// read with the rows pinned above it inside n, and in its draw group where a template inside n
|
||||
// names one.
|
||||
func (f *readFold) reads(n node) []pathRead {
|
||||
if r, done := f.memo[n]; done {
|
||||
return r
|
||||
}
|
||||
var out []pathRead
|
||||
switch n := n.(type) {
|
||||
case *choice:
|
||||
for _, it := range n.items {
|
||||
out = append(out, f.reads(it)...)
|
||||
}
|
||||
case *template:
|
||||
if repeats(n) {
|
||||
break
|
||||
}
|
||||
for _, e := range renderEdges(n) {
|
||||
out = append(out, f.viaEdge(n, e, readRoute{})...)
|
||||
}
|
||||
if n.link.drawGroupKey != "" {
|
||||
for i := range out {
|
||||
if out[i].group == "" {
|
||||
out[i].group = n.link.drawGroupKey
|
||||
}
|
||||
}
|
||||
}
|
||||
case *table:
|
||||
out = f.rowReads(n, func(b *branches, row int) bool {
|
||||
b.wholePins = b.wholePins.clone()
|
||||
b.wholePins.add(n, row)
|
||||
return true
|
||||
})
|
||||
case *tableRow:
|
||||
out = f.rowReads(n.t, func(b *branches, row int) bool { return b.enter(n.t, row) })
|
||||
case *tableColumn:
|
||||
out = f.cellReads(n, func(b *branches, row int) bool { return b.enter(n.t, row) })
|
||||
case *folder, *nullItem:
|
||||
default:
|
||||
panic(internalError("reads has no case for node %T", n))
|
||||
}
|
||||
out = distinct(out)
|
||||
f.memo[n] = out
|
||||
return out
|
||||
}
|
||||
|
||||
// distinct keeps one of every read gathered by several routes, so a diamond of templates
|
||||
// contributes each read once however many routes reach it.
|
||||
func distinct(reads []pathRead) []pathRead {
|
||||
seen := make(map[readKey]bool, len(reads))
|
||||
out := reads[:0:0]
|
||||
for _, r := range reads {
|
||||
k := readKey{r.group, r.a.path, r.branches.pins.key(), r.branches.wholePins.key(), r.clash != nil}
|
||||
if !seen[k] {
|
||||
seen[k] = true
|
||||
out = append(out, r)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// viaEdge is what rendering e from n reads: the reference e reads, then what its leaf renders
|
||||
// under the rows that reference pins.
|
||||
func (f *readFold) viaEdge(n node, e renderEdge, route readRoute) []pathRead {
|
||||
if !e.readsRef() {
|
||||
return routed(f.reads(e.to), route)
|
||||
}
|
||||
a := e.read
|
||||
tr := tableReadOf(n.(*template).head(a.head), a, e.to)
|
||||
out := []pathRead{{route: route, a: a, tr: tr}}
|
||||
below := f.reads(e.to)
|
||||
if tr != nil {
|
||||
below = pinBelow(below, tr)
|
||||
}
|
||||
return append(out, routed(below, route)...)
|
||||
}
|
||||
|
||||
// pinBelow is the reads below a table read, once its selected rows are pinned above them: a read
|
||||
// under a cell those rows keep out is dropped, since that cell never renders on this route, and
|
||||
// a read whose own selectors clash with them carries that clash.
|
||||
func pinBelow(reads []pathRead, tr *tableRead) []pathRead {
|
||||
kept := reads[:0:0]
|
||||
for _, r := range reads {
|
||||
pins := r.branches.pins.clone()
|
||||
if err := tr.replayBelow(&pins); err != nil {
|
||||
continue
|
||||
}
|
||||
if r.tr != nil && r.clash == nil {
|
||||
check := pins.clone()
|
||||
r.clash = r.tr.replay(&check)
|
||||
}
|
||||
r.branches.pins = pins
|
||||
kept = append(kept, r)
|
||||
}
|
||||
return kept
|
||||
}
|
||||
|
||||
// rowReads is what the rows of t read: the cells of the columns its format renders, each tagged
|
||||
// with its row, and the format's other reads, which every row shares. A format reaching its own
|
||||
// cells through another category is checkOwnFamily's refusal, which runs before any survey of
|
||||
// these reads.
|
||||
func (f *readFold) rowReads(t *table, tag func(*branches, int) bool) []pathRead {
|
||||
var out []pathRead
|
||||
for _, e := range renderEdges(t.formatTemplate) {
|
||||
if c, isColumn := e.to.(*tableColumn); isColumn && !e.readsRef() {
|
||||
out = append(out, f.cellReads(c, tag)...)
|
||||
continue
|
||||
}
|
||||
out = append(out, f.viaEdge(t.formatTemplate, e, readRoute{})...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// cellReads is what the cells of column c read, each tagged with its row.
|
||||
func (f *readFold) cellReads(c *tableColumn, tag func(*branches, int) bool) []pathRead {
|
||||
var out []pathRead
|
||||
for r := 0; r < c.t.rowCount(); r++ {
|
||||
cell := c.t.cellTemplate(r, c.i)
|
||||
if cell == nil {
|
||||
continue
|
||||
}
|
||||
for _, read := range f.reads(cell) {
|
||||
if tag(&read.branches, r) {
|
||||
out = append(out, read)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// enter pins row r of t above a read, or reports that the rows already pinned keep it out.
|
||||
func (b *branches) enter(t *table, r int) bool {
|
||||
if b.pins.clash(t, r) != nil {
|
||||
return false
|
||||
}
|
||||
b.pins = b.pins.entered(t, r)
|
||||
return true
|
||||
}
|
||||
|
||||
// routed names the route the render's root reaches reads by, where none is named yet.
|
||||
func routed(reads []pathRead, route readRoute) []pathRead {
|
||||
if route == (readRoute{}) {
|
||||
return reads
|
||||
}
|
||||
out := make([]pathRead, len(reads))
|
||||
for i, r := range reads {
|
||||
if r.route == (readRoute{}) {
|
||||
r.route = route
|
||||
}
|
||||
out[i] = r
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// spelled names the route, and the reference it reaches a read by where its root edge is not that
|
||||
// reference.
|
||||
func (r readRoute) spelled(ref string) string {
|
||||
if ref == "" || ref == r.label {
|
||||
return r.spelling
|
||||
}
|
||||
return fmt.Sprintf("%s with {%s}", r.spelling, ref)
|
||||
}
|
||||
|
||||
// tableRead is what a reference path reads of a table family: the table its head
|
||||
// names, the rows its selectors pin, the tables it draws — those it walks with no
|
||||
// row pinned, and their unpinned ancestors — each selector's spelling, and whether
|
||||
// it lands on a `tableRow`.
|
||||
type tableRead struct {
|
||||
headTable *table
|
||||
pins pinSet
|
||||
leafPins *pinSet // the rows the leaf renders in: pins, without the rows below the row a ".." stepped up to
|
||||
drawn map[*table]bool
|
||||
sels []tableSel
|
||||
landsRow bool
|
||||
}
|
||||
|
||||
// tableSel is one selector on the way: the table it selects a row of, and the path
|
||||
// as written up to and including it.
|
||||
type tableSel struct {
|
||||
t *table
|
||||
spelling string
|
||||
}
|
||||
|
||||
// tableReadOf follows a reference path's steps for what it takes of a table family, so
|
||||
// two paths pinning one row by different routes compare equal.
|
||||
func tableReadOf(head node, a arm, leaf node) *tableRead {
|
||||
t, isTable := head.(*table)
|
||||
if !isTable {
|
||||
return nil
|
||||
}
|
||||
r := &tableRead{headTable: t}
|
||||
r.leafPins = &r.pins
|
||||
_, r.landsRow = leaf.(*tableRow)
|
||||
var climbed *table
|
||||
for _, st := range a.steps {
|
||||
switch st.kind {
|
||||
case stepParent:
|
||||
t, climbed = t.parentT, t.parentT
|
||||
case stepChild:
|
||||
if climbed != nil {
|
||||
r.leafPins, climbed = r.leafPins.above(climbed), nil
|
||||
}
|
||||
t = t.descendant(st.name)
|
||||
case stepSelect:
|
||||
r.pins.pin(t, st.row)
|
||||
r.sels = append(r.sels, tableSel{t, joinSegments(append([]string{a.writtenHead}, a.tail[:st.at+1]...))})
|
||||
case stepDraw:
|
||||
if r.leafPins != &r.pins {
|
||||
continue // a step down after a step up draws apart from the render's pins
|
||||
}
|
||||
if r.drawn == nil {
|
||||
r.drawn = map[*table]bool{}
|
||||
}
|
||||
for d, stop := t, r.pins.nearestPinned(t); d != stop; d = d.parentT {
|
||||
r.drawn[d] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// selected is the selector in r on t, or on the nearest ancestor of t it selects.
|
||||
func (r *tableRead) selected(t *table) (tableSel, bool) {
|
||||
for ; t != nil; t = t.parentT {
|
||||
for _, s := range r.sels {
|
||||
if s.t == t {
|
||||
return s, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return tableSel{}, false
|
||||
}
|
||||
|
||||
// drawnOf is a table the read draws that pins holds a row of, if any.
|
||||
func (r *tableRead) drawnOf(pins *pinSet) *table {
|
||||
var found *table
|
||||
pins.each(func(t *table, _ int) {
|
||||
if found == nil && r.drawn[t] {
|
||||
found = t
|
||||
}
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
// replay pins the read's rows into d, where they agree with the rows pinned before.
|
||||
func (r *tableRead) replay(d *pinSet) error { return replayPins(&r.pins, d) }
|
||||
|
||||
// replayBelow pins into d the rows the read's leaf renders in.
|
||||
func (r *tableRead) replayBelow(d *pinSet) error { return replayPins(r.leafPins, d) }
|
||||
|
||||
func replayPins(from, d *pinSet) error {
|
||||
var err error
|
||||
from.each(func(t *table, row int) {
|
||||
if err == nil {
|
||||
err = d.pinRow(t, row)
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -20,9 +20,8 @@ type Column struct {
|
||||
}
|
||||
|
||||
// Record is one record rendered from a template: every direct field is a column,
|
||||
// listed in name order. Each column is its own expansion, so a sibling field is
|
||||
// local to it, while a reference that reads a path is drawn once for the whole
|
||||
// record, per group, and a pick bound by {ref as n} once for the whole record.
|
||||
// listed in name order. Each {…} draws afresh, while a pick bound by {x as n} is
|
||||
// drawn once for the whole record.
|
||||
type Record struct {
|
||||
columns []Column
|
||||
}
|
||||
@@ -131,10 +130,10 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
||||
}
|
||||
var draws renderDraws
|
||||
sc := renderScope{draws: &draws}
|
||||
var frames frameStack
|
||||
sc := renderScope{frames: &frames}
|
||||
if t, isTable := n.(*table); isTable {
|
||||
if n, err = tableRecord(f.rand, t, tail, sc); err != nil {
|
||||
if n, sc.row, err = tableRecord(f.rand, t, tail); err != nil {
|
||||
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
||||
}
|
||||
} else if len(tail) > 0 {
|
||||
@@ -151,25 +150,22 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
||||
return renderRecord(f.rand, shape.template, shape.columns, sc), nil
|
||||
}
|
||||
|
||||
// tableRecord walks a path's tail from a table to the table whose row is the record,
|
||||
// pinning in sc the rows it selects or draws.
|
||||
func tableRecord(s *generatorState, t *table, tail []string, sc renderScope) (node, error) {
|
||||
// tableRecord walks a path's tail from a table to the table whose row is the record, and that
|
||||
// row, selected or drawn.
|
||||
func tableRecord(s *generatorState, t *table, tail []string) (node, renderedRow, error) {
|
||||
n, pins, err := descend(s, t, tail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, renderedRow{}, err
|
||||
}
|
||||
group := sc.groupDraws()
|
||||
group.pins = pins
|
||||
switch n := n.(type) {
|
||||
case *table:
|
||||
n.drawIn(s, &group.pins)
|
||||
return n, nil
|
||||
return n, renderedRow{n, n.drawIn(s, &pins)}, nil
|
||||
case *tableRow:
|
||||
return n.t, nil
|
||||
return n.t, renderedRow{n.t, pins.mustRow(n.t)}, nil
|
||||
case *tableColumn:
|
||||
return nil, fmt.Errorf("descends into %q, a column; a record is a table's row", n.t.header[n.i])
|
||||
return nil, renderedRow{}, fmt.Errorf("descends into %q, a column; a record is a table's row", n.t.header[n.i])
|
||||
}
|
||||
return n, nil
|
||||
return n, renderedRow{}, nil
|
||||
}
|
||||
|
||||
// recordShape is what recordOf settled about a node: the template to project, its
|
||||
@@ -203,12 +199,12 @@ type RecordTemplate struct {
|
||||
columns []Column
|
||||
}
|
||||
|
||||
// Fake renders the record with one draw.
|
||||
// Fake renders the record, one pick of each name across its columns.
|
||||
func (t *RecordTemplate) Fake() *Record {
|
||||
t.g.mu.Lock()
|
||||
defer t.g.mu.Unlock()
|
||||
var draws renderDraws
|
||||
return renderRecord(t.g.rand, t.template, t.columns, renderScope{draws: &draws})
|
||||
var frames frameStack
|
||||
return renderRecord(t.g.rand, t.template, t.columns, renderScope{frames: &frames})
|
||||
}
|
||||
|
||||
// NewRecordTemplate compiles an inline record — a JSON object with a format and
|
||||
@@ -262,15 +258,11 @@ func recordOf(n node) (*template, []Column, error) {
|
||||
return t, columns, nil
|
||||
}
|
||||
|
||||
// renderRecord draws each column once, in the name order recordOf fixed, as one render
|
||||
// over sc's draws; a table's columns read the row pinned there.
|
||||
// renderRecord draws each column once, in the name order recordOf fixed, as one render, so the
|
||||
// columns read one pick of each name; a table's columns read sc's row.
|
||||
func renderRecord(s *generatorState, t *template, columns []Column, sc renderScope) *Record {
|
||||
sc = sc.in(t)
|
||||
if mark := sc.renderFrame(t); mark >= 0 {
|
||||
defer sc.draws.popFrames(mark)
|
||||
}
|
||||
if t.site.isFormat() {
|
||||
sc = sc.at(t.site.table.rowNode, &sc.groupDraws().pins)
|
||||
defer sc.frames.pop(mark)
|
||||
}
|
||||
r := &Record{columns: append([]Column(nil), columns...)}
|
||||
for i := range r.columns {
|
||||
|
||||
+16
-19
@@ -69,8 +69,7 @@ func refSegments(name string, folder []string) ([]string, error) {
|
||||
|
||||
// linkRefs binds every reference and compiles every format, once all data is merged,
|
||||
// so a reference sees the override-resolved tree. A reference's head binds into
|
||||
// refHeads under its root path and its tail reads like a sibling path, so two
|
||||
// spellings of one target are one draw.
|
||||
// refHeads under its root path and its tail reads like a sibling path.
|
||||
func linkRefs(sites []categorySite, root map[string]node) error {
|
||||
if err := eachTemplate(sites, func(s categorySite, path string, t *template) error {
|
||||
return linkTemplate(s.dir, path, s.path, t, root)
|
||||
@@ -123,23 +122,13 @@ func compileArm(t *template, a *arm) {
|
||||
panic(internalError("{%s} reads a head nothing bound", a.spelling))
|
||||
}
|
||||
w := compilePath(head, a.tail)
|
||||
a.steps, a.leaves, a.cover = w.steps, w.leaves, w.cover
|
||||
switch _, held := t.compiled.held[a.head]; {
|
||||
case a.isRefPath():
|
||||
a.kind = refPathRead
|
||||
case held:
|
||||
a.kind = heldRead
|
||||
}
|
||||
a.steps, a.leaves = w.steps, w.leaves
|
||||
}
|
||||
|
||||
// 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:
|
||||
// resolveLink binds every reference t reads, refusing one to t's own category:
|
||||
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
|
||||
func (t *template) resolveLink(folder []string, path, category string, root map[string]node) (templateLink, error) {
|
||||
var link templateLink
|
||||
if t.drawGroup != "" {
|
||||
link.drawGroupKey = category + "/" + t.drawGroup
|
||||
}
|
||||
names := refTokens(t.tokens)
|
||||
if len(names) == 0 {
|
||||
return link, nil
|
||||
@@ -169,8 +158,8 @@ func (t *template) resolveLink(folder []string, path, category string, root map[
|
||||
return link, nil
|
||||
}
|
||||
|
||||
// columnRead is a record's column read by a format of that one reference alone, which is the
|
||||
// column: it takes the column's datatype and null.
|
||||
// columnRead is a record's column read by a format of that one reference or name read alone,
|
||||
// which is the column: it takes the column's datatype and null.
|
||||
type columnRead struct {
|
||||
a arm
|
||||
column node
|
||||
@@ -182,11 +171,19 @@ func columnReadOf(t *template, link templateLink) *columnRead {
|
||||
return nil
|
||||
}
|
||||
a := splitArm(name, link.refs)
|
||||
target, isTemplate := link.refHeads[a.head].(*template)
|
||||
if !isTemplate || !target.isRecord || len(a.tail) != 1 {
|
||||
if column := recordColumn(link.refHeads[a.head], a.tail); column != nil {
|
||||
return &columnRead{a: a, column: column}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// recordColumn is the column tail names in head, nil where head is no record or tail no column of it.
|
||||
func recordColumn(head node, tail []string) node {
|
||||
target, isTemplate := head.(*template)
|
||||
if !isTemplate || !target.isRecord || len(tail) != 1 {
|
||||
return nil
|
||||
}
|
||||
return &columnRead{a: a, column: target.fields[a.tail[0]]}
|
||||
return target.fields[tail[0]]
|
||||
}
|
||||
|
||||
// loneRef is the reference a format of one reference token and nothing else reads.
|
||||
|
||||
@@ -25,8 +25,6 @@ func (f *Generator) Fake(path string) (string, error) {
|
||||
return "", fmt.Errorf("fejkdata: %w", err)
|
||||
}
|
||||
f.loadShippedAt(segments)
|
||||
var draws renderDraws
|
||||
sc := renderScope{draws: &draws}
|
||||
n, pins, err := descend(f.rand, &f.root, segments)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fejkdata: %s: %w", path, err)
|
||||
@@ -34,9 +32,8 @@ func (f *Generator) Fake(path string) (string, error) {
|
||||
if _, ok := n.(*folder); ok {
|
||||
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
||||
}
|
||||
group := sc.groupDraws()
|
||||
group.pins = pins
|
||||
sc, _ = sc.at(n, &group.pins).enter(n, &group.memo)
|
||||
var frames frameStack
|
||||
sc, _ := renderScope{frames: &frames}.at(n, &pins).enter(n, nil)
|
||||
return render(f.rand, n, sc), nil
|
||||
}
|
||||
|
||||
@@ -53,15 +50,14 @@ func descend(s *generatorState, root node, segments []string) (node, pinSet, err
|
||||
return n, *leafPins, nil
|
||||
}
|
||||
|
||||
// renderOnce renders n as one render, over draws of its own.
|
||||
// renderOnce renders n as one render, over frames of its own.
|
||||
func renderOnce(s *generatorState, n node) string {
|
||||
var draws renderDraws
|
||||
return render(s, n, renderScope{draws: &draws})
|
||||
var frames frameStack
|
||||
return render(s, n, renderScope{frames: &frames})
|
||||
}
|
||||
|
||||
// render evaluates a compiled node to a string. compile validates every node up
|
||||
// front, so rendering a compiled tree cannot fail. sc holds the reference draws the
|
||||
// render shares; each repeat iteration renders over draws of its own.
|
||||
// front, so rendering a compiled tree cannot fail.
|
||||
// A child this switch renders is one renderEdges must list too, or the fences miss it.
|
||||
func render(s *generatorState, n node, sc renderScope) string {
|
||||
switch n := n.(type) {
|
||||
@@ -81,10 +77,9 @@ func render(s *generatorState, n node, sc renderScope) string {
|
||||
}
|
||||
return n.t.cell(row, n.i)
|
||||
case *template:
|
||||
sc = sc.in(n)
|
||||
if n.repeat == 1 {
|
||||
if mark := sc.renderFrame(n); mark >= 0 {
|
||||
defer sc.draws.popFrames(mark)
|
||||
defer sc.frames.pop(mark)
|
||||
}
|
||||
if lit, fixed := n.fixedText(); fixed {
|
||||
return lit
|
||||
@@ -97,8 +92,8 @@ func render(s *generatorState, n node, sc renderScope) string {
|
||||
}
|
||||
}
|
||||
|
||||
// renderRepeat renders each iteration of t as a render of its own, inside the name scopes
|
||||
// rendering t, where a name bound outside t keeps its pick.
|
||||
// renderRepeat renders each iteration of t inside the name scopes rendering t, where a name bound
|
||||
// outside t keeps its pick, and a name t binds picks again.
|
||||
func renderRepeat(s *generatorState, t *template, sc renderScope) string {
|
||||
var b strings.Builder
|
||||
b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
|
||||
@@ -106,23 +101,17 @@ func renderRepeat(s *generatorState, t *template, sc renderScope) string {
|
||||
if i > 0 {
|
||||
b.WriteString(t.separator)
|
||||
}
|
||||
b.WriteString(expandAnew(s, t, sc.draws.frameStack, sc.base))
|
||||
if t.ownNameScope == nil {
|
||||
b.WriteString(expand(s, t, sc))
|
||||
continue
|
||||
}
|
||||
mark := sc.frames.push(newPickFrame(t.ownNameScope))
|
||||
b.WriteString(expand(s, t, sc))
|
||||
sc.frames.pop(mark)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// expandAnew expands one repeat iteration of t as a render of its own, in no group, inside the
|
||||
// name scopes of stack from base. Inlined into renderRepeat's loop, its draws would move to the heap.
|
||||
//
|
||||
//go:noinline
|
||||
func expandAnew(s *generatorState, t *template, stack *frameStack, base int) string {
|
||||
draws := renderDraws{frameStack: stack}
|
||||
if t.ownNameScope != nil {
|
||||
defer draws.popFrames(draws.pushFrame(newPickFrame(t.ownNameScope)))
|
||||
}
|
||||
return expand(s, t, renderScope{draws: &draws, base: base})
|
||||
}
|
||||
|
||||
// pick selects one item. Uniform choices are O(1); weighted choices are an
|
||||
// O(log n) search over precomputed cumulative weights.
|
||||
func pick(s *generatorState, c *choice) node {
|
||||
@@ -135,31 +124,19 @@ func pick(s *generatorState, c *choice) node {
|
||||
func expand(s *generatorState, t *template, sc renderScope) string {
|
||||
var b strings.Builder
|
||||
b.Grow(t.compiled.grow)
|
||||
// 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
|
||||
// path reads its draw group's memo in sc instead.
|
||||
var hold *drawMemo
|
||||
if len(t.compiled.held) > 0 {
|
||||
hold = &drawMemo{
|
||||
variant: make(map[string]node, len(t.compiled.held)),
|
||||
value: make(map[string]readValue, len(t.compiled.held)),
|
||||
}
|
||||
}
|
||||
for i := range t.compiled.ops {
|
||||
o := &t.compiled.ops[i]
|
||||
switch o.kind {
|
||||
case literalRun:
|
||||
b.WriteString(o.lit)
|
||||
case nameRead:
|
||||
b.WriteString(readField(s, t, hold, sc, o.arms[s.IntN(len(o.arms))]).text)
|
||||
b.WriteString(readField(s, t, sc, o.arms[s.IntN(len(o.arms))]).text)
|
||||
case builtinCall:
|
||||
// Read before the call, so the value a calc computes is the value the
|
||||
// format showed. calcVars fixed the order op.operands holds.
|
||||
var operands []string
|
||||
if len(o.operands) > 0 {
|
||||
operands = make([]string, len(o.operands))
|
||||
for j, a := range o.operands {
|
||||
operands[j] = readField(s, t, hold, sc, a).text
|
||||
operands[j] = readField(s, t, sc, a).text
|
||||
}
|
||||
}
|
||||
b.WriteString(o.call(s, b.String(), operands)) // b.String() is the output so far
|
||||
@@ -168,19 +145,10 @@ func expand(s *generatorState, t *template, sc renderScope) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// readField renders one arm of a token. A field the expansion holds — a level some
|
||||
// token addresses by a dotted path that is not a reference, or a field an operand
|
||||
// reads — is drawn once and kept in hold, so {place.postal-code} and {place.locality}
|
||||
// read one row, either read twice gives one value, and a shown operand is the operand
|
||||
// computed. A field a read of a name addresses is kept in that name's pick.
|
||||
// Every other field is drawn afresh, so {word} {word} still draws twice.
|
||||
func readField(s *generatorState, t *template, hold *drawMemo, sc renderScope, a arm) readValue {
|
||||
if a.kind == refPathRead {
|
||||
return readReference(s, t, sc, a)
|
||||
}
|
||||
if sc.draws.trace != nil {
|
||||
traceRead(sc.draws.trace, t, sc, a)
|
||||
}
|
||||
// readField renders one arm of a token. A read of a name, or of a level a read of the name
|
||||
// rendering addresses, is kept in that name's pick; every other read draws afresh, so {word}
|
||||
// {word} draws twice and {p.a} {p.b} reads two draws of p.
|
||||
func readField(s *generatorState, t *template, sc renderScope, a arm) readValue {
|
||||
switch {
|
||||
case a.kind == namedRead:
|
||||
return readName(s, sc, a)
|
||||
@@ -188,56 +156,18 @@ func readField(s *generatorState, t *template, hold *drawMemo, sc renderScope, a
|
||||
return readUnder(s, t, sc, a)
|
||||
}
|
||||
sc.pick = nil
|
||||
switch {
|
||||
case a.kind == freshRead && isRef(a.head):
|
||||
return readValue{text: render(s, t.head(a.head), sc.entering())}
|
||||
case a.kind == freshRead:
|
||||
return readValue{text: render(s, t.head(a.head), sc)}
|
||||
}
|
||||
return readMemo(s, t, hold, nil, sc, a)
|
||||
}
|
||||
|
||||
// readReference reads a reference path, kept in its draw group's memo, so its draw
|
||||
// spans the render.
|
||||
func readReference(s *generatorState, t *template, sc renderScope, a arm) readValue {
|
||||
if sc.draws.trace != nil {
|
||||
traceRead(sc.draws.trace, t, sc, a)
|
||||
}
|
||||
group := sc.groupDraws()
|
||||
return readMemo(s, t, &group.memo, &group.pins, sc, a)
|
||||
}
|
||||
|
||||
func readMemo(s *generatorState, t *template, memo *drawMemo, pins *pinSet, sc renderScope, a arm) readValue {
|
||||
if r, done := memo.value[a.path]; done {
|
||||
return r
|
||||
}
|
||||
leaf, leafPins := drawSteps(s, t.head(a.head), a.steps, pins, memo, a.levels)
|
||||
mark := -1
|
||||
if pins != nil {
|
||||
sc, mark = sc.at(leaf, leafPins).enter(leaf, memo)
|
||||
if !isRef(a.head) {
|
||||
leaf, _ := drawSteps(s, t.head(a.head), a.steps, nil, nil, a.levels)
|
||||
return renderLeaf(s, leaf, sc)
|
||||
}
|
||||
var pins pinSet
|
||||
leaf, leafPins := drawSteps(s, t.head(a.head), a.steps, &pins, nil, a.levels)
|
||||
sc, mark := sc.at(leaf, leafPins).enter(leaf, nil)
|
||||
r := renderLeaf(s, leaf, sc)
|
||||
if mark >= 0 {
|
||||
sc.draws.popFrames(mark)
|
||||
}
|
||||
if memo.value == nil {
|
||||
memo.value = map[string]readValue{}
|
||||
}
|
||||
memo.value[a.path] = r
|
||||
sc.frames.pop(mark)
|
||||
return r
|
||||
}
|
||||
|
||||
func traceRead(trace renderTrace, t *template, sc renderScope, a arm) {
|
||||
var row renderedRow
|
||||
switch {
|
||||
case t.site.isCell():
|
||||
row = renderedRow{t.site.table, t.site.row}
|
||||
case t.site.isFormat():
|
||||
row = renderedRow{t.site.table, sc.rowOf(t.site.table)}
|
||||
}
|
||||
trace(strings.Clone(sc.group), row, a)
|
||||
}
|
||||
|
||||
// renderLeaf draws and renders what a read lands on: null on a null item, or on a column of one
|
||||
// reference alone whose read drew null.
|
||||
func renderLeaf(s *generatorState, n node, sc renderScope) readValue {
|
||||
@@ -247,15 +177,14 @@ func renderLeaf(s *generatorState, n node, sc renderScope) readValue {
|
||||
return readValue{null: true}
|
||||
case *template:
|
||||
if leaf.link.readsColumn != nil {
|
||||
return readReference(s, leaf, sc.in(leaf), leaf.link.readsColumn.a)
|
||||
return readField(s, leaf, sc, leaf.link.readsColumn.a)
|
||||
}
|
||||
}
|
||||
return readValue{text: render(s, n, sc)}
|
||||
}
|
||||
|
||||
// resolveChoice resolves a choice to one variant, so a held head is a concrete node the
|
||||
// rest of the expansion shares. Nested choices unwrap too: a draw is one value, not
|
||||
// another set to pick from.
|
||||
// resolveChoice resolves a choice to one variant. Nested choices unwrap too: a draw is one value,
|
||||
// not another set to pick from.
|
||||
func resolveChoice(s *generatorState, n node) node {
|
||||
for c, ok := n.(*choice); ok; c, ok = n.(*choice) {
|
||||
n = pick(s, c)
|
||||
|
||||
@@ -95,6 +95,7 @@ type structFields struct {
|
||||
typ reflect.Type
|
||||
label string
|
||||
tags map[string]any
|
||||
paths map[string]string // each field tagged with a path, and the path
|
||||
shape *structShape
|
||||
}
|
||||
|
||||
@@ -104,11 +105,12 @@ func (sc *structCompile) compileShape(t reflect.Type, label string) (*structShap
|
||||
}
|
||||
sc.visiting[t] = true
|
||||
defer delete(sc.visiting, t)
|
||||
c := &structFields{structCompile: sc, typ: t, label: label, tags: map[string]any{}, shape: &structShape{}}
|
||||
c := &structFields{structCompile: sc, typ: t, label: label, tags: map[string]any{}, paths: map[string]string{}, shape: &structShape{}}
|
||||
if err := c.gatherFields(t, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(c.tags) > 0 {
|
||||
bindShared(sc.root, c.tags, c.paths)
|
||||
if err := c.shape.compileRecord(sc.root, t, label, c.tags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -172,11 +174,14 @@ func (c *structFields) addTag(sf reflect.StructField, tag string) error {
|
||||
if visible, ok := c.typ.FieldByName(sf.Name); !ok || !slices.Equal(visible.Index, sf.Index) {
|
||||
return fmt.Errorf("%s.%s: hidden by another field named %s, so its fake tag cannot fill it; rename one", c.label, fieldPath(c.typ, sf.Index), sf.Name)
|
||||
}
|
||||
v, err := tagValue(sf, tag)
|
||||
v, path, err := tagValue(sf, tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s.%s: %w", c.label, sf.Name, err)
|
||||
}
|
||||
c.tags[sf.Name] = v
|
||||
if path != "" {
|
||||
c.paths[sf.Name] = path
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -215,22 +220,53 @@ func (c *structFields) nest(sf reflect.StructField, elem reflect.Type) error {
|
||||
}
|
||||
|
||||
// tagValue reads a field's fake tag as the value its column compiles from: an inline template
|
||||
// as written, or a path as the reference {/path}.
|
||||
func tagValue(sf reflect.StructField, tag string) (any, error) {
|
||||
// as written, or a path as the reference {/path}, returning the path too.
|
||||
func tagValue(sf reflect.StructField, tag string) (any, string, error) {
|
||||
if err := checkTaggedType(sf); err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
inline, err := isTemplate(tag)
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
case inline:
|
||||
return inputValue(tag)
|
||||
v, err := inputValue(tag)
|
||||
return v, "", err
|
||||
}
|
||||
if err := checkPathNames(tag); err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
return "{/" + tag + "}", nil
|
||||
return "{/" + tag + "}", tag, nil
|
||||
}
|
||||
|
||||
// bindShared has the fields whose paths read into one category, two or more of them and none
|
||||
// selecting a row, read one pick of it through a name, as a record's columns read one pick:
|
||||
// docs/decisions.md#a-structs-path-tags-into-one-category-read-one-pick-of-it
|
||||
func bindShared(root *folder, tags map[string]any, paths map[string]string) {
|
||||
byCategory := map[string][]string{}
|
||||
for _, field := range sortedNames(paths) {
|
||||
segs, err := splitPath(paths[field])
|
||||
if err != nil || hasSelector(segs) {
|
||||
continue
|
||||
}
|
||||
if _, i := folderAt(root, segs); i < len(segs) {
|
||||
category := strings.Join(segs[:i+1], ".")
|
||||
byCategory[category] = append(byCategory[category], field)
|
||||
}
|
||||
}
|
||||
var format strings.Builder
|
||||
for _, category := range sortedNames(byCategory) {
|
||||
fields := byCategory[category]
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
name := strings.ReplaceAll(category, ".", "-")
|
||||
fmt.Fprintf(&format, "{/%s as %s}", category, name)
|
||||
for _, field := range fields {
|
||||
tags[field] = "{" + name + strings.TrimPrefix(paths[field], category) + "}"
|
||||
}
|
||||
}
|
||||
tags["format"] = format.String()
|
||||
}
|
||||
|
||||
// checkTaggedType rejects a tagged field no column can fill.
|
||||
@@ -253,7 +289,6 @@ func checkTaggedType(sf reflect.StructField) error {
|
||||
// compileRecord compiles the tagged fields of t as one record, and proves each column holds
|
||||
// only what its field's Go type can.
|
||||
func (s *structShape) compileRecord(root *folder, t reflect.Type, label string, tags map[string]any) error {
|
||||
tags["format"] = ""
|
||||
n, err := compile(tags)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", label, err)
|
||||
@@ -318,8 +353,8 @@ func (k columnKind) holds(v proven) bool {
|
||||
// fill draws the record into v's tagged fields, then each nested struct as a record of its own.
|
||||
func (s *structShape) fill(state *generatorState, v reflect.Value) {
|
||||
if s.record != nil {
|
||||
var draws renderDraws
|
||||
for i, c := range renderRecord(state, s.record, s.columns, renderScope{draws: &draws}).columns {
|
||||
var frames frameStack
|
||||
for i, c := range renderRecord(state, s.record, s.columns, renderScope{frames: &frames}).columns {
|
||||
setColumn(fieldAt(v, s.fieldIndexes[i]), c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// table is a category whose rows come from a TSV beside it: the header names the
|
||||
// columns, each row is one draw, and the format renders the drawn row. A column is a
|
||||
// cell of the row a render pinned; a cell carrying tokens compiles to a string node.
|
||||
// cell of the row a path pinned; a cell carrying tokens compiles to a string node.
|
||||
type table struct {
|
||||
segment string // path's last segment
|
||||
path string // the category's path from the data root, which a selector is written at
|
||||
@@ -493,7 +493,7 @@ func (t *table) drawUnder(s *generatorState, pr int) int {
|
||||
return rows[pickCum(s, lookup.childCum[k])]
|
||||
}
|
||||
|
||||
// drawIn is the render's row of t: the one pinned in p, else one drawn inside the nearest
|
||||
// drawIn is the row of t a path or a named pick reads: the one pinned in p, else one drawn inside the nearest
|
||||
// pinned ancestor — its parent drawn inside that first where the ancestor is further up — or
|
||||
// over the whole table, and pinned with its ancestors.
|
||||
func (t *table) drawIn(s *generatorState, p *pinSet) int {
|
||||
@@ -599,11 +599,3 @@ func (t *table) selectorSpelling(r int) string {
|
||||
}
|
||||
return fmt.Sprintf("%s line %d", t.file, r+2)
|
||||
}
|
||||
|
||||
// familyRoot is the table a chain of parents ends at.
|
||||
func (t *table) familyRoot() *table {
|
||||
for t.parentT != nil {
|
||||
t = t.parentT
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user