cat / 012
clone

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

main
012 / internal / functions / probe.go
probe.go 4.6K
1package functions23import (4 "slices"56 "src.rest/cat/012/internal/formula"7)89// Parts of a formula evaluated where they stand, for stepping through it10// as Excel's Evaluate Formula does (the engine's steps.go). Rather than a11// second evaluator, the formula is evaluated once as usual with each12// part wrapped in a probe, which records what the part computed there:13// in its array context or not, inside LET with its names bound, and not14// at all in an IF's branch not taken. Only operators, calls and LAMBDA15// calls are wrapped: functions look at their arguments' nodes only to16// tell references, ranges, names and literals apart, which stay as they17// are, so a probe changes nothing a function sees.1819// Part is what one part of a formula computed the first time it was20// computed.21type Part struct {22 Value Value23 // Array holds the first rows and columns of an array the part24 // computed, with Rows and Cols its whole size; nil otherwise.25 Array *Array26 // Lambda is set when the part computed a LAMBDA.27 Lambda bool28 // Reached is false for a part the formula never computed.29 Reached bool30}3132// previewSize is how many rows and columns of an array a Part keeps.33const previewSize = 83435// probe is a part of a formula being recorded (see Part).36type probe struct {37 x Node38 i int39 parts []Part40}4142// EvalParts evaluates n in the cell at, as EvalCell does, and returns43// what it computed, with the array it spills, and what each part at44// paths computed. A path is child45// indexes from the root, in formula.EachChild's order; the empty path is46// the whole formula. A path to a reference, name or literal records47// nothing: those are read where they stand.48func EvalParts(n Node, paths [][]int, get *Reader, at Addr) (Part, []Part) {49 parts := make([]Part, len(paths))50 for i, p := range paths {51 n = wrapAt(n, p, probe{i: i, parts: parts})52 }53 top := Part{Value: EvalCell(n, get, at), Reached: true}54 if sp := get.Spilled(); sp != nil {55 top.Array = preview(sp)56 }57 return top, parts58}5960// probed reports whether a part of this kind is wrapped in a probe.61func probed(n Node) bool {62 switch n.(type) {63 case formula.Unary, formula.Binary, formula.Call, formula.Invoke, decUnary, decBinary:64 return true65 }66 return false67}6869// ReplaceAt returns a copy of n with the node at path, child indexes in70// formula.EachChild's order, replaced by what f makes of it. The nodes71// on the way are copied; n itself is left as it is.72func ReplaceAt(n Node, path []int, f func(Node) Node) Node {73 if len(path) == 0 {74 return f(n)75 }76 return withChild(n, path[0], func(k Node) Node { return ReplaceAt(k, path[1:], f) })77}7879// wrapAt returns n with the node at path wrapped in p, when it is one80// probed.81func wrapAt(n Node, path []int, p probe) Node {82 if len(path) == 0 {83 if !probed(n) {84 return n85 }86 p.x = n87 return p88 }89 return withChild(n, path[0], func(k Node) Node { return wrapAt(k, path[1:], p) })90}9192// withChild returns a copy of n with its child i replaced by f's result,93// looking through probes already in place.94func withChild(n Node, i int, f func(Node) Node) Node {95 switch n := n.(type) {96 case probe:97 n.x = withChild(n.x, i, f)98 return n99 case formula.Unary:100 n.X = f(n.X)101 return n102 case decUnary:103 n.X = f(n.X)104 return n105 case formula.Binary:106 n.L, n.R = pick(i, n.L, n.R, f)107 return n108 case decBinary:109 n.L, n.R = pick(i, n.L, n.R, f)110 return n111 case formula.Call:112 n.Args = slices.Clone(n.Args)113 n.Args[i] = f(n.Args[i])114 return n115 case formula.Invoke:116 if i == 0 {117 n.Fn = f(n.Fn)118 return n119 }120 n.Args = slices.Clone(n.Args)121 n.Args[i-1] = f(n.Args[i-1])122 return n123 case formula.Array:124 rows := slices.Clone(n.Rows)125 for r, row := range rows {126 if i < len(row) {127 rows[r] = slices.Clone(row)128 rows[r][i] = f(row[i])129 break130 }131 i -= len(row)132 }133 n.Rows = rows134 return n135 }136 return n137}138139// pick applies f to l or r, by i.140func pick(i int, l, r Node, f func(Node) Node) (Node, Node) {141 if i == 0 {142 return f(l), r143 }144 return l, f(r)145}146147// eval computes the part and records it, the first time.148func (p probe) eval(get lookup) Value {149 v := eval(p.x, get)150 if pt := &p.parts[p.i]; !pt.Reached {151 pt.Value, pt.Reached = v, true152 if arr := get.arrayOf(v); arr != nil {153 pt.Array = preview(arr)154 }155 pt.Lambda = get.lambdaOf(v) != nil156 }157 return v158}159160// preview copies the first rows and columns of a.161func preview(a *Array) *Array {162 rows, cols := min(a.Rows, previewSize), min(a.Cols, previewSize)163 out := &Array{Rows: a.Rows, Cols: a.Cols, DRows: rows, DCols: cols, V: make([]Value, rows*cols), Fill: a.Fill}164 for r := range rows {165 for c := range cols {166 out.V[r*cols+c] = a.At(r, c)167 }168 }169 return out170}