diff --git a/README.md b/README.md index b7edb18..bbb09a1 100644 --- a/README.md +++ b/README.md @@ -886,8 +886,8 @@ format.go the {token} grammar: scanning, tokens, operands, validation, com hold.go the hold: one draw per held name, and reading a field through it holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways holdset.go one reference draw per render and group: the hold set a render reads its paths through -drawfence.go the load fences over reference draws: a drawGroup's, a render's, a record's columns and a table family's -readsurvey.go the load-time survey of what a render reads, and what a path reads of a table family, for the draw fences +drawfence.go the load fences over reference draws: a drawGroup's, a table's own family, and a render's and a record's columns through the survey +readsurvey.go the load-time survey of what a render reads, and what a path reads of a table family, and its verdict on them pins.go the pin set: the table rows a render fixes, which the draw fences replay reference.go reference sigils, and binding references across the tree graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences diff --git a/drawfence.go b/drawfence.go index 9561589..6080817 100644 --- a/drawfence.go +++ b/drawfence.go @@ -1,10 +1,6 @@ package fejkdata -import ( - "fmt" - "sort" - "strings" -) +import "fmt" // checkNestedDrawGroup refuses a template beneath one drawing in group that names group again, // short of a repeat or another draw group. @@ -193,113 +189,3 @@ func readsTable(n node, a arm) bool { // checkColumnDraws fences a record's columns as one render. func checkColumnDraws(t *template, columns []string) error { return surveyColumns(t, columns).check() } - -// 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 (s *readSurvey) check() error { - sort.SliceStable(s.reads, func(i, j int) bool { - if s.reads[i].at.group != s.reads[j].at.group { - return s.reads[i].at.group < s.reads[j].at.group - } - return s.reads[i].a.path < s.reads[j].a.path - }) - for i, level := range s.reads { - for _, into := range s.reads[i+1:] { - if into.at.group != level.at.group || alternatives(level.at, into.at) { - continue - } - if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.landsRow) { - return overlapError(level.at.route, level.a.spelling, into) - } - } - } - return checkFamilies(s.reads) -} - -// checkFamilies refuses reads of one table family in one draw group that cannot -// read one consistent draw: a table rendered whole beside a path into the family, -// a table one read draws that another pins, and two reads pinning different rows. -// The reads come sorted by group and path, so which pair is reported does not vary. -func checkFamilies(reads []pathRead) error { - for i, r := range reads { - if r.tr == nil { - continue - } - for _, o := range reads[:i] { - if o.tr == nil || o.at.group != r.at.group || alternatives(o.at, r.at) || o.tr.headTable.familyRoot() != r.tr.headTable.familyRoot() { - continue - } - if err := checkFamilyPair(o, r); err != nil { - return err - } - } - } - return replayPairs(reads) -} - -// checkFamilyPair refuses a table read whole beside a path into its family, and a -// table one read draws that the other pins, since which token renders first would -// then decide the row. -// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held -func checkFamilyPair(a, b pathRead) error { - for _, pair := range [][2]pathRead{{a, b}, {b, a}} { - x, y := pair[0], pair[1] - if len(x.a.tail) == 0 && len(y.a.tail) > 0 { - return overlapError(x.at.route, x.a.spelling, y) - } - if drawn := x.tr.drawnOf(&y.tr.pins); drawn != 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.%s}, or draw them apart with a drawGroup", - x.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling), s.spelling, joinSegments(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.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling)) - } - } - return nil -} - -// replayPairs replays every two reads that can render together into one draws, the earlier -// read first. Pairs find every conflict a full replay would: clash judges a row against one -// pinned table, and the read that pinned it holds that pin itself, since a read's pins carry -// its rows' ancestors, so the pair of those two reads clashes the same way. -func replayPairs(reads []pathRead) error { - for i, r := range reads { - if r.tr == nil { - continue - } - for _, o := range reads[i+1:] { - if o.tr == nil || o.at.group != r.at.group || alternatives(r.at, o.at) { - continue - } - var d pinSet - if err := r.tr.replay(&d); err != nil { - return conflict(r, err) - } - if err := o.tr.replay(&d); err != nil { - return conflict(o, err) - } - } - } - return nil -} - -func conflict(r pathRead, err error) error { - return fmt.Errorf("%s: %w; select the same rows in every path into the family, or draw them apart with a drawGroup", r.at.route.spelled(r.a.spelling), err) -} - -// alternatives reports whether two reads never render together: one read renders one row of a -// table, so reads under different rows the walks pinned, or under different rows of one whole -// draw, never meet. -func alternatives(a, b surveyAt) bool { - return a.pins.differs(&b.pins) || a.wholePins.differs(&b.wholePins) -} - -func overlapError(route surveyRoute, 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.spelling)) -} diff --git a/readsurvey.go b/readsurvey.go index fb43df6..9490a3f 100644 --- a/readsurvey.go +++ b/readsurvey.go @@ -3,6 +3,7 @@ package fejkdata import ( "fmt" "sort" + "strings" ) // readSurvey gathers, at load, the references one render reads, by draw group, for check to compare. @@ -240,3 +241,113 @@ func (r *tableRead) replay(d *pinSet) error { }) return err } + +// 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 (s *readSurvey) check() error { + sort.SliceStable(s.reads, func(i, j int) bool { + if s.reads[i].at.group != s.reads[j].at.group { + return s.reads[i].at.group < s.reads[j].at.group + } + return s.reads[i].a.path < s.reads[j].a.path + }) + for i, level := range s.reads { + for _, into := range s.reads[i+1:] { + if into.at.group != level.at.group || alternatives(level.at, into.at) { + continue + } + if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.landsRow) { + return overlapError(level.at.route, level.a.spelling, into) + } + } + } + return checkFamilies(s.reads) +} + +// checkFamilies refuses reads of one table family in one draw group that cannot +// read one consistent draw: a table rendered whole beside a path into the family, +// a table one read draws that another pins, and two reads pinning different rows. +// The reads come sorted by group and path, so which pair is reported does not vary. +func checkFamilies(reads []pathRead) error { + for i, r := range reads { + if r.tr == nil { + continue + } + for _, o := range reads[:i] { + if o.tr == nil || o.at.group != r.at.group || alternatives(o.at, r.at) || o.tr.headTable.familyRoot() != r.tr.headTable.familyRoot() { + continue + } + if err := checkFamilyPair(o, r); err != nil { + return err + } + } + } + return replayPairs(reads) +} + +// checkFamilyPair refuses a table read whole beside a path into its family, and a +// table one read draws that the other pins, since which token renders first would +// then decide the row. +// docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held +func checkFamilyPair(a, b pathRead) error { + for _, pair := range [][2]pathRead{{a, b}, {b, a}} { + x, y := pair[0], pair[1] + if len(x.a.tail) == 0 && len(y.a.tail) > 0 { + return overlapError(x.at.route, x.a.spelling, y) + } + if drawn := x.tr.drawnOf(&y.tr.pins); drawn != 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.%s}, or draw them apart with a drawGroup", + x.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling), s.spelling, joinSegments(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.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling)) + } + } + return nil +} + +// replayPairs replays every two reads that can render together into one draws, the earlier +// read first. Pairs find every conflict a full replay would: clash judges a row against one +// pinned table, and the read that pinned it holds that pin itself, since a read's pins carry +// its rows' ancestors, so the pair of those two reads clashes the same way. +func replayPairs(reads []pathRead) error { + for i, r := range reads { + if r.tr == nil { + continue + } + for _, o := range reads[i+1:] { + if o.tr == nil || o.at.group != r.at.group || alternatives(r.at, o.at) { + continue + } + var d pinSet + if err := r.tr.replay(&d); err != nil { + return conflict(r, err) + } + if err := o.tr.replay(&d); err != nil { + return conflict(o, err) + } + } + } + return nil +} + +func conflict(r pathRead, err error) error { + return fmt.Errorf("%s: %w; select the same rows in every path into the family, or draw them apart with a drawGroup", r.at.route.spelled(r.a.spelling), err) +} + +// alternatives reports whether two reads never render together: one read renders one row of a +// table, so reads under different rows the walks pinned, or under different rows of one whole +// draw, never meet. +func alternatives(a, b surveyAt) bool { + return a.pins.differs(&b.pins) || a.wholePins.differs(&b.wholePins) +} + +func overlapError(route surveyRoute, 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.spelling)) +} diff --git a/todo.md b/todo.md index b9f9112..8009fd1 100644 --- a/todo.md +++ b/todo.md @@ -28,9 +28,6 @@ ranking `readSurvey.walk` hardest and all seven the unit they would least modify This round, in order: -- Move `readSurvey`'s verdict — `check`, `checkFamilies`, `checkFamilyPair`, - `replayPairs`, `alternatives`, `conflict` and `overlapError` — from `drawfence.go` - into `readsurvey.go`: both architects looked for it beside the survey. - Record each selector's table and spelling in `pathProbe.readRow`, and delete `tableReadOf`'s second walk over the tail: it steps by `descendant` apart from `route`, so a route that steps up to a parent row credits a selector to the wrong table.