Formulas read other cells through lookup, now a reader with two methods: cell for one cell and cells for a whole range, row by row. each, which SUM, AVERAGE, COUNT, MIN, MAX, their decimal twins, the AND/OR family and the functions collecting numbers or text go through, reads ranges with cells, so the range is one call to the reader: the seam where column blocks, prefix sums or cached aggregates can serve it later. The sheet name is resolved once per range. lookup is a concrete type so the callback stays on the stack; fan-in and running-total recalculation times and allocations are unchanged (within 1%).
- commit
- b6ca379d9bc67600481c1928f3dce24e80dc9f67
- parent
- 6b40936f4c473559e9214ad4892c0ba134cd37c4
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 2 | +- |
| M | internal/ | 14 | ++++--- |
| M | internal/ | 4 | +- |
| M | internal/ | 15 | ++++--- |
| M | internal/ | 4 | +- |
| M | internal/ | 66 | ++++++++++++++++++++++-------- |
@@ -97,7 +97,7 @@ func rectMatrix(sheet string, r Rect, get lookup) matrix {
9797 return matrix{9898 rows: r.To.Row - r.From.Row + 1, cols: r.To.Col - r.From.Col + 1,9999 cell: func(row, col int) Value {100- return get(sheet, Addr{Col: r.From.Col + col, Row: r.From.Row + row})100+ return get.cell(sheet, Addr{Col: r.From.Col + col, Row: r.From.Row + row})101101 },102102 origin: r.From, sheet: sheet, ref: true,103103 }@@ -88,10 +88,12 @@ func toNum(v Value) (float64, *Value) {
8888 return 0, errOf(v)8989 }9090 91-// lookup resolves the current value of a referenced cell: on the92-// formula's own sheet when sheet is "", otherwise on the sheet with that93-// name.94-type lookup func(sheet string, a Addr) Value91+// lookup is how a formula reads other cells (see reader): one at a time,92+// or a whole range at once, so reading a range needn't cost a lookup per93+// cell and can later be served from column blocks or cached aggregates.94+// It is a concrete type rather than an interface so the callbacks given95+// to cells stay on the stack.96+type lookup = *reader9597 9698 func eval(n Node, get lookup) Value {9799 switch n := n.(type) {@@ -102,7 +104,7 @@ func eval(n Node, get lookup) Value {
102104 case formula.Bool:103105 return boolean(n.V)104106 case formula.Ref:105- return get(n.Sheet, n.Addr)107+ return get.cell(n.Sheet, n.Addr)106108 case formula.Name:107109 return ErrName108110 case formula.RefErr:@@ -113,7 +115,7 @@ func eval(n Node, get lookup) Value {
113115 // A range outside a function: Sheets uses the top-left cell here114116 // for single-cell ranges and #VALUE! otherwise.115117 if n.Rect.From == n.Rect.To {116- return get(n.Sheet, n.Rect.From)118+ return get.cell(n.Sheet, n.Rect.From)117119 }118120 return ErrValue119121 case formula.Unary:@@ -82,11 +82,11 @@ func (s *Sheet) errorOrigin(n Node, want Value) (Node, *loc) {
8282 }8383 switch n := n.(type) {8484 case formula.Ref:85- if same(get(n.Sheet, n.Addr)) {85+ if same(get.cell(n.Sheet, n.Addr)) {8686 return n, at(n.Sheet, n.Addr)8787 }8888 case formula.Range:89- if n.Rect.From == n.Rect.To && same(get(n.Sheet, n.Rect.From)) {89+ if n.Rect.From == n.Rect.To && same(get.cell(n.Sheet, n.Rect.From)) {9090 return n, at(n.Sheet, n.Rect.From)9191 }9292 case formula.Unary:@@ -166,7 +166,7 @@ type agg struct {
166166 func each(args []Node, get lookup, fn func(v Value, direct bool) *Value) *Value {167167 for _, arg := range args {168168 if ref, ok := arg.(formula.Ref); ok {169- if e := fn(get(ref.Sheet, ref.Addr), false); e != nil {169+ if e := fn(get.cell(ref.Sheet, ref.Addr), false); e != nil {170170 return e171171 }172172 continue@@ -175,12 +175,13 @@ func each(args []Node, get lookup, fn func(v Value, direct bool) *Value) *Value
175175 continue176176 }177177 if rn, ok := arg.(formula.Range); ok {178- for r := rn.Rect.From.Row; r <= rn.Rect.To.Row; r++ {179- for c := rn.Rect.From.Col; c <= rn.Rect.To.Col; c++ {180- if e := fn(get(rn.Sheet, Addr{Col: c, Row: r}), false); e != nil {181- return e182- }183- }178+ var e *Value179+ get.cells(rn.Sheet, rn.Rect, func(v Value) bool {180+ e = fn(v, false)181+ return e == nil182+ })183+ if e != nil {184+ return e184185 }185186 continue186187 }@@ -83,7 +83,7 @@ func jevState(n Node, get lookup) (any, error) {
8383 for r := rn.Rect.From.Row; r <= rn.Rect.To.Row; r++ {8484 var row []any8585 for c := rn.Rect.From.Col; c <= rn.Rect.To.Col; c++ {86- v := get(rn.Sheet, Addr{Col: c, Row: r})86+ v := get.cell(rn.Sheet, Addr{Col: c, Row: r})8787 if v.Kind == Error {8888 return nil, inputError{v}8989 }@@ -139,7 +139,7 @@ func jevList(n Node, get lookup) ([]string, error) {
139139 if rn, ok := n.(formula.Range); ok {140140 for r := rn.Rect.From.Row; r <= rn.Rect.To.Row; r++ {141141 for c := rn.Rect.From.Col; c <= rn.Rect.To.Col; c++ {142- v := get(rn.Sheet, Addr{Col: c, Row: r})142+ v := get.cell(rn.Sheet, Addr{Col: c, Row: r})143143 if v.Kind == Error {144144 return nil, inputError{v}145145 }@@ -229,24 +229,56 @@ func (w *Workbook) crossReads(u, l loc) bool {
229229 return false230230 }231231 232-// lookupOn resolves references in a formula on s with get: references233-// without a sheet read s, others the sheet they name (#REF! when no sheet234-// has that name). The last sheet name is remembered, so a range on235-// another sheet resolves its name once.236-func (w *Workbook) lookupOn(s *Sheet, get func(*Sheet, Addr) Value) lookup {237- var lastName string238- var last *Sheet239- return func(sheet string, a Addr) Value {240- if sheet == "" {241- return get(s, a)242- }243- if sheet != lastName || last == nil {244- lastName, last = sheet, w.byKey[formula.SheetKey(sheet)]245- }246- if last == nil {247- return ErrRef232+// reader resolves the references of formulas on s with read: references233+// without a sheet ("") read s, others the sheet they name, with cells on234+// a sheet no sheet has the name of reading as #REF!. The last sheet name235+// is remembered, so a range on another sheet resolves its name once.236+type reader struct {237+ w *Workbook238+ s *Sheet239+ read func(*Sheet, Addr) Value240+ lastName string241+ last *Sheet242+}243+244+func (w *Workbook) lookupOn(s *Sheet, read func(*Sheet, Addr) Value) lookup {245+ return &reader{w: w, s: s, read: read}246+}247+248+// sheet is the sheet a reference written with the name sheet points at.249+func (rd *reader) sheet(sheet string) *Sheet {250+ if sheet == "" {251+ return rd.s252+ }253+ if sheet != rd.lastName || rd.last == nil {254+ rd.lastName, rd.last = sheet, rd.w.byKey[formula.SheetKey(sheet)]255+ }256+ return rd.last257+}258+259+// cell returns the current value of a cell.260+func (rd *reader) cell(sheet string, a Addr) Value {261+ t := rd.sheet(sheet)262+ if t == nil {263+ return ErrRef264+ }265+ return rd.read(t, a)266+}267+268+// cells calls fn with the value of every cell of r, row by row, until fn269+// returns false.270+func (rd *reader) cells(sheet string, r Rect, fn func(Value) bool) {271+ t := rd.sheet(sheet)272+ for row := r.From.Row; row <= r.To.Row; row++ {273+ for col := r.From.Col; col <= r.To.Col; col++ {274+ v := ErrRef275+ if t != nil {276+ v = rd.read(t, Addr{Col: col, Row: row})277+ }278+ if !fn(v) {279+ return280+ }248281 }249- return get(last, a)250282 }251283 }252284