package compile import ( "bytes" "fmt" "testing" "go.starlark.net/resolve" "go.starlark.net/syntax" ) // TestPlusFolding ensures that the compiler generates optimized code for // n-ary addition of strings, lists, and tuples. func TestPlusFolding(t *testing.T) { isPredeclared := func(name string) bool { return name == "x" } isUniversal := func(name string) bool { return false } for i, test := range []struct { src string // source expression want string // disassembled code }{ { // string folding `"a" + "b" + "c" + "d"`, `constant "abcd"; return`, }, { // string folding with variable: `"a" + "b" + x + "c" + "d"`, `constant "ab"; predeclared x; plus; constant "cd"; plus; return`, }, { // list folding `[1] + [2] + [3]`, `constant 1; constant 2; constant 3; makelist<3>; return`, }, { // list folding with variable `[1] + [2] + x + [3]`, `constant 1; constant 2; makelist<2>; ` + `predeclared x; plus; ` + `constant 3; makelist<1>; plus; ` + `return`, }, { // tuple folding `() + (1,) + (2, 3)`, `constant 1; constant 2; constant 3; maketuple<3>; return`, }, { // tuple folding with variable `() + (1,) + x + (2, 3)`, `constant 1; maketuple<1>; predeclared x; plus; ` + `constant 2; constant 3; maketuple<2>; plus; ` + `return`, }, } { expr, err := syntax.ParseExpr("in.star", test.src, 0) if err != nil { t.Errorf("#%d: %v", i, err) continue } locals, err := resolve.Expr(expr, isPredeclared, isUniversal) if err != nil { t.Errorf("#%d: %v", i, err) continue } got := disassemble(Expr(expr, "", locals)) if test.want != got { t.Errorf("expression <<%s>> generated <<%s>>, want <<%s>>", test.src, got, test.want) } } } // disassemble is a trivial disassembler tailored to the accumulator test. func disassemble(f *Funcode) string { out := new(bytes.Buffer) code := f.Code for pc := 0; pc < len(code); { op := Opcode(code[pc]) pc++ // TODO(adonovan): factor in common with interpreter. var arg uint32 if op >= OpcodeArgMin { for s := uint(0); ; s += 7 { b := code[pc] pc++ arg |= uint32(b&0x7f) << s if b < 0x80 { break } } } if out.Len() > 0 { out.WriteString("; ") } fmt.Fprintf(out, "%s", op) if op >= OpcodeArgMin { switch op { case CONSTANT: switch x := f.Prog.Constants[arg].(type) { case string: fmt.Fprintf(out, " %q", x) default: fmt.Fprintf(out, " %v", x) } case LOCAL: fmt.Fprintf(out, " %s", f.Locals[arg].Name) case PREDECLARED: fmt.Fprintf(out, " %s", f.Prog.Names[arg]) default: fmt.Fprintf(out, "<%d>", arg) } } } return out.String() }