cat / 012
clone

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

main
012 / internal / sheet / steps.go
steps.go 6.6K
1package sheet23import (4 "slices"56 "src.rest/cat/012/internal/formula"7 "src.rest/cat/012/internal/functions"8)910// Steps is a formula evaluated one part at a time, as Excel's Evaluate11// Formula: the part computed next is marked in the formula, computing it12// puts its value in its place, and a reference to another formula can be13// stepped into. The values come from one evaluation by the function14// library with each part recorded where it stands (functions.EvalParts),15// so they are what the formula computes, context and all, and a branch16// IF didn't take is never computed. References are read as the formula17// reads them: the cell's value.18type Steps struct {19 sheet *Sheet20 at Addr21 expr Node // the formula as written, which Text prints22 parts []part // in the order they're computed: operands first, left to right23 next int // the part computed next; len(parts) once done24 top functions.Part25 index map[string]int // parts by pathKey, while references are read26}2728// part is one part of the formula: an operator, a call or a reference.29type part struct {30 path []int31 node Node // as written32 functions.Part33 into loc // a reference to a formula's cell, which can be stepped into34}3536// EvaluateSteps starts evaluating the formula at a step by step, or37// returns nil when a holds no formula.38func (s *Sheet) EvaluateSteps(a Addr) *Steps {39 c, _, _ := s.cells.peek(a)40 if c == nil || !c.IsFormula() {41 return nil42 }43 st := &Steps{sheet: s, at: a, expr: c.expr}44 var paths [][]int45 st.collect(c.expr, nil, func(p []int, n Node) {46 st.parts = append(st.parts, part{path: p, node: n})47 paths = append(paths, p)48 })49 run := s.wb.arith(s.bound(a, c))50 var parts []functions.Part51 st.top, parts = functions.EvalParts(run, paths, s.wb.values(s).lib, a)52 for i := range st.parts {53 st.parts[i].Part = parts[i]54 }55 st.index = make(map[string]int, len(st.parts))56 for i, p := range st.parts {57 st.index[pathKey(p.path)] = i58 }59 st.readRefs()60 st.index = nil61 st.parts = slices.DeleteFunc(st.parts, func(p part) bool { return !p.Reached })62 return st63}6465// collect calls fn with the path of each part of n, operands before66// what they're operands of: operators, calls, LAMBDA calls, and67// references to one cell, written or named.68func (st *Steps) collect(n Node, path []int, fn func([]int, Node)) {69 i := 070 formula.EachChild(n, func(k Node) {71 st.collect(k, append(slices.Clip(path), i), fn)72 i++73 })74 switch n := n.(type) {75 case formula.Unary, formula.Binary, formula.Call, formula.Invoke, formula.Ref:76 fn(path, n)77 case formula.Name:78 if nm, ok := st.sheet.wb.names[nameKey(n.Name)]; ok && !nm.Gone() && nm.Range.From == nm.Range.To {79 fn(path, n)80 }81 case formula.TableRef:82 if _, ok := st.sheet.bindTable(st.at, n, 0).(formula.Ref); ok {83 fn(path, n) // one cell, as Sales[@Amount] is84 }85 }86}8788// readRefs reads the references among the parts, as the formula reads89// them, where the formula reached them.90func (st *Steps) readRefs() {91 for i := range st.parts {92 p := &st.parts[i]93 t, a, ok := st.refCell(p.node)94 if !ok || !st.reached(p.path) {95 continue96 }97 p.Reached = true98 if t == nil {99 p.Value = ErrRef100 continue101 }102 p.Value = t.Value(a)103 if c, _, _ := t.cells.peek(a); c != nil && c.IsFormula() {104 p.into = loc{t, a}105 }106 }107}108109// refCell is the cell a reference, single-cell name or table reference110// points at, and111// its sheet (nil for a sheet no sheet has the name of).112func (st *Steps) refCell(n Node) (*Sheet, Addr, bool) {113 s := st.sheet114 switch n := n.(type) {115 case formula.Ref:116 return s.wb.resolve(s, n.Sheet), n.Addr, true117 case formula.Name:118 nm := s.wb.names[nameKey(n.Name)]119 return nm.Sheet, nm.Range.From, true120 case formula.TableRef:121 if r, ok := s.bindTable(st.at, n, 0).(formula.Ref); ok {122 return s.wb.resolve(s, r.Sheet), r.Addr, true123 }124 }125 return nil, Addr{}, false126}127128// reached reports whether the formula computed what's at path, a129// reference: when the part it's in was computed, and, for an argument of130// IF, IFERROR, IFNA or CHOOSE, when that call took it.131func (st *Steps) reached(path []int) bool {132 for up := len(path) - 1; up >= 0; up-- {133 i, ok := st.index[pathKey(path[:up])]134 if !ok {135 continue // an array literal: go on out136 }137 o := &st.parts[i]138 if !o.Reached {139 return false140 }141 if up == len(path)-1 {142 return st.taken(o, path[up])143 }144 return true145 }146 return true147}148149// taken reports whether the call o, computed, took its argument arg.150func (st *Steps) taken(o *part, arg int) bool {151 call, ok := o.node.(formula.Call)152 if !ok || arg == 0 {153 return true154 }155 first := st.valueAt(append(slices.Clip(o.path), 0), call.Args[0])156 switch call.Fn.Signature().Name {157 case "IF":158 if first.Kind == Error || first.Kind == Text {159 return false160 }161 return (first.Num != 0) == (arg == 1)162 case "IFERROR":163 return first.Kind == Error164 case "IFNA":165 return first == ErrNA166 case "CHOOSE":167 return first.Kind == Number && int(first.Num) == arg168 }169 return true170}171172// valueAt is what the node n at path computed: a part's recorded value,173// or a literal's own.174func (st *Steps) valueAt(path []int, n Node) Value {175 if i, ok := st.index[pathKey(path)]; ok && st.parts[i].Reached {176 return st.parts[i].Value177 }178 if t, a, ok := st.refCell(n); ok && t != nil {179 return t.Value(a)180 }181 return functions.EvalAt(n, st.sheet.wb.values(st.sheet).lib, st.at)182}183184// Cell is the formula's cell.185func (st *Steps) Cell() (*Sheet, Addr) { return st.sheet, st.at }186187// Done reports whether every part is computed.188func (st *Steps) Done() bool { return st.next >= len(st.parts) }189190// Progress is how many parts are computed, of how many.191func (st *Steps) Progress() (done, total int) { return st.next, len(st.parts) }192193// Step computes the next part.194func (st *Steps) Step() {195 if !st.Done() {196 st.next++197 }198}199200// Restart goes back to the formula as written.201func (st *Steps) Restart() { st.next = 0 }202203// Next is the part computed next, as written, and its value as the204// formula would write it; ok is false once done.205func (st *Steps) Next() (expr, value string, ok bool) {206 if st.Done() {207 return "", "", false208 }209 p := st.parts[st.next]210 return formula.Expr(p.node), partText(p.Part), true211}212213// Result is the formula's value, an array it spills written whole.214func (st *Steps) Result() string { return partText(st.top) }215216// CanStepIn reports whether the next part is a reference to a formula's217// cell, which Into steps into.218func (st *Steps) CanStepIn() bool { return !st.Done() && st.parts[st.next].into.s != nil }219220// Into starts stepping through the formula the next part refers to,221// when it is a reference to a formula's cell; nil otherwise.222func (st *Steps) Into() *Steps {223 if !st.CanStepIn() {224 return nil225 }226 l := st.parts[st.next].into227 return l.s.EvaluateSteps(l.a)228}