557 lines
16 KiB
Go
557 lines
16 KiB
Go
package fejkdata
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// drawSet is one render's reference draws: the unnamed draw group's, and each named one's.
|
|
type drawSet struct {
|
|
unnamed draws
|
|
named map[string]*draws
|
|
}
|
|
|
|
// drawScope is where a render reads its reference paths: a draw set, in the draw group of the
|
|
// template rendering, and the row a table's format is rendering, which its columns read.
|
|
type drawScope struct {
|
|
set *drawSet
|
|
group string
|
|
t *table
|
|
row int
|
|
}
|
|
|
|
// pinned is the row the render pinned for t, if any.
|
|
func (d *draws) pinned(t *table) (int, bool) {
|
|
for _, p := range d.pins[:d.npins] {
|
|
if p.t == t {
|
|
return p.row, true
|
|
}
|
|
}
|
|
r, ok := d.more[t]
|
|
return r, ok
|
|
}
|
|
|
|
// mustRow is the row pinned for t, which the walk reaching a column pinned.
|
|
func (d *draws) mustRow(t *table) int {
|
|
r, ok := d.pinned(t)
|
|
if !ok {
|
|
panic(fmt.Sprintf("fejkdata: a column of %s is rendered with no row pinned", t.category))
|
|
}
|
|
return r
|
|
}
|
|
|
|
// pin pins row r of t, and the rows of t's ancestors it links to.
|
|
func (d *draws) pin(t *table, r int) {
|
|
for {
|
|
if _, done := d.pinned(t); done {
|
|
return
|
|
}
|
|
if d.npins < len(d.pins) {
|
|
d.pins[d.npins] = tablePin{t, r}
|
|
d.npins++
|
|
} else {
|
|
if d.more == nil {
|
|
d.more = map[*table]int{}
|
|
}
|
|
d.more[t] = r
|
|
}
|
|
if t.parentT == nil {
|
|
return
|
|
}
|
|
t, r = t.parentT, t.parentRow(r)
|
|
}
|
|
}
|
|
|
|
// rowOf is the render's row of t: the one pinned, 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 (d *draws) rowOf(t *table) int {
|
|
if r, ok := d.pinned(t); ok {
|
|
return r
|
|
}
|
|
r := -1
|
|
for a := t.parentT; a != nil && r < 0; a = a.parentT {
|
|
if _, ok := d.pinned(a); !ok {
|
|
continue
|
|
}
|
|
if t.parentT != a {
|
|
d.rowOf(t.parentT)
|
|
}
|
|
pr, _ := d.pinned(t.parentT)
|
|
r = t.drawUnder(d.s, pr)
|
|
}
|
|
if r < 0 {
|
|
r = t.draw(d.s)
|
|
}
|
|
d.pin(t, r)
|
|
return r
|
|
}
|
|
|
|
// selectRow pins the row a selector names, refusing one outside the rows pinned
|
|
// before it.
|
|
func (d *draws) selectRow(t *table, sel string) error {
|
|
r, err := t.find(sel, d)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if pr, ok := d.pinned(t); ok && pr != r {
|
|
return fmt.Errorf("%s is not %s, the row already drawn", t.selectorSpelling(r), t.selectorSpelling(pr))
|
|
}
|
|
for a := t.parentT; a != nil; a = a.parentT {
|
|
if pa, ok := d.pinned(a); ok && !t.under(r, a, pa) {
|
|
return fmt.Errorf("%s is not inside %s", t.selectorSpelling(r), a.selectorSpelling(pa))
|
|
}
|
|
}
|
|
d.pin(t, r)
|
|
return nil
|
|
}
|
|
|
|
// inside keeps the rows of t that sit inside every pinned ancestor.
|
|
func (d *draws) inside(t *table, rows []int) []int {
|
|
for a := t.parentT; a != nil; a = a.parentT {
|
|
pa, ok := d.pinned(a)
|
|
if !ok {
|
|
continue
|
|
}
|
|
var kept []int
|
|
for _, r := range rows {
|
|
if t.under(r, a, pa) {
|
|
kept = append(kept, r)
|
|
}
|
|
}
|
|
rows = kept
|
|
}
|
|
return rows
|
|
}
|
|
|
|
// newDrawSet makes the unnamed group's maps where the set is declared, keeping them on that frame's
|
|
// stack for a render that reads through them; a zero drawSet makes them on its first read instead.
|
|
func newDrawSet(s *session) drawSet {
|
|
return drawSet{unnamed: draws{variant: map[string]node{}, value: map[string]draw{}, s: s}}
|
|
}
|
|
|
|
// renderOnce renders n as one render, over draws of its own.
|
|
func renderOnce(s *session, n node) string {
|
|
set := drawSet{unnamed: draws{s: s}}
|
|
return render(s, n, drawScope{set: &set})
|
|
}
|
|
|
|
// in is the scope t renders in: its draw group where it names one, else its caller's.
|
|
func (sc drawScope) in(t *template) drawScope {
|
|
if t.drawGroupKey != "" {
|
|
sc.group = t.drawGroupKey
|
|
}
|
|
return sc
|
|
}
|
|
|
|
// draws is the set's draws for sc's draw group. The session is passed in rather than
|
|
// read out of the set: copying it from there would leak the set's maps to the heap.
|
|
func (sc drawScope) draws(s *session) *draws {
|
|
if sc.group == "" {
|
|
return &sc.set.unnamed
|
|
}
|
|
d, drew := sc.set.named[sc.group]
|
|
if !drew {
|
|
if sc.set.named == nil {
|
|
sc.set.named = map[string]*draws{}
|
|
}
|
|
d = &draws{variant: map[string]node{}, value: map[string]draw{}, s: s}
|
|
sc.set.named[strings.Clone(sc.group)] = d // a key from sc would leak sc, and with it every render's draw set, to the heap
|
|
}
|
|
return d
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// keyDrawGroup keys t's draw group by the category t sits in, "" for an inline template, so a name
|
|
// is local to its category.
|
|
func (t *template) keyDrawGroup(category string) {
|
|
if t.drawGroup != "" {
|
|
t.drawGroupKey = category + "/" + t.drawGroup
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// drawCheck fences each template of a scope as a render of its own, remembering which nodes read a
|
|
// reference path.
|
|
type drawCheck struct {
|
|
memo map[readsMemo]bool
|
|
}
|
|
|
|
type readsMemo struct {
|
|
n node
|
|
stopAtGroup bool
|
|
}
|
|
|
|
// 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 (c *drawCheck) checkDrawGroup(path string, n node) error {
|
|
if t, ok := n.(*template); ok && t.drawGroup != "" && !c.splitsDraws(t) {
|
|
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)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *drawCheck) checkDraws(path string, n node) error {
|
|
t, ok := n.(*template)
|
|
if !ok || !c.readsPath(t) {
|
|
return nil
|
|
}
|
|
w := newDrawWalk()
|
|
for _, e := range renderEdges(t) {
|
|
w.edge(t, e, drawAt{group: t.drawGroupKey, route: drawRoute{e.reached(), e.label}})
|
|
}
|
|
if err := w.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.
|
|
func (c *drawCheck) checkRecordDraws(path string, n node) error {
|
|
t, ok := n.(*template)
|
|
if !ok || !t.record {
|
|
return nil
|
|
}
|
|
// A record-only template's format renders nothing, so weigh the columns, not the format.
|
|
columns := recordColumns(t)
|
|
reads := false
|
|
for _, name := range columns {
|
|
reads = reads || c.readsPath(t.fields[name])
|
|
}
|
|
if !reads {
|
|
return nil
|
|
}
|
|
if err := checkColumnDraws(t, columns); err != nil {
|
|
return fmt.Errorf("%s: %w", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// readsPath reports whether rendering n reads a reference path, short of a repeat, which renders
|
|
// over draws of its own.
|
|
func (c *drawCheck) readsPath(n node) bool { return c.reads(n, false) }
|
|
|
|
// splitsDraws reports whether rendering n reads a reference path that n's own draw group answers
|
|
// for: one outside a repeat and outside a nested draw group, which hold their own draws.
|
|
func (c *drawCheck) splitsDraws(n node) bool { return c.reads(n, true) }
|
|
|
|
// reads walks what rendering n renders for a reference path, stopping at a repeat — and at a nested
|
|
// draw group when stopAtGroup — since each holds draws of its own.
|
|
func (c *drawCheck) reads(n node, stopAtGroup bool) bool {
|
|
k := readsMemo{n, stopAtGroup}
|
|
if r, done := c.memo[k]; done {
|
|
return r
|
|
}
|
|
r := false
|
|
for _, e := range renderEdges(n) {
|
|
if a, isRef := refRead(n, e.label); (isRef && (len(a.tail) > 0 || readsTable(n, a))) || (!repeats(e.to) && !(stopAtGroup && grouped(e.to)) && c.reads(e.to, stopAtGroup)) {
|
|
r = true
|
|
break
|
|
}
|
|
}
|
|
if c.memo == nil {
|
|
c.memo = map[readsMemo]bool{}
|
|
}
|
|
c.memo[k] = r
|
|
return r
|
|
}
|
|
|
|
func repeats(n node) bool {
|
|
t, isTemplate := n.(*template)
|
|
return isTemplate && t.repeat > 1
|
|
}
|
|
|
|
func grouped(n node) bool {
|
|
t, isTemplate := n.(*template)
|
|
return isTemplate && t.drawGroupKey != ""
|
|
}
|
|
|
|
// readsTable reports whether a reference of n names a table, whose draw the render's
|
|
// group answers for even when read whole.
|
|
func readsTable(n node, a arm) bool {
|
|
t, isTemplate := n.(*template)
|
|
if !isTemplate {
|
|
return false
|
|
}
|
|
_, isTable := t.fields[a.key].(*table)
|
|
return isTable
|
|
}
|
|
|
|
// refRead is the reference an edge of n reads; false when the edge reads none.
|
|
func refRead(n node, label string) (arm, bool) {
|
|
t, isTemplate := n.(*template)
|
|
if !isTemplate {
|
|
return arm{}, false
|
|
}
|
|
a := splitArm(label, t.refs)
|
|
return a, isRef(a.key)
|
|
}
|
|
|
|
// checkColumnDraws fences a record's columns as one render.
|
|
func checkColumnDraws(t *template, columns []string) error {
|
|
w := newDrawWalk()
|
|
for _, name := range columns {
|
|
w.walk(t.fields[name], drawAt{group: t.drawGroupKey, route: drawRoute{spelling: fmt.Sprintf("column %q", name)}})
|
|
}
|
|
return w.check()
|
|
}
|
|
|
|
// drawWalk gathers the references one render reads, by draw group, for check to compare.
|
|
type drawWalk struct {
|
|
reads []pathRead
|
|
read map[drawKey]bool
|
|
seen map[drawVisit]bool
|
|
}
|
|
|
|
// drawAt is where a walk stands: the draw group it draws in, and how the render's root reached it.
|
|
type drawAt struct {
|
|
group string
|
|
route drawRoute
|
|
}
|
|
|
|
// drawRoute is how a render reaches a draw: as its author spells it, and the root edge's label.
|
|
type drawRoute struct{ spelling, label string }
|
|
|
|
// pathRead is one reference a render reads: a path, or a bare reference with no tail.
|
|
type pathRead struct {
|
|
at drawAt
|
|
a arm
|
|
tr *tableRead // set where the reference names a table
|
|
}
|
|
|
|
// tableRead is what a reference reads of a table family: the table named, the rows
|
|
// its selectors pin along the way, and whether it lands on a row rendered whole.
|
|
type tableRead struct {
|
|
head *table
|
|
sels []tableSel
|
|
whole bool
|
|
}
|
|
|
|
// tableSel is one selector on the way: the table it selects a row of, and the
|
|
// selector's spelling up to its closing bracket.
|
|
type tableSel struct {
|
|
t *table
|
|
row int
|
|
spelling string
|
|
}
|
|
|
|
// tableReadOf reads what a reference path does of a table, replaying its selectors
|
|
// over a walk of its own — checkPath proved each names a row — so two paths naming
|
|
// one row by key and by name compare equal.
|
|
func tableReadOf(head node, a arm, leaf node) *tableRead {
|
|
t, isTable := head.(*table)
|
|
if !isTable {
|
|
return nil
|
|
}
|
|
tr := &tableRead{head: t}
|
|
var pins draws
|
|
cur, seen := t, a.name
|
|
for _, seg := range a.tail {
|
|
switch {
|
|
case isSelector(seg):
|
|
end := strings.Index(seen, "]") + 1
|
|
_ = pins.selectRow(cur, selectorOf(seg))
|
|
row, _ := pins.pinned(cur)
|
|
tr.sels = append(tr.sels, tableSel{cur, row, a.name[:len(a.name)-len(seen)+end]})
|
|
seen = seen[end:]
|
|
case cur.children[seg] != nil:
|
|
cur = cur.children[seg]
|
|
}
|
|
}
|
|
c, isColumn := leaf.(*column)
|
|
tr.whole = isColumn && c.i < 0
|
|
return tr
|
|
}
|
|
|
|
// family is the table a chain of parents ends at.
|
|
func (t *table) family() *table {
|
|
for t.parentT != nil {
|
|
t = t.parentT
|
|
}
|
|
return t
|
|
}
|
|
|
|
// selected is the selector in r that pins t, or the nearest ancestor of t it pins.
|
|
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
|
|
}
|
|
|
|
func (r *tableRead) selection() string {
|
|
parts := make([]string, len(r.sels))
|
|
for i, s := range r.sels {
|
|
parts[i] = fmt.Sprintf("%s[%d]", s.t.category, s.row)
|
|
}
|
|
return strings.Join(parts, " ")
|
|
}
|
|
|
|
type drawVisit struct {
|
|
n node
|
|
group string
|
|
}
|
|
|
|
type drawKey struct {
|
|
group string
|
|
path string
|
|
}
|
|
|
|
func newDrawWalk() *drawWalk {
|
|
return &drawWalk{read: map[drawKey]bool{}, seen: map[drawVisit]bool{}}
|
|
}
|
|
|
|
// walk follows what rendering n renders. A repeat renders over draws of its own, so the walk stops
|
|
// there.
|
|
func (w *drawWalk) walk(n node, at drawAt) {
|
|
v := drawVisit{n, at.group}
|
|
if w.seen[v] {
|
|
return
|
|
}
|
|
w.seen[v] = true
|
|
if repeats(n) {
|
|
return
|
|
}
|
|
if t, isTemplate := n.(*template); isTemplate && t.drawGroupKey != "" {
|
|
at.group = t.drawGroupKey
|
|
}
|
|
for _, e := range renderEdges(n) {
|
|
w.edge(n, e, at)
|
|
}
|
|
}
|
|
|
|
func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
|
|
if a, reads := refRead(from, e.label); reads {
|
|
if k := (drawKey{at.group, a.path}); !w.read[k] {
|
|
w.read[k] = true
|
|
w.reads = append(w.reads, pathRead{at, a, tableReadOf(from.(*template).fields[a.key], a, e.to)})
|
|
}
|
|
}
|
|
w.walk(e.to, at)
|
|
}
|
|
|
|
// check refuses what one draw per reference path cannot answer for: a read of a level beside a path
|
|
// another read takes into it, and two reads of one table family that select different rows. Reads
|
|
// are compared in path order, so which pair is reported does not vary.
|
|
func (w *drawWalk) check() error {
|
|
sort.SliceStable(w.reads, func(i, j int) bool {
|
|
if w.reads[i].at.group != w.reads[j].at.group {
|
|
return w.reads[i].at.group < w.reads[j].at.group
|
|
}
|
|
return w.reads[i].a.path < w.reads[j].a.path
|
|
})
|
|
for i, level := range w.reads {
|
|
for _, into := range w.reads[i+1:] {
|
|
if into.at.group != level.at.group {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.whole) {
|
|
return overlapError(level.at.route, level.a.name, into)
|
|
}
|
|
if err := checkFamily(level, into); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkFamily refuses two reads of one table family in one group that cannot read one consistent
|
|
// draw: a table rendered whole beside a path into the family, and two paths selecting different rows.
|
|
func checkFamily(a, b pathRead) error {
|
|
if a.tr == nil || b.tr == nil || a.tr.head.family() != b.tr.head.family() {
|
|
return nil
|
|
}
|
|
for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
|
|
bare, path := pair[0], pair[1]
|
|
if len(bare.a.tail) == 0 && len(path.a.tail) > 0 {
|
|
return overlapError(bare.at.route, bare.a.name, path)
|
|
}
|
|
}
|
|
if len(a.a.tail) == 0 || len(b.a.tail) == 0 || a.tr.selection() == b.tr.selection() {
|
|
return nil
|
|
}
|
|
for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
|
|
selected, plain := pair[0], pair[1]
|
|
if len(plain.tr.sels) > 0 {
|
|
continue
|
|
}
|
|
if s, ok := selected.tr.selected(plain.tr.head); ok {
|
|
tail := plain.a.tail
|
|
if s.t != plain.tr.head {
|
|
tail = append([]string{plain.tr.head.category}, tail...)
|
|
}
|
|
return fmt.Errorf("%s reads %s without the row %s selects; write {%s.%s}, or draw them apart with a drawGroup",
|
|
plain.at.route.spelled(plain.a.name), plain.tr.head.category, selected.at.route.spelled(selected.a.name), s.spelling, strings.Join(tail, "."))
|
|
}
|
|
}
|
|
return fmt.Errorf("%s and %s select different rows of one table family; select the same rows in both, or draw them apart with a drawGroup",
|
|
a.at.route.spelled(a.a.name), b.at.route.spelled(b.a.name))
|
|
}
|
|
|
|
func overlapError(route drawRoute, ref string, into pathRead) error {
|
|
return fmt.Errorf("%s renders a level that %s reads a path into; name the fields you want instead, or draw them apart with a drawGroup", route.spelled(ref), into.at.route.spelled(into.a.name))
|
|
}
|
|
|
|
// spelled names the route, and the reference it reaches a draw by where its root edge is not that
|
|
// reference.
|
|
func (r drawRoute) spelled(ref string) string {
|
|
if ref == "" || ref == r.label {
|
|
return r.spelling
|
|
}
|
|
return fmt.Sprintf("%s with {%s}", r.spelling, ref)
|
|
}
|