cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Blocked arrays close cycles over the cells they need

Dami committed 8c15188
An array blocked by a value, only as big as another's spill makes it,
and reading what that other needs, let the other spill or not by the
order they were computed in: undoing a deleted column left it spilled
where opening the file blocked both (TestRandomSharedEdits seed 44689).
The cycle walk counts a blocked array as spilling over the cells it
needs, as it does one blocked by a cycle, the arrays are checked again
when one is blocked over other cells, and a blocked array that goes
frees the cycles through it.
commit
8c1518833dbbad93d2b62d22c4e4d521c099dccb
parent
31f8f178a438fde501dc7f2a6e4920843e9afcff
author
Dami <dami.etoile@gmail.com>
kind
fix risk score 1.9 of 3, Jev's estimate
4 files changed, +60 -8
Mdocs/formulas/arrays.md5++++-
Minternal/sheet/spill.go11++++++++---
Minternal/sheet/spillblock.go23+++++++++++++++++++----
Minternal/sheet/spillcycle_test.go29+++++++++++++++++++++++++++++
M docs/formulas/arrays.md +4 -1
M internal/sheet/spill.go +8 -3
M internal/sheet/spillblock.go +19 -4
@@ -47,7 +47,7 @@ func (s *Sheet) spillsIntoItself(a Addr, area Rect) (string, []loc) {
4747 // An array's cells first, so a cycle through them is found as4848 // one, and the array blocked with it, rather than through the4949 // anchor alone.50- if sp := u.s.spills[u.a]; sp != nil && (sp.why == "" || sp.circular) {50+ if sp := u.s.spills[u.a]; sp != nil && sp.walked() {5151 w.cells(u.s, sp.area, u.a, u, false)5252 }5353 if !w.found {
@@ -241,13 +241,13 @@ func (w *Workbook) recheckCircular() []loc {
241241 }242242 243243 // forgetSpill drops the anchor at a's spill for good, its formula244-// computing one value or none, returning the cells that changed. When a245-// cycle blocked it, it counted as spilling over the cells it needed, so244+// computing one value or none, returning the cells that changed. When245+// blocked, it counted as spilling over the cells it needed (walked), so246246 // the arrays blocked by a cycle are checked again.247247 func (s *Sheet) forgetSpill(a Addr) []loc {248248 old := s.spills[a]249249 changed := s.dropSpill(a)250- if old != nil && old.circular {250+ if old != nil && old.why != "" {251251 changed = append(changed, s.wb.recheckCircular()...)252252 }253253 return changed
@@ -271,3 +271,18 @@ func blockCircular(arrays []loc) []loc {
271271 }272272 return changed273273 }274+275+// walked reports whether spillsIntoItself follows the cells of the276+// array: spilled, or blocked, over the cells it needs, what it would277+// spill over given room. A blocked array whose size follows what it278+// reads may be blocked or free by whether another spills into its279+// inputs, and that other by whether it spills: counted as spilling,280+// the two are blocked together, as when the cycle is found in one go,281+// rather than by the order they were computed in. The cells of an array282+// too big for max-cells aren't walked.283+func (sp *spill) walked() bool {284+ if sp.why == "" || sp.circular {285+ return true286+ }287+ return (sp.area.To.Row-sp.area.From.Row+1)*(sp.area.To.Col-sp.area.From.Col+1) <= MaxCells()288+}
M internal/sheet/spillcycle_test.go +29 -0
@@ -64,3 +64,32 @@ func TestCycleGoneWithAnArray(t *testing.T) {
6464 t.Errorf("C9 opened: %+v", sp)6565 }6666 }67+68+// An array spilling into the inputs of another, blocked by a value but69+// only as wide as it is for the first's spill, and reading what the70+// other needs: counted as spilling, the blocked one closes the cycle,71+// so both are blocked whichever was typed first, as when opened.72+func TestCycleThroughABlockedArray(t *testing.T) {73+ cells := map[string]string{"C9": "1.5", "C13": "=SORT(D19:E20)", "D18": "=SORT(C9:E13)", "E19": "=SEQUENCE(3)"}74+ want := map[string]string{"C13": "#REF!", "D18": "#REF!", "D13": "", "E20": "2"}75+ for _, order := range [][]string{{"C9", "E19", "C13", "D18"}, {"C9", "D18", "C13", "E19"}, {"D18", "C13", "C9", "E19"}} {76+ wantSettled(t, "blocked", order, cells, want)77+ }78+}79+80+// A blocked array that goes, its formula computing one value, frees the81+// arrays whose cycle went through the cells it needed (spill.walked):82+// once Total is defined, B7 reads its own #REF! through G9.83+func TestCycleGoneWithABlockedArray(t *testing.T) {84+ cells := map[string]string{85+ "H9": "=VLOOKUP(G8,G14:G15,1,FALSE)", "E7": "=SORT(F10:G14)", "G9": "=SUM(Total)*2", "B7": "=SORT(G7:H11)",86+ "H5": "=SORT(C4:E8)", "B9": "=VLOOKUP(A13,C14:D18,1,FALSE)", "G18": "=ABS(C14)+1",87+ }88+ s := New()89+ for _, a := range []string{"H9", "E7", "G9", "B7", "H5", "B9"} {90+ s.Set(at(a), cells[a])91+ }92+ s.Book().DefineName("Total", s, NewRect(Addr{}, Addr{Col: 1, Row: 9}))93+ s.Set(at("G18"), cells["G18"])94+ wantShown(t, s, map[string]string{"B7": "#REF!", "H5": ""})95+}