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 / scope.go
scope.go 3.7K
1package functions23import (4 "strings"56 "src.rest/cat/012/internal/formula"7 "src.rest/cat/012/internal/value"8)910// Names LET and LAMBDA bind (Local nodes), and calling a LAMBDA: the11// scope a formula is evaluated in. The functions themselves are in12// lambda.go.1314// binding is a name LET or LAMBDA bound: to a reference or range (ref),15// which then reads as the range does wherever it's used, or to a value.16type binding struct {17 name string18 ref Node19 val Value20}2122// lookupName finds the innermost binding of name.23func (rd *Reader) lookupName(name string) (binding, bool) {24 for i := len(rd.scope) - 1; i >= 0; i-- {25 if strings.EqualFold(rd.scope[i].name, name) {26 return rd.scope[i], true27 }28 }29 return binding{}, false30}3132// local is the value of a bound name.33func (rd *Reader) local(name string) Value {34 b, ok := rd.lookupName(name)35 switch {36 case !ok:37 return value.ErrName38 case b.ref != nil:39 return eval(b.ref, rd)40 }41 return b.val42}4344// refOf is the reference or range n stands for when a function reads it45// whole: n itself, a name bound to one, or the argument an entry being46// mapped over stands in for.47func (rd *Reader) refOf(n Node) Node {48 switch l := n.(type) {49 case formula.Local:50 if b, ok := rd.lookupName(l.Name); ok && b.ref != nil {51 return b.ref52 }53 case liftArg:54 return unlift(n)55 }56 return n57}5859// bind evaluates what a name is bound to: a reference or range stays60// one, read as it would be where the name is used, except in an array61// context, which reads a range whole.62func (rd *Reader) bind(name string, n Node) binding {63 if rd.lift == 0 {64 switch r := rd.refOf(n).(type) {65 case formula.Ref, formula.Range:66 return binding{name: name, ref: r}67 }68 }69 want := rd.wantArr70 rd.wantArr = true71 v := eval(n, rd)72 rd.wantArr = want73 return binding{name: name, val: v}74}7576// invoke evaluates a LAMBDA call written in the formula: LAMBDA(...)(...)77// or a name bound to a LAMBDA with arguments.78func (rd *Reader) invoke(n formula.Invoke) Value {79 want := rd.wantArr80 rd.wantArr = true81 fn := eval(n.Fn, rd)82 rd.wantArr = want83 l := rd.lambdaOf(fn)84 switch {85 case fn.Kind == value.Error:86 return fn87 case l == nil:88 return value.ErrValue89 case len(n.Args) != len(l.params):90 return value.ErrNA // Sheets: wrong number of arguments91 }92 vals := make([]binding, len(n.Args))93 for i, a := range n.Args {94 vals[i] = rd.bind(l.params[i], a)95 }96 return rd.apply(l, vals)97}9899// apply evaluates a LAMBDA's expression with its parameters bound, in100// the scope it was written in.101func (rd *Reader) apply(l *lambda, args []binding) Value {102 saved := rd.scope103 rd.scope = append(append([]binding(nil), l.scope...), args...)104 want := rd.wantArr105 rd.wantArr = true106 v := eval(l.body, rd)107 rd.wantArr = want108 rd.scope = saved109 return v110}111112// call calls a LAMBDA with values, for the functions that take one,113// outside any array context: its expression sees the values it's given.114func (rd *Reader) call(l *lambda, vals ...Value) Value {115 args := make([]binding, len(vals))116 for i, v := range vals {117 args[i] = binding{name: l.params[i], val: v}118 }119 lift := rd.lift120 rd.lift = 0121 v := rd.apply(l, args)122 rd.lift = lift123 return v124}125126// lambdaArg reads a LAMBDA argument taking n values: #N/A when it takes127// another number, as Sheets says.128func lambdaArg(n Node, get lookup, params int) (*lambda, *Value) {129 v := wholeArg(n, get)130 if v.Kind == value.Error {131 return nil, errOf(v)132 }133 l := get.lambdaOf(v)134 switch {135 case l == nil:136 return nil, &value.ErrValue137 case len(l.params) != params:138 return nil, &value.ErrNA139 }140 return l, nil141}142143// single is what a LAMBDA called for one entry returns: an array there144// is #VALUE!, as Sheets says a LAMBDA must return one value.145func (rd *Reader) single(v Value) Value {146 if v.Kind == value.Array {147 return value.ErrValue148 }149 return v150}