Close the row set over ancestors only where the render pins the row
This commit is contained in:
+3
-1
@@ -18,7 +18,9 @@ replacement, and each removed path, column or flag.
|
|||||||
`parent`, a name repeating inside one parent row, a name spelling another row's key, a table named like a column of any
|
`parent`, a name repeating inside one parent row, a name spelling another row's key, a table named like a column of any
|
||||||
table above it, a table whose format or cell references its own family, and,
|
table above it, a table whose format or cell references its own family, and,
|
||||||
within one render and draw group, a path drawing a table another path selects a
|
within one render and draw group, a path drawing a table another path selects a
|
||||||
row of, or two paths pinning different rows of one table.
|
row of, or two paths pinning different rows of one table. Two reads are held to one
|
||||||
|
draw only where a render can pin both their rows: a path read pins a row's ancestors
|
||||||
|
with it, while a table read whole draws a row and pins none.
|
||||||
- `New` refuses a root choice of templates sharing one format and one set of string
|
- `New` refuses a root choice of templates sharing one format and one set of string
|
||||||
fields, naming the rows TSV to write instead.
|
fields, naming the rows TSV to write instead.
|
||||||
- `misc.territory`, `misc.currency`, `misc.language`, `misc.httpstatus` and
|
- `misc.territory`, `misc.currency`, `misc.language`, `misc.httpstatus` and
|
||||||
|
|||||||
@@ -1140,8 +1140,9 @@ the Development section below, and who ships a register the four above then draw
|
|||||||
- **The rows of a table are alternatives.** Only one row renders, so a cell in one
|
- **The rows of a table are alternatives.** Only one row renders, so a cell in one
|
||||||
row and a cell in another never meet, and each may select its own row of another
|
row and a cell in another never meet, and each may select its own row of another
|
||||||
table; the cells of one row, and whatever they reach, do meet, and so does the
|
table; the cells of one row, and whatever they reach, do meet, and so does the
|
||||||
format beside them. The rule the fence applies is that two reads meet unless
|
format beside them. The rule the fence applies is that two reads meet unless they
|
||||||
they sit in two rows of one table, or in a row outside a selected ancestor's. A
|
sit in two rows of one table, or in rows no render pins at once: a path read pins a
|
||||||
|
row's ancestors with it, while a table read whole draws a row and pins none. A
|
||||||
choice's items get no such treatment yet: two items selecting different rows
|
choice's items get no such treatment yet: two items selecting different rows
|
||||||
are still refused.
|
are still refused.
|
||||||
- **A table never reaches its own family, by any route.** A `repeat` iteration and a
|
- **A table never reaches its own family, by any route.** A `repeat` iteration and a
|
||||||
|
|||||||
@@ -291,18 +291,20 @@ type drawWalk struct {
|
|||||||
seen map[drawVisit]bool
|
seen map[drawVisit]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawAt is where a walk stands: the draw group it draws in, how the render's root reached it, and
|
// drawAt is where a walk stands: the draw group it draws in, how the render's root reached it, the
|
||||||
// the table rows it entered.
|
// table rows it entered, and the table a whole read draws a row of.
|
||||||
type drawAt struct {
|
type drawAt struct {
|
||||||
group string
|
group string
|
||||||
route drawRoute
|
route drawRoute
|
||||||
alt rowSet
|
alt rowSet
|
||||||
|
drawn *table
|
||||||
}
|
}
|
||||||
|
|
||||||
// rowSet is the rows a walk entered, one per table: only one row of a table renders, so reads in
|
// rowSet is the rows a walk entered, one per table: only one row of a table renders, so reads in
|
||||||
// two rows of one table never meet, while reads in one row, across its columns and whatever they
|
// two rows of one table never meet, while reads in one row, across its columns and whatever they
|
||||||
// reach, do. It simulates what the render pins: enter is (*draws).pin and rowOf is its pinned, and
|
// reach, do. It stands in for the rows the render pins, so a row a path read pins enters with the
|
||||||
// TestRowSetSimulatesPinning holds the two to one answer.
|
// ancestors (*draws).pin pins alongside it, while a table read whole is drawn a row of and pins
|
||||||
|
// none.
|
||||||
type rowSet []tablePin
|
type rowSet []tablePin
|
||||||
|
|
||||||
func (s rowSet) rowOf(t *table) (int, bool) {
|
func (s rowSet) rowOf(t *table) (int, bool) {
|
||||||
@@ -314,24 +316,24 @@ func (s rowSet) rowOf(t *table) (int, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// enter is s with row r of t and the ancestor rows it links to, where t is not in it yet; the set
|
// enter is s with row r of t, where t is not in it yet, and, where pins says the render pins that
|
||||||
// is copied, since walks branch.
|
// row rather than draws it, the ancestor rows it links to. The set is copied, since walks branch.
|
||||||
func (s rowSet) enter(t *table, r int) rowSet {
|
func (s rowSet) enter(t *table, r int, pins bool) rowSet {
|
||||||
if _, in := s.rowOf(t); in {
|
if _, in := s.rowOf(t); in {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
out := append(make(rowSet, 0, len(s)+1), s...)
|
out := append(make(rowSet, 0, len(s)+1), s...)
|
||||||
for {
|
for {
|
||||||
if _, in := out.rowOf(t); in {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
out = append(out, tablePin{t, r})
|
out = append(out, tablePin{t, r})
|
||||||
if t.parentT == nil {
|
if !pins || t.parentT == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
t, r = t.parentT, t.parentRow(r)
|
t, r = t.parentT, t.parentRow(r)
|
||||||
|
if _, in := out.rowOf(t); in {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
sort.Slice(out, func(i, j int) bool { return out[i].t.category < out[j].t.category })
|
}
|
||||||
|
sort.Slice(out, func(i, j int) bool { return out[i].t.path < out[j].t.path })
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,12 +396,15 @@ func (w *drawWalk) walk(n node, at drawAt) {
|
|||||||
if t, isTemplate := n.(*template); isTemplate && t.drawGroupKey != "" {
|
if t, isTemplate := n.(*template); isTemplate && t.drawGroupKey != "" {
|
||||||
at.group = t.drawGroupKey
|
at.group = t.drawGroupKey
|
||||||
}
|
}
|
||||||
|
if t, isTable := n.(*table); isTable {
|
||||||
|
at.drawn = t
|
||||||
|
}
|
||||||
for _, e := range renderEdges(n) {
|
for _, e := range renderEdges(n) {
|
||||||
if cell, isCell := e.to.(*template); isCell && cell.cellOf != nil {
|
if cell, isCell := e.to.(*template); isCell && cell.cellOf != nil {
|
||||||
if at.alt.excludes(cell) {
|
if at.alt.excludes(cell) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w.edge(n, e, drawAt{at.group, at.route, at.alt.enter(cell.cellOf, cell.cellRow)})
|
w.edge(n, e, drawAt{at.group, at.route, at.alt.enter(cell.cellOf, cell.cellRow, cell.cellOf != at.drawn), at.drawn})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w.edge(n, e, at)
|
w.edge(n, e, at)
|
||||||
@@ -430,7 +435,7 @@ func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
|
|||||||
w.reads = append(w.reads, pathRead{at, a, tr})
|
w.reads = append(w.reads, pathRead{at, a, tr})
|
||||||
}
|
}
|
||||||
if tr != nil {
|
if tr != nil {
|
||||||
tr.pins.each(func(t *table, r int) { at.alt = at.alt.enter(t, r) })
|
tr.pins.each(func(t *table, r int) { at.alt = at.alt.enter(t, r, true) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.walk(e.to, at)
|
w.walk(e.to, at)
|
||||||
|
|||||||
+34
-27
@@ -964,9 +964,9 @@ func TestAmbiguousNameNamesARunnablePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRowSetSimulatesPinning proves the load-time walk's rowSet answers what
|
// TestRowSetSimulatesPinning proves the walk's row set answers what the render's pinning
|
||||||
// (*draws).pin and pinned answer at render time: the rows one entry brings with it,
|
// answers: a path read pins a row and the ancestors it links to, a whole read draws a row
|
||||||
// the cells that entry rules out, and which two entries are alternatives.
|
// and pins none.
|
||||||
func TestRowSetSimulatesPinning(t *testing.T) {
|
func TestRowSetSimulatesPinning(t *testing.T) {
|
||||||
f := newGenerator(t, writeFiles(t, geo()), WithSeed(1))
|
f := newGenerator(t, writeFiles(t, geo()), WithSeed(1))
|
||||||
var tables []*table
|
var tables []*table
|
||||||
@@ -979,24 +979,22 @@ func TestRowSetSimulatesPinning(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, entered := range tables {
|
for _, entered := range tables {
|
||||||
for row := 0; row < entered.rows(); row++ {
|
for row := 0; row < entered.rows(); row++ {
|
||||||
s := rowSet(nil).enter(entered, row)
|
if drawn := rowSet(nil).enter(entered, row, false); len(drawn) != 1 || drawn[0] != (tablePin{entered, row}) {
|
||||||
|
t.Errorf("a drawn %s enters %v, want its own row alone", entered.selectorSpelling(row), drawn)
|
||||||
|
}
|
||||||
|
s := rowSet(nil).enter(entered, row, true)
|
||||||
var pinned draws
|
var pinned draws
|
||||||
|
held := 0
|
||||||
pinned.pin(entered, row)
|
pinned.pin(entered, row)
|
||||||
assertRowSetAgrees(t, s, pinned, entered, row, tables)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertRowSetAgrees compares the set entered with row of entered against the same
|
|
||||||
// row pinned, over every row of tables.
|
|
||||||
func assertRowSetAgrees(t *testing.T, s rowSet, pinned draws, entered *table, row int, tables []*table) {
|
|
||||||
t.Helper()
|
|
||||||
pinned.each(func(tbl *table, r int) {
|
pinned.each(func(tbl *table, r int) {
|
||||||
got, in := s.rowOf(tbl)
|
held++
|
||||||
if !in || got != r {
|
if got, in := s.rowOf(tbl); !in || got != r {
|
||||||
t.Errorf("entering %s leaves %s unset in the row set, though pinning it pins %s", entered.selectorSpelling(row), tbl.path, tbl.selectorSpelling(r))
|
t.Errorf("entering %s leaves %s unset, though pinning it pins %s", entered.selectorSpelling(row), tbl.path, tbl.selectorSpelling(r))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
if held != len(s) {
|
||||||
|
t.Errorf("entering %s holds %d rows, though pinning it pins %d", entered.selectorSpelling(row), len(s), held)
|
||||||
|
}
|
||||||
for _, cand := range tables {
|
for _, cand := range tables {
|
||||||
for cr := 0; cr < cand.rows(); cr++ {
|
for cr := 0; cr < cand.rows(); cr++ {
|
||||||
var beside draws
|
var beside draws
|
||||||
@@ -1005,28 +1003,37 @@ func assertRowSetAgrees(t *testing.T, s rowSet, pinned draws, entered *table, ro
|
|||||||
if got := s.excludes(&template{cellOf: cand, cellRow: cr}); got != refused {
|
if got := s.excludes(&template{cellOf: cand, cellRow: cr}); got != refused {
|
||||||
t.Errorf("excludes(%s) = %v beside %s, but pinRow refuses it = %v", cand.selectorSpelling(cr), got, entered.selectorSpelling(row), refused)
|
t.Errorf("excludes(%s) = %v beside %s, but pinRow refuses it = %v", cand.selectorSpelling(cr), got, entered.selectorSpelling(row), refused)
|
||||||
}
|
}
|
||||||
a := drawAt{alt: s}
|
if got := alternatives(drawAt{alt: s}, drawAt{alt: rowSet(nil).enter(cand, cr, true)}); got != refused {
|
||||||
b := drawAt{alt: rowSet(nil).enter(cand, cr)}
|
|
||||||
if got := alternatives(a, b); got != refused {
|
|
||||||
t.Errorf("alternatives(%s, %s) = %v, but pinning both refuses = %v", entered.selectorSpelling(row), cand.selectorSpelling(cr), got, refused)
|
t.Errorf("alternatives(%s, %s) = %v, but pinning both refuses = %v", entered.selectorSpelling(row), cand.selectorSpelling(cr), got, refused)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestCellsOfRowsThatNeverMeetAreNotCompared pins what the entered ancestors buy: a
|
// TestCellReadsMeetWhereTheRenderPairsTheRows pins which cells' reads are one render's to
|
||||||
// cell of region 01 and a cell of a municipality of region 12 never render together,
|
// reconcile: a path read pins the row's ancestors, so a cell of region 01 and a cell of a
|
||||||
// so the reads inside them are not one render's to reconcile.
|
// municipality of region 12 never render together, while a whole read draws its row and pins
|
||||||
func TestCellsOfRowsThatNeverMeetAreNotCompared(t *testing.T) {
|
// no ancestor, so those same two cells do.
|
||||||
files := with(geo(), map[string]string{
|
func TestCellReadsMeetWhereTheRenderPairsTheRows(t *testing.T) {
|
||||||
"addr.json": `{"format":"{city}","city":"Lund"}`,
|
cells := with(geo(), map[string]string{
|
||||||
|
"addr.json": `{"format":"{city}","city":["Lund","Malmö"]}`,
|
||||||
"municipality.tsv": "code\tname\tregion\tpopulation\tnote\n0180\tStockholm\t01\t980000\t-\n0184\tSolna\t01\t85000\t-\n1280\tMalmö\t12\t360000\t{/addr.city}\n1281\tLund\t12\t130000\t-\n1480\tGöteborg\t14\t590000\t-\n",
|
"municipality.tsv": "code\tname\tregion\tpopulation\tnote\n0180\tStockholm\t01\t980000\t-\n0184\tSolna\t01\t85000\t-\n1280\tMalmö\t12\t360000\t{/addr.city}\n1281\tLund\t12\t130000\t-\n1480\tGöteborg\t14\t590000\t-\n",
|
||||||
"region.tsv": "code\tname\tpopulation\tnote\n01\tStockholms län\t2400000\t{/addr}\n12\tSkåne län\t1400000\t-\n14\tVästra Götalands län\t1750000\t-\n",
|
"region.tsv": "code\tname\tpopulation\tnote\n01\tStockholms län\t2400000\t{/addr}\n12\tSkåne län\t1400000\t-\n14\tVästra Götalands län\t1750000\t-\n",
|
||||||
"x.json": `"{/region.note} {/municipality.note}"`,
|
|
||||||
})
|
})
|
||||||
f, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, files)), WithSeed(1))
|
paths := with(cells, map[string]string{"x.json": `"{/region.note} {/municipality.note}"`})
|
||||||
|
f, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, paths)), WithSeed(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("New = %v, want the two cells accepted", err)
|
t.Fatalf("New = %v, want the two cells accepted", err)
|
||||||
}
|
}
|
||||||
fake(t, f, "x")
|
fake(t, f, "x")
|
||||||
|
whole := with(cells, map[string]string{
|
||||||
|
"municipality.json": `{"format":"{name}={note}","rows":"municipality.tsv","key":"code","name":"name","parent":"region","weight":"population"}`,
|
||||||
|
"region.json": `{"format":"{name}={note}","rows":"region.tsv","key":"code","name":"name","weight":"population"}`,
|
||||||
|
"x.json": `"{/region}|{/municipality}"`,
|
||||||
|
})
|
||||||
|
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, whole)), WithSeed(1)); err == nil || !strings.Contains(err.Error(), "reads a path into") {
|
||||||
|
t.Fatalf("New = %v, want the two drawn rows held to one draw of addr", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user