cat / 012
clone

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

Keep the evaluation depth count off the hot path

Dami committed 7ea2199
Numbers typed in skip the formula path, operators count their own
nesting without an extra call, and an abandoned evaluation finds its
visiting cells by scanning rather than keeping a stack: RecalcAll and
Edit on the chain, volatile and dense shapes are back to their
previous times.
commit
7ea2199c090dbda7f2c6ad485096a67869e94cdb
parent
7140572955a6c0bba351e82b6daf3fe310049d77
author
Dami <dami.etoile@gmail.com>
2 files changed, +72 -53
Minternal/sheet/eval.go22++----
Minternal/sheet/evaluate.go103+++++++++++++++++++-----------
M internal/sheet/eval.go +8 -14
@@ -118,19 +118,6 @@ func eval(n Node, get lookup) Value {
118118 return get.cell(n.Sheet, n.Rect.From)119119 }120120 return ErrValue121- case formula.Unary, decUnary, formula.Binary, decBinary, formula.Call:122- get.w.depth++123- v := evalNested(n, get)124- get.w.depth--125- return v126- }127- return ErrValue128-}129-130-// evalNested computes an operator or a call, which evaluate their131-// operands: eval counts the nesting around it (see evaluate.go).132-func evalNested(n Node, get lookup) Value {133- switch n := n.(type) {134121 case formula.Unary:135122 return evalUnary(n, get, false)136123 case decUnary:
@@ -140,7 +127,10 @@ func evalNested(n Node, get lookup) Value {
140127 case decBinary:141128 return evalBinary(formula.Binary(n), get, true)142129 case formula.Call:143- return funcOf(n).call(n.Args, get)130+ get.w.depth++ // see evaluate.go; operators count in theirs131+ v := funcOf(n).call(n.Args, get)132+ get.w.depth--133+ return v144134 }145135 return ErrValue146136 }
@@ -148,7 +138,9 @@ func evalNested(n Node, get lookup) Value {
148138 // evalUnary computes a prefix operator or the postfix %; with dec, % is149139 // decimal (decimal.go).150140 func evalUnary(n formula.Unary, get lookup, dec bool) Value {141+ get.w.depth++151142 x := eval(n.X, get)143+ get.w.depth--152144 if x.Kind == Error {153145 return x154146 }
@@ -183,7 +175,9 @@ func evalUnary(n formula.Unary, get lookup, dec bool) Value {
183175 // evalBinary computes a binary operator; with dec, arithmetic is decimal184176 // (decimal.go).185177 func evalBinary(n formula.Binary, get lookup, dec bool) Value {178+ get.w.depth++186179 l, r := eval(n.L, get), eval(n.R, get)180+ get.w.depth--187181 if l.Kind == Error {188182 return l189183 }
M internal/sheet/evaluate.go +64 -39
@@ -32,7 +32,6 @@ type tooDeep struct{}
3232 // evaluator is the state of one evaluate.3333 type evaluator struct {3434 w *Workbook35- stack []loc // cells being evaluated, outermost first3635 pending []loc // cells put off, each needed by the one before3736 }3837
@@ -52,10 +51,8 @@ func (w *Workbook) evaluate() {
5251 s.calcFmt = w.formatFrom(s)5352 }5453 for _, s := range w.sheets {55- for a, st := range s.calc {56- if st == dirty {57- e.settle(loc{s, a})58- }54+ for !e.sweep(s) {55+ e.settle()5956 }6057 }6158 for _, s := range w.sheets {
@@ -80,14 +77,11 @@ func (e *evaluator) compute(s *Sheet, a Addr) Value {
8077 case c.derived: // a pivot's result, set when the pivot was computed8178 s.calc[a] = done8279 return c.Value83- case w.depth >= maxEvalDepth:80+ case c.expr != nil && w.depth >= maxEvalDepth:8481 s.calc[a] = deferred8582 e.pending = append(e.pending, loc{s, a})8683 panic(tooDeep{})8784 }88- s.calc[a] = visiting89- e.stack = append(e.stack, loc{s, a})90- w.depth++9185 c.auto = Format{}9286 switch {9387 case c.Input == "":
@@ -97,26 +91,47 @@ func (e *evaluator) compute(s *Sheet, a Addr) Value {
9791 case c.expr == nil:9892 c.Value = Value{Kind: Text, Str: strings.TrimPrefix(c.Input, "'")}9993 default:100- expr := s.bound(c)101- outer := w.evaluating102- w.evaluating = loc{s, a}103- c.Value = eval(w.arith(expr), s.calcGet)104- w.evaluating = outer105- if _, lit := expr.(formula.Num); !lit {106- c.auto = inferFormat(expr, s.calcFmt)94+ if n, lit := c.expr.(formula.Num); lit { // a number typed in95+ c.Value = num(n.V)96+ } else {97+ e.formula(s, a, c)10798 }10899 }109- w.depth--110- e.stack = e.stack[:len(e.stack)-1]111100 s.calc[a] = done112101 s.cells.changed(a)113102 return c.Value114103 }115104 116-// settle evaluates the dirty cell at l, and every cell put off on the117-// way, deepest first.118-func (e *evaluator) settle(l loc) {119- e.pending = append(e.pending[:0], l)105+// formula evaluates the formula in c, at a, which may read other cells:106+// it's visiting meanwhile.107+func (e *evaluator) formula(s *Sheet, a Addr, c *Cell) {108+ w := e.w109+ s.calc[a] = visiting110+ w.depth++111+ expr := s.bound(c)112+ outer := w.evaluating113+ w.evaluating = loc{s, a}114+ c.Value = eval(w.arith(expr), s.calcGet)115+ w.evaluating = outer116+ c.auto = inferFormat(expr, s.calcFmt)117+ w.depth--118+}119+120+// sweep evaluates s's dirty cells, reporting false if it went too deep121+// and put off a cell.122+func (e *evaluator) sweep(s *Sheet) (finished bool) {123+ defer e.abandon(&finished)124+ for a, st := range s.calc {125+ if st == dirty {126+ e.compute(s, a)127+ }128+ }129+ return true130+}131+132+// settle evaluates the cells put off, deepest first, putting off more on133+// the way if need be.134+func (e *evaluator) settle() {120135 for len(e.pending) > 0 {121136 top := e.pending[len(e.pending)-1]122137 if top.s.calc[top.a] == deferred {
@@ -131,23 +146,33 @@ func (e *evaluator) settle(l loc) {
131146 // try evaluates the cell at l, reporting false if it went too deep and132147 // put off a cell, having undone what it had started.133148 func (e *evaluator) try(l loc) (finished bool) {134- defer func() {135- if r := recover(); r != nil {136- if _, deep := r.(tooDeep); !deep {137- panic(r)138- }139- for _, v := range e.stack {140- v.s.calc[v.a] = dirty141- }142- for _, p := range e.pending {143- p.s.calc[p.a] = deferred144- }145- e.stack = e.stack[:0]146- e.w.depth = 0147- e.w.evaluating = loc{}148- finished = false149- }150- }()149+ defer e.abandon(&finished)151150 e.compute(l.s, l.a)152151 return true153152 }153+154+// abandon, deferred, recovers from an evaluation that went too deep:155+// the cells it was evaluating go back to dirty, those put off stay156+// deferred, and finished is set false.157+func (e *evaluator) abandon(finished *bool) {158+ r := recover()159+ if r == nil {160+ return161+ }162+ if _, deep := r.(tooDeep); !deep {163+ panic(r)164+ }165+ for _, s := range e.w.sheets {166+ for a, st := range s.calc {167+ if st == visiting {168+ s.calc[a] = dirty169+ }170+ }171+ }172+ for _, p := range e.pending {173+ p.s.calc[p.a] = deferred174+ }175+ e.w.depth = 0176+ e.w.evaluating = loc{}177+ *finished = false178+}