{1,2;3,4} is an Array node of rows of elements. Functions whose
signature binds names (LET, LAMBDA) have them parsed as Local nodes,
which named ranges never replace, and a bound name or a LAMBDA call
followed by arguments is an Invoke. Walkers go through EachChild, so
the new nodes are visited everywhere references and names are found.
Co-Authored-By: Claude <noreply@anthropic.com>- commit
- 81f1a2aa9e01d964dd6b6299ff8ea62c6516c276
- parent
- fb9d110c486f6ce4a9cc78d2cf661559fe39da83
- author
- Dami <dami.etoile@gmail.com>
| A | internal/ | 103 | ++++++++++++++++++++++++++ |
| M | internal/ | 75 | +++++++++++++++---- |
| M | internal/ | 2 | +- |
| M | internal/ | 119 | ++++++++++++++++++++++++++++-- |
| M | internal/ | 35 | ++++++++- |
| M | internal/ | 46 | ++++++++--- |
@@ -0,0 +1,103 @@
1+package formula2+3+import (4+ "strings"5+ "testing"6+)7+8+// bindFuncs adds LET and LAMBDA to the test functions.9+func bindFuncs(name string) (Func, bool) {10+ switch name {11+ case "LET":12+ return testFunc{Name: "LET", Min: 3, Max: -1, Step: 2, Binds: BindLet}, true13+ case "LAMBDA":14+ return testFunc{Name: "LAMBDA", Min: 1, Max: -1, Binds: BindLambda}, true15+ }16+ return testFuncs(name)17+}18+19+func TestParseArrays(t *testing.T) {20+ for _, tt := range []struct{ in, want string }{21+ {"={1,2;3,4}", "={1,2;3,4}"},22+ {"={ 1 , 2 }*2", "={1,2}*2"},23+ {`={"a";"b"}`, `={"a";"b"}`},24+ {"={A1:A3,B1:B3}", "={A1:A3,B1:B3}"},25+ {"=SUM({1;2},3)", "=SUM({1;2},3)"},26+ {"={-1,2+3}", "={-1,2+3}"},27+ } {28+ n, err := Parse(tt.in, testFuncs)29+ if err != nil {30+ t.Errorf("Parse(%q): %v", tt.in, err)31+ continue32+ }33+ if got := Text(n); got != tt.want {34+ t.Errorf("Text(Parse(%q)) = %q, want %q", tt.in, got, tt.want)35+ }36+ }37+ for _, in := range []string{"={}", "={1,2", "={1 2}", "={1,}"} {38+ if _, err := Parse(in, testFuncs); err == nil {39+ t.Errorf("Parse(%q) should fail", in)40+ }41+ }42+}43+44+func TestParseLetAndLambda(t *testing.T) {45+ for _, tt := range []struct{ in, want string }{46+ {"=LET(x, 1, x+1)", "=LET(x,1,x+1)"},47+ {"=LET(Total, A1:A3, SUM(total))", "=LET(Total,A1:A3,SUM(total))"},48+ {"=LAMBDA(a, b, a*b)(2, 3)", "=LAMBDA(a,b,a*b)(2,3)"},49+ {"=LET(f, LAMBDA(v, v*2), f(4))", "=LET(f,LAMBDA(v,v*2),f(4))"},50+ {"=LET(x, 1, x) + x", "=LET(x,1,x)+x"},51+ } {52+ n, err := Parse(tt.in, bindFuncs)53+ if err != nil {54+ t.Errorf("Parse(%q): %v", tt.in, err)55+ continue56+ }57+ if got := Text(n); got != tt.want {58+ t.Errorf("Text(Parse(%q)) = %q, want %q", tt.in, got, tt.want)59+ }60+ }61+ // Names bound by LET are locals; the same name outside is a name.62+ n, _ := Parse("=LET(x, 1, x) + x", bindFuncs)63+ var names []string64+ WalkNames(n, func(nn Name) { names = append(names, nn.Name) })65+ if strings.Join(names, ",") != "x" {66+ t.Errorf("names outside LET = %v, want the one x", names)67+ }68+ call := n.(Binary).L.(Call)69+ if _, ok := call.Args[2].(Local); !ok {70+ t.Errorf("x inside LET = %T, want Local", call.Args[2])71+ }72+ for _, tt := range []struct{ in, want string }{73+ {"=LET(x, 1)", "Wrong number of arguments to LET"},74+ {"=LET(1, 2, 3)", "Argument 1 of LET must be a name"},75+ {"=LET(A1, 2, A1)", "Argument 1 of LET must be a name"},76+ {"=LAMBDA(1, 2)", "Argument 1 of LAMBDA must be a name"},77+ {"=f(1)", "Unknown function F"},78+ } {79+ _, err := Parse(tt.in, bindFuncs)80+ if err == nil || !strings.HasPrefix(err.Error(), tt.want) {81+ t.Errorf("Parse(%q) = %v, want %q", tt.in, err, tt.want)82+ }83+ }84+}85+86+func TestRewriteArrays(t *testing.T) {87+ n, err := Parse("={A1,B1;LET(x,C1,x),1}", bindFuncs)88+ if err != nil {89+ t.Fatal(err)90+ }91+ moved, ok := Rewrite(n, Shift(0, 1))92+ if !ok || Text(moved) != "={A2,B2;LET(x,C2,x),1}" {93+ t.Errorf("shifted = %s (%v)", Text(moved), ok)94+ }95+ if Text(n) != "={A1,B1;LET(x,C1,x),1}" {96+ t.Errorf("the original changed: %s", Text(n))97+ }98+ var refs []string99+ WalkRefs(n, func(_ string, a Addr) { refs = append(refs, a.String()) }, func(string, Rect) {})100+ if strings.Join(refs, ",") != "A1,B1,C1" {101+ t.Errorf("refs = %v", refs)102+ }103+}@@ -42,6 +42,19 @@ type (
4242 Fn Func4343 Args []Node4444 }45+ // Array is an array literal, {1,2;3,4}: rows of elements separated46+ // by ";", elements by ",". An element may itself be a range or an47+ // array, as {A1:A3,B1:B3} joins two columns.48+ Array struct{ Rows [][]Node }49+ // Local is a name LET or LAMBDA binds, used within that call, as in50+ // LET(total, SUM(A:A), total*2). Named ranges never replace it.51+ Local struct{ Name string }52+ // Invoke calls what Fn computes to, a LAMBDA, with Args:53+ // LAMBDA(x, x*2)(3), or f(3) where LET bound f to a LAMBDA.54+ Invoke struct {55+ Fn Node56+ Args []Node57+ }4558 )4659 4760 // Func is a function a formula can call. The parser checks calls against@@ -60,28 +73,63 @@ type Signature struct {
6073 // Step > 0 means arguments after Min come in groups of Step, like6174 // SUMIFS' (range, criterion) pairs.6275 Step int76+ // Binds says which arguments name values for the rest of the call,77+ // as LET's and LAMBDA's do.78+ Binds Binding6379 }6480 81+// Binding is how a function's arguments bind names (Local).82+type Binding uint883+84+const (85+ BindNone Binding = iota86+ // BindLet: LET(name1, value1, [name2, value2, ...], expression),87+ // each name bound for the arguments after it.88+ BindLet89+ // BindLambda: LAMBDA([name, ...], expression), the names bound in90+ // the expression.91+ BindLambda92+)93+6594 // Funcs finds a function by its upper-case name or alias.6695 type Funcs func(name string) (Func, bool)6796 68-// WalkNames calls fn for every name in n.69-func WalkNames(n Node, fn func(Name)) {97+// EachChild calls fn with every expression directly inside n: operands,98+// arguments and array elements.99+func EachChild(n Node, fn func(Node)) {70100 switch n := n.(type) {71- case Name:72- fn(n)73101 case Unary:74- WalkNames(n.X, fn)102+ fn(n.X)75103 case Binary:76- WalkNames(n.L, fn)77- WalkNames(n.R, fn)104+ fn(n.L)105+ fn(n.R)78106 case Call:79107 for _, a := range n.Args {80- WalkNames(a, fn)108+ fn(a)109+ }110+ case Array:111+ for _, row := range n.Rows {112+ for _, e := range row {113+ fn(e)114+ }115+ }116+ case Invoke:117+ fn(n.Fn)118+ for _, a := range n.Args {119+ fn(a)81120 }82121 }83122 }84123 124+// WalkNames calls fn for every name in n.125+func WalkNames(n Node, fn func(Name)) {126+ if nn, ok := n.(Name); ok {127+ fn(nn)128+ return129+ }130+ EachChild(n, func(k Node) { WalkNames(k, fn) })131+}132+85133 // WalkRefs calls fn for every single-cell reference and range in n, with86134 // the sheet it was qualified with ("" for the formula's own sheet).87135 func WalkRefs(n Node, ref func(string, Addr), rng func(string, Rect)) {@@ -90,14 +138,7 @@ func WalkRefs(n Node, ref func(string, Addr), rng func(string, Rect)) {
90138 ref(n.Sheet, n.Addr)91139 case Range:92140 rng(n.Sheet, n.Rect)93- case Unary:94- WalkRefs(n.X, ref, rng)95- case Binary:96- WalkRefs(n.L, ref, rng)97- WalkRefs(n.R, ref, rng)98- case Call:99- for _, a := range n.Args {100- WalkRefs(a, ref, rng)101- }141+ default:142+ EachChild(n, func(k Node) { WalkRefs(k, ref, rng) })102143 }103144 }@@ -39,7 +39,7 @@ func (e *ParseError) Error() string { return e.Msg }
3939 var (4040 twoCharOps = [...]string{"..", "<=", ">=", "<>"}4141 hashOps = [...]string{"#AND#", "#OR#", "#NOT#"}42- oneCharOps = "+-*/^=<>&(),;:%"42+ oneCharOps = "+-*/^=<>&(),;:%{}"4343 refErrorText = "#REF!"4444 )4545 @@ -38,6 +38,9 @@ type parser struct {
3838 pos int3939 funcs Funcs4040 depth int // of expr calls, see MaxDepth41+ // locals are the names LET and LAMBDA calls being parsed have bound,42+ // innermost last, as upper-case keys.43+ locals []string4144 }4245 4346 // Parse parses a formula, finding the functions it calls with funcs. src@@ -173,6 +176,8 @@ func (p *parser) prefixOp(t token) (Node, error) {
173176 }174177 p.next()175178 return n, nil179+ case "{":180+ return p.array(t)176181 case "+", "-", "#NOT#":177182 power := unaryPower178183 if t.text == "#NOT#" {@@ -205,7 +210,11 @@ func (p *parser) ident(t token, sheet string) (Node, error) {
205210 return Bool{false}, nil206211 }207212 // Names match case-insensitively but print as written.208- return Name{p.src[t.pos : t.pos+len(t.text)]}, nil213+ written := p.src[t.pos : t.pos+len(t.text)]214+ if p.bound(t.text) {215+ return Local{written}, nil216+ }217+ return Name{written}, nil209218 }210219 if !p.isOp(":") && !p.isOp("..") {211220 return Ref{a, abs, sheet}, nil@@ -268,8 +277,17 @@ func NewRange(a, b Addr, aAbs, bAbs Abs) Range {
268277 }269278 270279 // call parses a function call, with or without parentheses (@PI), and271-// checks its number of arguments.280+// checks its number of arguments. A name LET or LAMBDA bound, followed281+// by arguments, calls the LAMBDA it stands for.272282 func (p *parser) call(t token) (Node, error) {283+ if p.bound(t.text) && p.isOp("(") {284+ p.next()285+ args, err := p.args(t.text, BindNone)286+ if err != nil {287+ return nil, err288+ }289+ return Invoke{Fn: Local{p.src[t.pos : t.pos+len(t.text)]}, Args: args}, nil290+ }273291 fn, ok := p.funcs(t.text)274292 if !ok {275293 return nil, &ParseError{t.pos, "Unknown function " + t.text}@@ -278,7 +296,9 @@ func (p *parser) call(t token) (Node, error) {
278296 n := Call{Fn: fn}279297 if p.isOp("(") {280298 p.next()281- args, err := p.args(sig.Name)299+ scope := len(p.locals)300+ args, err := p.args(sig.Name, sig.Binds)301+ p.locals = p.locals[:scope]282302 if err != nil {283303 return nil, err284304 }@@ -287,19 +307,35 @@ func (p *parser) call(t token) (Node, error) {
287307 if !sig.accepts(len(n.Args)) {288308 return nil, &ParseError{t.pos, fmt.Sprintf("Wrong number of arguments to %s(%s)", sig.Name, sig.Args)}289309 }310+ if err := checkBinds(sig, n.Args, t.pos); err != nil {311+ return nil, err312+ }313+ if sig.Binds == BindLambda && p.isOp("(") {314+ p.next()315+ args, err := p.args(sig.Name, BindNone)316+ if err != nil {317+ return nil, err318+ }319+ return Invoke{Fn: n, Args: args}, nil320+ }290321 return n, nil291322 }292323 293324 // args parses a call's arguments after its "(", through the ")". An294-// argument may be left out: F(a, , c) or F(a, ).295-func (p *parser) args(name string) ([]Node, error) {325+// argument may be left out: F(a, , c) or F(a, ). With binds, the names326+// the call binds are declared as they come (see declares).327+func (p *parser) args(name string, binds Binding) ([]Node, error) {296328 if p.isOp(")") {297329 p.next()298330 return nil, nil299331 }300332 var args []Node301333 for {302- if p.isOp(",") || p.isOp(";") || p.isOp(")") {334+ if decl, ok := p.declares(binds, len(args)); ok {335+ args = append(args, decl)336+ p.next()337+ p.locals = append(p.locals, strings.ToUpper(decl.Name))338+ } else if p.isOp(",") || p.isOp(";") || p.isOp(")") {303339 args = append(args, Empty{})304340 } else {305341 arg, err := p.expr(0)@@ -318,6 +354,77 @@ func (p *parser) args(name string) ([]Node, error) {
318354 }319355 }320356 357+// declares reports whether argument i of a call binding names with binds358+// is a name it declares: a plain name, not a cell, followed by a comma359+// (the last argument is the expression, never a name).360+func (p *parser) declares(binds Binding, i int) (Local, bool) {361+ if binds == BindNone || binds == BindLet && i%2 == 1 {362+ return Local{}, false363+ }364+ t, sep := p.peek(), p.toks[min(p.pos+1, len(p.toks)-1)]365+ if t.kind != tokIdent || sep.kind != tokOp || sep.text != "," && sep.text != ";" {366+ return Local{}, false367+ }368+ if _, _, isRef := ParseRef(t.text); isRef || t.text == "TRUE" || t.text == "FALSE" {369+ return Local{}, false370+ }371+ return Local{p.src[t.pos : t.pos+len(t.text)]}, true372+}373+374+// bound reports whether a LET or LAMBDA being parsed binds the name key.375+func (p *parser) bound(key string) bool {376+ for _, l := range p.locals {377+ if l == key {378+ return true379+ }380+ }381+ return false382+}383+384+// checkBinds checks that a LET or LAMBDA names what it binds: LET takes385+// pairs of a name and a value before its expression (its signature386+// counts them), LAMBDA names before its expression.387+func checkBinds(sig Signature, args []Node, pos int) error {388+ if sig.Binds == BindNone || len(args) == 0 {389+ return nil390+ }391+ for i, a := range args[:len(args)-1] {392+ if sig.Binds == BindLet && i%2 == 1 {393+ continue394+ }395+ if _, ok := a.(Local); !ok {396+ return &ParseError{pos, fmt.Sprintf("Argument %d of %s must be a name, like x or total", i+1, sig.Name)}397+ }398+ }399+ return nil400+}401+402+// array parses an array literal after its "{": elements separated by ","403+// in a row and rows by ";", through the "}".404+func (p *parser) array(open token) (Node, error) {405+ if p.isOp("}") {406+ return nil, &ParseError{open.pos, "An array needs at least one value"}407+ }408+ rows := [][]Node{nil}409+ for {410+ e, err := p.expr(0)411+ if err != nil {412+ return nil, err413+ }414+ rows[len(rows)-1] = append(rows[len(rows)-1], e)415+ sep := p.next()416+ switch {417+ case sep.kind == tokOp && sep.text == "}":418+ return Array{rows}, nil419+ case sep.kind == tokOp && sep.text == ",":420+ case sep.kind == tokOp && sep.text == ";":421+ rows = append(rows, nil)422+ default:423+ return nil, &ParseError{sep.pos, "Expected , ; or } in an array"}424+ }425+ }426+}427+321428 // accepts reports whether a call may pass n arguments.322429 func (s Signature) accepts(n int) bool {323430 return n >= s.Min && (s.Max < 0 || n <= s.Max) && (s.Step <= 0 || (n-s.Min)%s.Step == 0)@@ -73,15 +73,40 @@ func printNode(b *strings.Builder, n Node) {
7373 b.WriteString(n.Op)7474 printChild(b, n.R, power(n.R) <= p)7575 case Call:76- b.WriteString(n.Fn.Signature().Name + "(")77- for i, a := range n.Args {76+ b.WriteString(n.Fn.Signature().Name)77+ printArgs(b, n.Args)78+ case Local:79+ b.WriteString(n.Name)80+ case Invoke:81+ printNode(b, n.Fn)82+ printArgs(b, n.Args)83+ case Array:84+ b.WriteByte('{')85+ for i, row := range n.Rows {7886 if i > 0 {79- b.WriteByte(',')87+ b.WriteByte(';')88+ }89+ for j, e := range row {90+ if j > 0 {91+ b.WriteByte(',')92+ }93+ printNode(b, e)8094 }81- printNode(b, a)8295 }83- b.WriteByte(')')96+ b.WriteByte('}')97+ }98+}99+100+// printArgs writes a call's arguments in parentheses.101+func printArgs(b *strings.Builder, args []Node) {102+ b.WriteByte('(')103+ for i, a := range args {104+ if i > 0 {105+ b.WriteByte(',')106+ }107+ printNode(b, a)84108 }109+ b.WriteByte(')')85110 }86111 87112 func printUnary(b *strings.Builder, n Unary) {@@ -34,7 +34,28 @@ func Rewrite(n Node, rw Rewriter) (Node, bool) {
3434 }3535 return n, okL || okR3636 case Call:37- return rewriteCall(n, rw)37+ args, ok := rewriteList(n.Args, rw)38+ n.Args = args39+ return n, ok40+ case Invoke:41+ fn, okFn := Rewrite(n.Fn, rw)42+ args, ok := rewriteList(n.Args, rw)43+ n.Fn, n.Args = fn, args44+ return n, ok || okFn45+ case Array:46+ var rows [][]Node47+ for i, row := range n.Rows {48+ if r, ok := rewriteList(row, rw); ok {49+ if rows == nil {50+ rows = append([][]Node(nil), n.Rows...)51+ }52+ rows[i] = r53+ }54+ }55+ if rows == nil {56+ return n, false57+ }58+ return Array{rows}, true3859 }3960 return n, false4061 }@@ -58,23 +79,22 @@ func (rw Rewriter) leaf(n Node) Node {
5879 return n5980 }6081 61-// rewriteCall rewrites a call's arguments, copying them only if one62-// changed.63-func rewriteCall(n Call, rw Rewriter) (Node, bool) {64- var args []Node65- for i, a := range n.Args {82+// rewriteList rewrites a list of arguments or elements, copying it only83+// if one changed.84+func rewriteList(list []Node, rw Rewriter) ([]Node, bool) {85+ var out []Node86+ for i, a := range list {6687 if b, ok := Rewrite(a, rw); ok {67- if args == nil {68- args = append([]Node(nil), n.Args...)88+ if out == nil {89+ out = append([]Node(nil), list...)6990 }70- args[i] = b91+ out[i] = b7192 }7293 }73- if args == nil {74- return n, false94+ if out == nil {95+ return list, false7596 }76- n.Args = args77- return n, true97+ return out, true7898 }7999 80100 // Shift is the rewrite for copying a formula by (dc, dr): relative parts