1package functions23import (4 "src.rest/cat/012/internal/formula"5 "src.rest/cat/012/internal/value"6)78// In an array context (ARRAYFORMULA, or an argument that takes ranges) a9// function of one value given arrays is applied to each of their10// entries, as Sheets does: ARRAYFORMULA(LEN(A1:A3)) is the length of11// each cell, and ARRAYFORMULA(VLOOKUP(A1:A3, D:E, 2, FALSE)) looks each12// one up. Which arguments are mapped over isn't listed anywhere: the13// function is called once with its arguments standing in (liftArg), and14// those it reads as one value are the ones mapped over, while those it15// reads whole (VLOOKUP's table, SUMIF's range) stay whole. So16// SUM(LEN(A1:A3)) adds three lengths, and SUMIF(A:A, D1:D3) inside17// ARRAYFORMULA sums once per criterion.1819// liftFrame is one function call being mapped over arrays: its20// arguments, their values in the array context once read, and the entry21// the call is computing.22type liftFrame struct {23 args []Node24 vals []Value25 got []bool // vals[i] is computed26 read []bool // argument i was read as one value, and is an array27 r, c int // the entry being computed28 fill bool // computing the value past every argument's data29}3031// liftArg stands for argument i of a call being mapped over arrays.32// Read as one value (eval), it is the entry being computed; read whole33// (matrixArg, eachOf, arrayArg), it is the argument itself.34type liftArg struct {35 f *liftFrame36 i int37}3839// value is the argument in the array context: evaluated once.40func (a liftArg) value(get lookup) Value {41 f := a.f42 if !f.got[a.i] {43 f.vals[a.i], f.got[a.i] = evalArr(f.args[a.i], get), true44 }45 return f.vals[a.i]46}4748// one is the argument's entry for the call being computed.49func (a liftArg) one(get lookup) Value {50 v := a.value(get)51 arr := get.arrayOf(v)52 if arr == nil {53 return v54 }55 f := a.f56 f.read[a.i] = true57 if f.fill {58 return arr.Fill59 }60 return arr.bcast(f.r, f.c)61}6263// unlift is the node an argument stands for, when it is a reference or64// range a function reads whole, or n itself.65func unlift(n Node) Node {66 if a, ok := n.(liftArg); ok {67 switch orig := a.f.args[a.i].(type) {68 case formula.Range, formula.Ref:69 return orig70 }71 }72 return n73}7475// liftCall calls f with args in an array context, mapping it over the76// arrays it reads as one value.77func liftCall(f *FuncDef, args []Node, get lookup) Value {78 fr := &liftFrame{args: args, vals: make([]Value, len(args)), got: make([]bool, len(args)), read: make([]bool, len(args))}79 stand := make([]Node, len(args))80 for i, a := range args {81 switch a.(type) {82 case formula.Num, formula.Str, formula.Bool, formula.Empty, formula.Ref:83 stand[i] = a // one value however it's read84 default:85 stand[i] = liftArg{fr, i}86 }87 }88 lift, want := get.lift, get.wantArr89 get.lift, get.wantArr = 0, f.arrays == liftPass90 defer func() { get.lift, get.wantArr = lift, want }()91 whole := f.call(stand, get)92 v := get.first(whole)93 var s shape94 var arrs []*Array95 for i, r := range fr.read {96 if r {97 a := get.arrayOf(fr.vals[i])98 s.fit(a)99 arrs = append(arrs, a)100 }101 }102 if len(arrs) == 0 {103 if f.arrays == liftPass {104 return whole // mapped over nothing: an array it returns stays whole105 }106 return v107 }108 s.data(arrs...)109 out, ok := s.newArray()110 if !ok {111 return value.ErrValue112 }113 for i := range s.drows {114 for j := range s.dcols {115 if i == 0 && j == 0 { // the call above116 out.Set(0, 0, v)117 continue118 }119 fr.r, fr.c = i, j120 out.Set(i, j, get.first(f.call(stand, get)))121 }122 }123 if s.drows < s.rows || s.dcols < s.cols {124 fr.fill = true125 out.Fill = get.first(f.call(stand, get))126 }127 return get.arrayValue(out)128}