cat / 012
clone

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

cat/module-path
012 / internal / formula / rewrite.go
rewrite.go 5.6K
1package formula23// Reference rewriting: copying a formula shifts its relative references,4// and moving or inserting and deleting cells updates every reference to5// the cells that moved, as Sheets does. A reference that can't be kept6// becomes #REF!.78// Rewriter maps the references in a formula. Any function may return9// RefErr; a nil function leaves those nodes alone.10type Rewriter struct {11 Ref func(Ref) Node12 Range func(Range) Node13 Name func(Name) Node14 Local func(Local) Node15 Table func(TableRef) Node16}1718// Rewrite returns n with its references mapped, and whether anything19// changed. Unchanged subtrees are shared.20func Rewrite(n Node, rw Rewriter) (Node, bool) {21 switch n := n.(type) {22 case Ref, Range, Name, Local, TableRef:23 out := rw.leaf(n)24 return out, out != n25 case Unary:26 x, ok := Rewrite(n.X, rw)27 if ok {28 n.X = x29 }30 return n, ok31 case Binary:32 l, okL := Rewrite(n.L, rw)33 r, okR := Rewrite(n.R, rw)34 if okL || okR {35 n.L, n.R = l, r36 }37 return n, okL || okR38 case Call:39 args, ok := rewriteList(n.Args, rw)40 n.Args = args41 return n, ok42 case Invoke:43 fn, okFn := Rewrite(n.Fn, rw)44 args, ok := rewriteList(n.Args, rw)45 n.Fn, n.Args = fn, args46 return n, ok || okFn47 case Array:48 var rows [][]Node49 for i, row := range n.Rows {50 if r, ok := rewriteList(row, rw); ok {51 if rows == nil {52 rows = append([][]Node(nil), n.Rows...)53 }54 rows[i] = r55 }56 }57 if rows == nil {58 return n, false59 }60 return Array{rows}, true61 }62 return n, false63}6465// leaf maps a reference, range, name or bound name.66func (rw Rewriter) leaf(n Node) Node {67 switch n := n.(type) {68 case Ref:69 if rw.Ref != nil {70 return rw.Ref(n)71 }72 case Range:73 if rw.Range != nil {74 return rw.Range(n)75 }76 case Name:77 if rw.Name != nil {78 return rw.Name(n)79 }80 case Local:81 if rw.Local != nil {82 return rw.Local(n)83 }84 case TableRef:85 if rw.Table != nil {86 return rw.Table(n)87 }88 }89 return n90}9192// rewriteList rewrites a list of arguments or elements, copying it only93// if one changed.94func rewriteList(list []Node, rw Rewriter) ([]Node, bool) {95 var out []Node96 for i, a := range list {97 if b, ok := Rewrite(a, rw); ok {98 if out == nil {99 out = append([]Node(nil), list...)100 }101 out[i] = b102 }103 }104 if out == nil {105 return list, false106 }107 return out, true108}109110// Shift is the rewrite for copying a formula by (dc, dr): relative parts111// move, $absolute parts stay, and references pushed off the sheet become112// #REF!.113func Shift(dc, dr int) Rewriter {114 move := func(a Addr, abs Abs) (Addr, bool) {115 if abs&AbsCol == 0 {116 a.Col += dc117 }118 if abs&AbsRow == 0 {119 a.Row += dr120 }121 return a, a.Valid()122 }123 return Rewriter{124 Ref: func(n Ref) Node {125 a, ok := move(n.Addr, n.Abs)126 if !ok {127 return RefErr{}128 }129 return Ref{a, n.Abs, n.Sheet}130 },131 Range: func(n Range) Node {132 // Whole columns and rows stay whole, as A:A does in Sheets.133 a0, a1 := n.Abs[0], n.Abs[1]134 if n.Rect.AllRows() {135 a0, a1 = a0|AbsRow, a1|AbsRow136 }137 if n.Rect.AllCols() {138 a0, a1 = a0|AbsCol, a1|AbsCol139 }140 from, ok1 := move(n.Rect.From, a0)141 to, ok2 := move(n.Rect.To, a1)142 if !ok1 || !ok2 {143 return RefErr{}144 }145 r := NewRange(from, to, n.Abs[0], n.Abs[1])146 r.Sheet = n.Sheet147 return r148 },149 }150}151152// Relocate is the rewrite for cells that move on one sheet: cell maps153// where each cell went (false if it's gone), and rng maps whole ranges.154// on reports whether a reference written with a sheet name ("" for none)155// points at the sheet whose cells moved; other references stay.156func Relocate(on func(sheet string) bool, cell func(Addr) (Addr, bool), rng func(Rect) (Rect, bool)) Rewriter {157 return Rewriter{158 Ref: func(n Ref) Node {159 if !on(n.Sheet) {160 return n161 }162 a, ok := cell(n.Addr)163 if !ok {164 return RefErr{}165 }166 return Ref{a, n.Abs, n.Sheet}167 },168 Range: func(n Range) Node {169 if !on(n.Sheet) {170 return n171 }172 r, ok := rng(n.Rect)173 if !ok {174 return RefErr{}175 }176 return Range{r, n.Abs, n.Sheet}177 },178 }179}180181// Span inserts (N > 0) or deletes (N < 0) lines starting at index At,182// along an axis with Size lines.183type Span struct{ At, N, Size int }184185// Point maps a line index; false if the line was deleted or pushed off186// the end.187func (sp Span) Point(v int) (int, bool) {188 switch {189 case v < sp.At:190 return v, true191 case sp.N < 0 && v < sp.At-sp.N:192 return 0, false193 }194 v += sp.N195 return v, v < sp.Size196}197198// Interval maps a range's lines lo..hi as Sheets does: inserting inside199// the range grows it, deleting part of it shrinks it, and deleting all of200// it leaves #REF!.201func (sp Span) Interval(lo, hi int) (int, int, bool) {202 if lo == 0 && hi == sp.Size-1 {203 return lo, hi, true // whole columns or rows stay whole204 }205 if sp.N > 0 {206 lo, okLo := sp.Point(lo)207 if !okLo {208 return 0, 0, false209 }210 hi, _ := sp.Point(hi)211 return lo, min(hi, sp.Size-1), true212 }213 end := sp.At - sp.N // first line after the deletion214 if lo >= sp.At && hi < end {215 return 0, 0, false216 }217 if lo >= sp.At {218 lo = max(lo, end) + sp.N219 }220 if hi >= sp.At {221 hi = max(hi+sp.N, sp.At-1)222 }223 return lo, hi, true224}225226// AxisMaps are the cell and range mappings for inserting or deleting227// rows (rows true) or columns, for Relocate.228func AxisMaps(rows bool, sp Span) (func(Addr) (Addr, bool), func(Rect) (Rect, bool)) {229 cell := func(a Addr) (Addr, bool) {230 var ok bool231 if rows {232 a.Row, ok = sp.Point(a.Row)233 } else {234 a.Col, ok = sp.Point(a.Col)235 }236 return a, ok237 }238 rng := func(r Rect) (Rect, bool) {239 var ok bool240 if rows {241 r.From.Row, r.To.Row, ok = sp.Interval(r.From.Row, r.To.Row)242 } else {243 r.From.Col, r.To.Col, ok = sp.Interval(r.From.Col, r.To.Col)244 }245 return r, ok246 }247 return cell, rng248}