Repository: goplus/gop
Pull request #475 (2020-07-18 16:53:03)

Auto Property
https://github.com/goplus/gop/issues/474


codecov-commenter: # [Codecov](https://codecov.io/gh/goplus/gop/pull/475?src=pr&el=h1) Report
> Merging [#475](https://codecov.io/gh/goplus/gop/pull/475?src=pr&el=desc) into [master](https://codecov.io/gh/goplus/gop/commit/a5ed3436b91bbcfe6eb90dd0678178209d8af0b0&el=desc) will **decrease** coverage by `0.00%`.
> The diff coverage is `66.66%`.

[![Impacted file tree graph](https://codecov.io/gh/goplus/gop/pull/475/graphs/tree.svg?width=650&height=150&src=pr&token=4qln9Wk3Pn)](https://codecov.io/gh/goplus/gop/pull/475?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #475      +/-   ##
==========================================
- Coverage   88.81%   88.80%   -0.01%     
==========================================
  Files          37       37              
  Lines        9528     9533       +5     
==========================================
+ Hits         8462     8466       +4     
- Misses        786      787       +1     
  Partials      280      280              
```


| [Impacted Files](https://codecov.io/gh/goplus/gop/pull/475?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [exec/bytecode/const.go](https://codecov.io/gh/goplus/gop/pull/475/diff?src=pr&el=tree#diff-ZXhlYy9ieXRlY29kZS9jb25zdC5nbw==) | `90.10% <62.50%> (-0.59%)` | :arrow_down: |
| [cl/value.go](https://codecov.io/gh/goplus/gop/pull/475/diff?src=pr&el=tree#diff-Y2wvdmFsdWUuZ28=) | `82.62% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goplus/gop/pull/475?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goplus/gop/pull/475?src=pr&el=footer). Last update [a5ed343...799bc6c](https://codecov.io/gh/goplus/gop/pull/475?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/cl/ctx_check.go b/cl/ctx_check.go
index fde0c976..319460d1 100644
--- a/cl/ctx_check.go
+++ b/cl/ctx_check.go
@@ -29,6 +29,7 @@ import (
 
 // Debug only
 func Debug(pkg *Package) {
+	isLower("")
 	if _, v, ok := pkg.Find("main"); ok {
 		fn := v.(*funcDecl)
 		isNoExecCtx(fn.ctx, fn.body)
diff --git a/cl/expr.go b/cl/expr.go
index d47218d0..459690e1 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -25,6 +25,7 @@ import (
 	"github.com/goplus/gop/ast/astutil"
 	"github.com/goplus/gop/exec.spec"
 	"github.com/goplus/gop/token"
+	"github.com/qiniu/x/ctype"
 	"github.com/qiniu/x/errors"
 	"github.com/qiniu/x/log"
 )
@@ -985,6 +986,14 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr) func() {
 		if sf, ok := t.FieldByName(name); ok {
 			log.Panicln("compileSelectorExpr todo: structField -", t, sf)
 		}
+		if _, ok := vx.t.MethodByName(name); !ok && isLower(name) {
+			name = strings.Title(name)
+			if _, ok = vx.t.MethodByName(name); !ok {
+				log.Panicln("compileSelectorExpr: symbol not found -", v.Sel.Name)
+			}
+			v.Sel.Name = name
+			return compileCallExpr(ctx, &ast.CallExpr{Fun: v})
+		}
 		pkgPath, method := normalizeMethod(n, t, name)
 		pkg := ctx.FindGoPackage(pkgPath)
 		if pkg == nil {
@@ -1005,6 +1014,13 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr) func() {
 	return nil
 }
 
+func isLower(name string) bool {
+	for _, c := range name {
+		return ctype.Is(ctype.LOWER, c)
+	}
+	return false
+}
+
 func countPtr(t reflect.Type) (int, reflect.Type) {
 	n := 0
 	for t.Kind() == reflect.Ptr {
diff --git a/cl/value.go b/cl/value.go
index 47331395..d91fa272 100644
--- a/cl/value.go
+++ b/cl/value.go
@@ -374,7 +374,7 @@ func boundConst(v interface{}, t reflect.Type) interface{} {
 	kind := kindOf(t)
 	if v == nil {
 		if kind >= reflect.Chan && kind <= reflect.Slice {
-			return nil
+			return reflect.Zero(t).Interface()
 		}
 		log.Panicln("boundConst: can't convert nil into", t)
 	}
diff --git a/exec/bytecode/const.go b/exec/bytecode/const.go
index 81dbf9f6..bd41d11e 100644
--- a/exec/bytecode/const.go
+++ b/exec/bytecode/const.go
@@ -125,10 +125,10 @@ func (p *Builder) pushInstr(val interface{}) (i Instr) {
 			return iPushTrue
 		}
 		return iPushFalse
-	} else if kind != reflect.String && !(kind >= reflect.Float32 && kind <= reflect.Complex128) {
-		if !(kind == reflect.Ptr && v.Type().Elem().PkgPath() == "math/big") {
-			log.Panicln("Push failed: unsupported type:", reflect.TypeOf(val), "-", val)
-		}
+	} else if kind == reflect.String || (kind >= reflect.Float32 && kind <= reflect.Complex128) {
+		// noop
+	} else if !isNilOrRatConst(kind, v) {
+		log.Panicln("Push failed: unsupported type:", reflect.TypeOf(val), "-", val)
 	}
 	code := p.code
 	i = (opPushConstR << bitsOpShift) | uint32(len(code.valConsts))
@@ -136,6 +136,15 @@ func (p *Builder) pushInstr(val interface{}) (i Instr) {
 	return
 }
 
+func isNilOrRatConst(kind reflect.Kind, v reflect.Value) bool {
+	if (kind >= reflect.Chan && kind <= reflect.Slice) || kind == reflect.UnsafePointer {
+		if v.IsNil() || (kind == reflect.Ptr && v.Type().Elem().PkgPath() == "math/big") {
+			return true
+		}
+	}
+	return false
+}
+
 // Push instr
 func (p *Builder) Push(val interface{}) *Builder {
 	p.code.data = append(p.code.data, p.pushInstr(val))
diff --git a/go.mod b/go.mod
index 14193af7..89508ff2 100644
--- a/go.mod
+++ b/go.mod
@@ -3,5 +3,5 @@ module github.com/goplus/gop
 go 1.14
 
 require (
-	github.com/qiniu/x v1.11.0
+	github.com/qiniu/x v1.11.3
 )
diff --git a/go.sum b/go.sum
index 839adb6f..49bff1cb 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,2 @@
-github.com/qiniu/x v1.11.0 h1:9/YI9gES2c+k4UsNrp4cSBvbBEHnuLjw0ZaCIOGdlLE=
-github.com/qiniu/x v1.11.0/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
+github.com/qiniu/x v1.11.3 h1:PVnPiMIPMXw7oDonbSqE1QGLEV7kvsvHtHswg7/bfTw=
+github.com/qiniu/x v1.11.3/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
diff --git a/lib/bytes/gomod_export.go b/lib/bytes/gomod_export.go
new file mode 100644
index 00000000..66ceeb33
--- /dev/null
+++ b/lib/bytes/gomod_export.go
@@ -0,0 +1,613 @@
+// Package bytes provide Go+ "bytes" package, as "bytes" package in Go.
+package bytes
+
+import (
+	bytes "bytes"
+	io "io"
+	unicode "unicode"
+
+	gop "github.com/goplus/gop"
+	qspec "github.com/goplus/gop/exec.spec"
+)
+
+func execmBufferBytes(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Buffer).Bytes()
+	p.Ret(1, ret0)
+}
+
+func execmBufferString(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Buffer).String()
+	p.Ret(1, ret0)
+}
+
+func execmBufferLen(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Buffer).Len()
+	p.Ret(1, ret0)
+}
+
+func execmBufferCap(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Buffer).Cap()
+	p.Ret(1, ret0)
+}
+
+func execmBufferTruncate(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	args[0].(*bytes.Buffer).Truncate(args[1].(int))
+	p.PopN(2)
+}
+
+func execmBufferReset(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	args[0].(*bytes.Buffer).Reset()
+	p.PopN(1)
+}
+
+func execmBufferGrow(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	args[0].(*bytes.Buffer).Grow(args[1].(int))
+	p.PopN(2)
+}
+
+func execmBufferWrite(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).Write(args[1].([]byte))
+	p.Ret(2, ret0, ret1)
+}
+
+func execmBufferWriteString(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).WriteString(args[1].(string))
+	p.Ret(2, ret0, ret1)
+}
+
+func toType0(v interface{}) io.Reader {
+	if v == nil {
+		return nil
+	}
+	return v.(io.Reader)
+}
+
+func execmBufferReadFrom(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).ReadFrom(toType0(args[1]))
+	p.Ret(2, ret0, ret1)
+}
+
+func toType1(v interface{}) io.Writer {
+	if v == nil {
+		return nil
+	}
+	return v.(io.Writer)
+}
+
+func execmBufferWriteTo(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).WriteTo(toType1(args[1]))
+	p.Ret(2, ret0, ret1)
+}
+
+func execmBufferWriteByte(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := args[0].(*bytes.Buffer).WriteByte(args[1].(byte))
+	p.Ret(2, ret0)
+}
+
+func execmBufferWriteRune(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).WriteRune(args[1].(rune))
+	p.Ret(2, ret0, ret1)
+}
+
+func execmBufferRead(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).Read(args[1].([]byte))
+	p.Ret(2, ret0, ret1)
+}
+
+func execmBufferNext(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := args[0].(*bytes.Buffer).Next(args[1].(int))
+	p.Ret(2, ret0)
+}
+
+func execmBufferReadByte(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0, ret1 := args[0].(*bytes.Buffer).ReadByte()
+	p.Ret(1, ret0, ret1)
+}
+
+func execmBufferReadRune(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0, ret1, ret2 := args[0].(*bytes.Buffer).ReadRune()
+	p.Ret(1, ret0, ret1, ret2)
+}
+
+func execmBufferUnreadRune(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Buffer).UnreadRune()
+	p.Ret(1, ret0)
+}
+
+func execmBufferUnreadByte(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Buffer).UnreadByte()
+	p.Ret(1, ret0)
+}
+
+func execmBufferReadBytes(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).ReadBytes(args[1].(byte))
+	p.Ret(2, ret0, ret1)
+}
+
+func execmBufferReadString(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Buffer).ReadString(args[1].(byte))
+	p.Ret(2, ret0, ret1)
+}
+
+func execCompare(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Compare(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execContains(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Contains(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execContainsAny(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.ContainsAny(args[0].([]byte), args[1].(string))
+	p.Ret(2, ret0)
+}
+
+func execContainsRune(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.ContainsRune(args[0].([]byte), args[1].(rune))
+	p.Ret(2, ret0)
+}
+
+func execCount(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Count(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execEqual(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Equal(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execEqualFold(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.EqualFold(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execFields(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.Fields(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execFieldsFunc(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.FieldsFunc(args[0].([]byte), args[1].(func(rune) bool))
+	p.Ret(2, ret0)
+}
+
+func execHasPrefix(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.HasPrefix(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execHasSuffix(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.HasSuffix(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execIndex(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Index(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execIndexAny(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.IndexAny(args[0].([]byte), args[1].(string))
+	p.Ret(2, ret0)
+}
+
+func execIndexByte(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.IndexByte(args[0].([]byte), args[1].(byte))
+	p.Ret(2, ret0)
+}
+
+func execIndexFunc(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.IndexFunc(args[0].([]byte), args[1].(func(r rune) bool))
+	p.Ret(2, ret0)
+}
+
+func execIndexRune(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.IndexRune(args[0].([]byte), args[1].(rune))
+	p.Ret(2, ret0)
+}
+
+func execJoin(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Join(args[0].([][]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execLastIndex(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.LastIndex(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execLastIndexAny(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.LastIndexAny(args[0].([]byte), args[1].(string))
+	p.Ret(2, ret0)
+}
+
+func execLastIndexByte(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.LastIndexByte(args[0].([]byte), args[1].(byte))
+	p.Ret(2, ret0)
+}
+
+func execLastIndexFunc(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.LastIndexFunc(args[0].([]byte), args[1].(func(r rune) bool))
+	p.Ret(2, ret0)
+}
+
+func execMap(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Map(args[0].(func(r rune) rune), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execNewBuffer(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.NewBuffer(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execNewBufferString(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.NewBufferString(args[0].(string))
+	p.Ret(1, ret0)
+}
+
+func execNewReader(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.NewReader(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execmReaderLen(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Reader).Len()
+	p.Ret(1, ret0)
+}
+
+func execmReaderSize(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Reader).Size()
+	p.Ret(1, ret0)
+}
+
+func execmReaderRead(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Reader).Read(args[1].([]byte))
+	p.Ret(2, ret0, ret1)
+}
+
+func execmReaderReadAt(_ int, p *gop.Context) {
+	args := p.GetArgs(3)
+	ret0, ret1 := args[0].(*bytes.Reader).ReadAt(args[1].([]byte), args[2].(int64))
+	p.Ret(3, ret0, ret1)
+}
+
+func execmReaderReadByte(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0, ret1 := args[0].(*bytes.Reader).ReadByte()
+	p.Ret(1, ret0, ret1)
+}
+
+func execmReaderUnreadByte(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Reader).UnreadByte()
+	p.Ret(1, ret0)
+}
+
+func execmReaderReadRune(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0, ret1, ret2 := args[0].(*bytes.Reader).ReadRune()
+	p.Ret(1, ret0, ret1, ret2)
+}
+
+func execmReaderUnreadRune(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := args[0].(*bytes.Reader).UnreadRune()
+	p.Ret(1, ret0)
+}
+
+func execmReaderSeek(_ int, p *gop.Context) {
+	args := p.GetArgs(3)
+	ret0, ret1 := args[0].(*bytes.Reader).Seek(args[1].(int64), args[2].(int))
+	p.Ret(3, ret0, ret1)
+}
+
+func execmReaderWriteTo(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0, ret1 := args[0].(*bytes.Reader).WriteTo(toType1(args[1]))
+	p.Ret(2, ret0, ret1)
+}
+
+func execmReaderReset(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	args[0].(*bytes.Reader).Reset(args[1].([]byte))
+	p.PopN(2)
+}
+
+func execRepeat(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Repeat(args[0].([]byte), args[1].(int))
+	p.Ret(2, ret0)
+}
+
+func execReplace(_ int, p *gop.Context) {
+	args := p.GetArgs(4)
+	ret0 := bytes.Replace(args[0].([]byte), args[1].([]byte), args[2].([]byte), args[3].(int))
+	p.Ret(4, ret0)
+}
+
+func execReplaceAll(_ int, p *gop.Context) {
+	args := p.GetArgs(3)
+	ret0 := bytes.ReplaceAll(args[0].([]byte), args[1].([]byte), args[2].([]byte))
+	p.Ret(3, ret0)
+}
+
+func execRunes(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.Runes(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execSplit(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Split(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execSplitAfter(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.SplitAfter(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execSplitAfterN(_ int, p *gop.Context) {
+	args := p.GetArgs(3)
+	ret0 := bytes.SplitAfterN(args[0].([]byte), args[1].([]byte), args[2].(int))
+	p.Ret(3, ret0)
+}
+
+func execSplitN(_ int, p *gop.Context) {
+	args := p.GetArgs(3)
+	ret0 := bytes.SplitN(args[0].([]byte), args[1].([]byte), args[2].(int))
+	p.Ret(3, ret0)
+}
+
+func execTitle(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.Title(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execToLower(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.ToLower(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execToLowerSpecial(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.ToLowerSpecial(args[0].(unicode.SpecialCase), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execToTitle(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.ToTitle(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execToTitleSpecial(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.ToTitleSpecial(args[0].(unicode.SpecialCase), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execToUpper(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.ToUpper(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execToUpperSpecial(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.ToUpperSpecial(args[0].(unicode.SpecialCase), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execToValidUTF8(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.ToValidUTF8(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execTrim(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.Trim(args[0].([]byte), args[1].(string))
+	p.Ret(2, ret0)
+}
+
+func execTrimFunc(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.TrimFunc(args[0].([]byte), args[1].(func(r rune) bool))
+	p.Ret(2, ret0)
+}
+
+func execTrimLeft(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.TrimLeft(args[0].([]byte), args[1].(string))
+	p.Ret(2, ret0)
+}
+
+func execTrimLeftFunc(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.TrimLeftFunc(args[0].([]byte), args[1].(func(r rune) bool))
+	p.Ret(2, ret0)
+}
+
+func execTrimPrefix(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.TrimPrefix(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+func execTrimRight(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.TrimRight(args[0].([]byte), args[1].(string))
+	p.Ret(2, ret0)
+}
+
+func execTrimRightFunc(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.TrimRightFunc(args[0].([]byte), args[1].(func(r rune) bool))
+	p.Ret(2, ret0)
+}
+
+func execTrimSpace(_ int, p *gop.Context) {
+	args := p.GetArgs(1)
+	ret0 := bytes.TrimSpace(args[0].([]byte))
+	p.Ret(1, ret0)
+}
+
+func execTrimSuffix(_ int, p *gop.Context) {
+	args := p.GetArgs(2)
+	ret0 := bytes.TrimSuffix(args[0].([]byte), args[1].([]byte))
+	p.Ret(2, ret0)
+}
+
+// I is a Go package instance.
+var I = gop.NewGoPackage("bytes")
+
+func init() {
+	I.RegisterFuncs(
+		I.Func("(*Buffer).Bytes", (*bytes.Buffer).Bytes, execmBufferBytes),
+		I.Func("(*Buffer).String", (*bytes.Buffer).String, execmBufferString),
+		I.Func("(*Buffer).Len", (*bytes.Buffer).Len, execmBufferLen),
+		I.Func("(*Buffer).Cap", (*bytes.Buffer).Cap, execmBufferCap),
+		I.Func("(*Buffer).Truncate", (*bytes.Buffer).Truncate, execmBufferTruncate),
+		I.Func("(*Buffer).Reset", (*bytes.Buffer).Reset, execmBufferReset),
+		I.Func("(*Buffer).Grow", (*bytes.Buffer).Grow, execmBufferGrow),
+		I.Func("(*Buffer).Write", (*bytes.Buffer).Write, execmBufferWrite),
+		I.Func("(*Buffer).WriteString", (*bytes.Buffer).WriteString, execmBufferWriteString),
+		I.Func("(*Buffer).ReadFrom", (*bytes.Buffer).ReadFrom, execmBufferReadFrom),
+		I.Func("(*Buffer).WriteTo", (*bytes.Buffer).WriteTo, execmBufferWriteTo),
+		I.Func("(*Buffer).WriteByte", (*bytes.Buffer).WriteByte, execmBufferWriteByte),
+		I.Func("(*Buffer).WriteRune", (*bytes.Buffer).WriteRune, execmBufferWriteRune),
+		I.Func("(*Buffer).Read", (*bytes.Buffer).Read, execmBufferRead),
+		I.Func("(*Buffer).Next", (*bytes.Buffer).Next, execmBufferNext),
+		I.Func("(*Buffer).ReadByte", (*bytes.Buffer).ReadByte, execmBufferReadByte),
+		I.Func("(*Buffer).ReadRune", (*bytes.Buffer).ReadRune, execmBufferReadRune),
+		I.Func("(*Buffer).UnreadRune", (*bytes.Buffer).UnreadRune, execmBufferUnreadRune),
+		I.Func("(*Buffer).UnreadByte", (*bytes.Buffer).UnreadByte, execmBufferUnreadByte),
+		I.Func("(*Buffer).ReadBytes", (*bytes.Buffer).ReadBytes, execmBufferReadBytes),
+		I.Func("(*Buffer).ReadString", (*bytes.Buffer).ReadString, execmBufferReadString),
+		I.Func("Compare", bytes.Compare, execCompare),
+		I.Func("Contains", bytes.Contains, execContains),
+		I.Func("ContainsAny", bytes.ContainsAny, execContainsAny),
+		I.Func("ContainsRune", bytes.ContainsRune, execContainsRune),
+		I.Func("Count", bytes.Count, execCount),
+		I.Func("Equal", bytes.Equal, execEqual),
+		I.Func("EqualFold", bytes.EqualFold, execEqualFold),
+		I.Func("Fields", bytes.Fields, execFields),
+		I.Func("FieldsFunc", bytes.FieldsFunc, execFieldsFunc),
+		I.Func("HasPrefix", bytes.HasPrefix, execHasPrefix),
+		I.Func("HasSuffix", bytes.HasSuffix, execHasSuffix),
+		I.Func("Index", bytes.Index, execIndex),
+		I.Func("IndexAny", bytes.IndexAny, execIndexAny),
+		I.Func("IndexByte", bytes.IndexByte, execIndexByte),
+		I.Func("IndexFunc", bytes.IndexFunc, execIndexFunc),
+		I.Func("IndexRune", bytes.IndexRune, execIndexRune),
+		I.Func("Join", bytes.Join, execJoin),
+		I.Func("LastIndex", bytes.LastIndex, execLastIndex),
+		I.Func("LastIndexAny", bytes.LastIndexAny, execLastIndexAny),
+		I.Func("LastIndexByte", bytes.LastIndexByte, execLastIndexByte),
+		I.Func("LastIndexFunc", bytes.LastIndexFunc, execLastIndexFunc),
+		I.Func("Map", bytes.Map, execMap),
+		I.Func("NewBuffer", bytes.NewBuffer, execNewBuffer),
+		I.Func("NewBufferString", bytes.NewBufferString, execNewBufferString),
+		I.Func("NewReader", bytes.NewReader, execNewReader),
+		I.Func("(*Reader).Len", (*bytes.Reader).Len, execmReaderLen),
+		I.Func("(*Reader).Size", (*bytes.Reader).Size, execmReaderSize),
+		I.Func("(*Reader).Read", (*bytes.Reader).Read, execmReaderRead),
+		I.Func("(*Reader).ReadAt", (*bytes.Reader).ReadAt, execmReaderReadAt),
+		I.Func("(*Reader).ReadByte", (*bytes.Reader).ReadByte, execmReaderReadByte),
+		I.Func("(*Reader).UnreadByte", (*bytes.Reader).UnreadByte, execmReaderUnreadByte),
+		I.Func("(*Reader).ReadRune", (*bytes.Reader).ReadRune, execmReaderReadRune),
+		I.Func("(*Reader).UnreadRune", (*bytes.Reader).UnreadRune, execmReaderUnreadRune),
+		I.Func("(*Reader).Seek", (*bytes.Reader).Seek, execmReaderSeek),
+		I.Func("(*Reader).WriteTo", (*bytes.Reader).WriteTo, execmReaderWriteTo),
+		I.Func("(*Reader).Reset", (*bytes.Reader).Reset, execmReaderReset),
+		I.Func("Repeat", bytes.Repeat, execRepeat),
+		I.Func("Replace", bytes.Replace, execReplace),
+		I.Func("ReplaceAll", bytes.ReplaceAll, execReplaceAll),
+		I.Func("Runes", bytes.Runes, execRunes),
+		I.Func("Split", bytes.Split, execSplit),
+		I.Func("SplitAfter", bytes.SplitAfter, execSplitAfter),
+		I.Func("SplitAfterN", bytes.SplitAfterN, execSplitAfterN),
+		I.Func("SplitN", bytes.SplitN, execSplitN),
+		I.Func("Title", bytes.Title, execTitle),
+		I.Func("ToLower", bytes.ToLower, execToLower),
+		I.Func("ToLowerSpecial", bytes.ToLowerSpecial, execToLowerSpecial),
+		I.Func("ToTitle", bytes.ToTitle, execToTitle),
+		I.Func("ToTitleSpecial", bytes.ToTitleSpecial, execToTitleSpecial),
+		I.Func("ToUpper", bytes.ToUpper, execToUpper),
+		I.Func("ToUpperSpecial", bytes.ToUpperSpecial, execToUpperSpecial),
+		I.Func("ToValidUTF8", bytes.ToValidUTF8, execToValidUTF8),
+		I.Func("Trim", bytes.Trim, execTrim),
+		I.Func("TrimFunc", bytes.TrimFunc, execTrimFunc),
+		I.Func("TrimLeft", bytes.TrimLeft, execTrimLeft),
+		I.Func("TrimLeftFunc", bytes.TrimLeftFunc, execTrimLeftFunc),
+		I.Func("TrimPrefix", bytes.TrimPrefix, execTrimPrefix),
+		I.Func("TrimRight", bytes.TrimRight, execTrimRight),
+		I.Func("TrimRightFunc", bytes.TrimRightFunc, execTrimRightFunc),
+		I.Func("TrimSpace", bytes.TrimSpace, execTrimSpace),
+		I.Func("TrimSuffix", bytes.TrimSuffix, execTrimSuffix),
+	)
+	I.RegisterVars(
+		I.Var("ErrTooLarge", &bytes.ErrTooLarge),
+	)
+	I.RegisterConsts(
+		I.Const("MinRead", qspec.ConstUnboundInt, bytes.MinRead),
+	)
+}
diff --git a/lib/default.go b/lib/default.go
index 61ed7196..f4a57842 100644
--- a/lib/default.go
+++ b/lib/default.go
@@ -2,6 +2,7 @@ package lib
 
 import (
 	_ "github.com/goplus/gop/lib/builtin" // builtin
+	_ "github.com/goplus/gop/lib/bytes"
 	_ "github.com/goplus/gop/lib/errors"
 	_ "github.com/goplus/gop/lib/flag"
 	_ "github.com/goplus/gop/lib/fmt"


Test patch:
diff --git a/cl/cltest/testing.go b/cl/cltest/testing.go
index 6f0e60de..5e0b9cb5 100644
--- a/cl/cltest/testing.go
+++ b/cl/cltest/testing.go
@@ -24,19 +24,21 @@ func Expect(t *testing.T, script string, expected string, panicMsg ...interface{
 		t.Fatal("ParseFSDir failed:", err, len(pkgs))
 	}
 
-	bar := pkgs["main"]
-	b := exec.NewBuilder(nil)
-	pkg, err := cl.NewPackage(b.Interface(), bar, fset, cl.PkgActClMain)
-	if err != nil {
-		t.Fatal("Compile failed:", err)
-	}
-	cl.Debug(pkg)
-	code := b.Resolve()
-
-	ctx := exec.NewContext(code)
 	e := ts.StartExpecting(t, ts.CapStdout)
 	defer e.Close()
-	e.Call(ctx.Exec, 0, code.Len()).Expect(expected).Panic(panicMsg...)
+
+	e.Call(func() {
+		bar := pkgs["main"]
+		b := exec.NewBuilder(nil)
+		pkg, err := cl.NewPackage(b.Interface(), bar, fset, cl.PkgActClMain)
+		if err != nil {
+			t.Fatal("Compile failed:", err)
+		}
+		cl.Debug(pkg)
+		code := b.Resolve()
+		ctx := exec.NewContext(code)
+		ctx.Exec(0, code.Len())
+	}).Expect(expected).Panic(panicMsg...)
 }
 
 // -----------------------------------------------------------------------------
@@ -50,17 +52,16 @@ func Call(t *testing.T, script string, idx ...int) *ts.TestCase {
 		t.Fatal("ParseFSDir failed:", err, len(pkgs))
 	}
 
-	bar := pkgs["main"]
-	b := exec.NewBuilder(nil)
-	pkg, err := cl.NewPackage(b.Interface(), bar, fset, cl.PkgActClMain)
-	if err != nil {
-		t.Fatal("Compile failed:", err)
-	}
-	cl.Debug(pkg)
-	code := b.Resolve()
-
-	ctx := exec.NewContext(code)
 	return ts.New(t).Call(func() interface{} {
+		bar := pkgs["main"]
+		b := exec.NewBuilder(nil)
+		pkg, err := cl.NewPackage(b.Interface(), bar, fset, cl.PkgActClMain)
+		if err != nil {
+			t.Fatal("Compile failed:", err)
+		}
+		cl.Debug(pkg)
+		code := b.Resolve()
+		ctx := exec.NewContext(code)
 		ctx.Exec(0, code.Len())
 		return ctx.Get(append(idx, -1)[0])
 	})
diff --git a/cl/expr_test.go b/cl/expr_test.go
index af4edd72..2ecf823b 100644
--- a/cl/expr_test.go
+++ b/cl/expr_test.go
@@ -17,7 +17,6 @@ package cl_test
 
 import (
 	"math/big"
-	"strings"
 	"testing"
 
 	"github.com/goplus/gop/cl/cltest"
@@ -25,16 +24,45 @@ import (
 
 // -----------------------------------------------------------------------------
 
+func TestAutoProperty(t *testing.T) {
+	cltest.Expect(t, `
+		import "bytes"
+		import "os"
+
+		b := bytes.NewBuffer(nil)
+		b.WriteString("Hello, ")
+		b.WriteString("Go+")
+		println(b.string)
+		`,
+		"Hello, Go+\n",
+	)
+	cltest.Expect(t, `
+		import "bytes"
+		import "os"
+
+		b := bytes.NewBuffer(nil)
+		b.WriteString("Hello, ")
+		b.WriteString("Go+")
+		println(b.string2)
+		`,
+		"",
+		nil, // panic
+	)
+}
+
 func TestUnbound(t *testing.T) {
-	cltest.Expect(t,
-		`println("Hello " + "qiniu:", 123, 4.5, 7i)`,
+	cltest.Expect(t, `
+		println("Hello " + "qiniu:", 123, 4.5, 7i)
+		`,
 		"Hello qiniu: 123 4.5 (0+7i)\n",
 	)
 }
 
 func TestPanic(t *testing.T) {
 	cltest.Expect(t,
-		`panic("Helo")`, "", "Helo",
+		`panic("Helo")`,
+		"",
+		"Helo", // panicMsg
 	)
 }
 
@@ -49,48 +77,46 @@ func TestMake(t *testing.T) {
 	cltest.Expect(t, `
 		a := make([]int, 0, 4)
 		a = append(a, 1, 2, 3)
-		println(a)`,
+		println(a)
+		`,
 		"[1 2 3]\n",
 	)
-}
-
-func TestMake2(t *testing.T) {
 	cltest.Expect(t, `
 		a := make([]int, 0, 4)
 		a = append(a, [1, 2, 3]...)
-		println(a)`,
+		println(a)
+		`,
 		"[1 2 3]\n",
 	)
-}
-
-func TestMake3(t *testing.T) {
 	cltest.Expect(t, `
 		n := 4
 		a := make(map[string]interface{}, uint16(n))
-		println(a)`,
+		println(a)
+		`,
 		"map[]\n",
 	)
-}
-
-func TestMake4(t *testing.T) {
 	cltest.Expect(t, `
 		import "reflect"
 
 		a := make(chan *func(), uint16(4))
-		println(reflect.TypeOf(a))`,
-		"chan *func()\n")
+		println(reflect.TypeOf(a))
+		`,
+		"chan *func()\n",
+	)
 }
 
 func TestOperator(t *testing.T) {
 	cltest.Expect(t, `
-		println("Hello", 123 * 4.5, 1 + 7i)`,
+		println("Hello", 123 * 4.5, 1 + 7i)
+		`,
 		"Hello 553.5 (1+7i)\n")
 }
 
 func TestVar(t *testing.T) {
 	cltest.Expect(t, `
 		x := 123.1
-		println("Hello", x)`,
+		println("Hello", x)
+		`,
 		"Hello 123.1\n")
 }
 
@@ -100,8 +126,10 @@ func TestVarOp(t *testing.T) {
 		y := 1 + x
 		println("Hello", y + 10)
 		n, err := println("Hello", y + 10)
-		println("ret:", n << 1, err)`,
-		"Hello 134.1\nHello 134.1\nret: 24 <nil>\n")
+		println("ret:", n << 1, err)
+		`,
+		"Hello 134.1\nHello 134.1\nret: 24 <nil>\n",
+	)
 }
 
 func TestGoPackage(t *testing.T) {
@@ -110,61 +138,69 @@ func TestGoPackage(t *testing.T) {
 		import gostrings "strings"
 
 		x := gostrings.NewReplacer("?", "!").Replace("hello, world???")
-		fmt.Println("x: " + x)`,
-		"x: hello, world!!!\n")
+		fmt.Println("x: " + x)
+		`,
+		"x: hello, world!!!\n",
+	)
 }
 
 func TestSlice(t *testing.T) {
 	cltest.Expect(t, `
 		x := []float64{1, 2.3, 3.6}
-		println("x:", x)`,
-		"x: [1 2.3 3.6]\n")
-}
-
-func TestSlice2(t *testing.T) {
+		println("x:", x)
+		`,
+		"x: [1 2.3 3.6]\n",
+	)
 	cltest.Expect(t, `
 		x := []float64{1, 2: 3.4, 5}
-		println("x:", x)`,
-		"x: [1 0 3.4 5]\n")
+		println("x:", x)
+		`,
+		"x: [1 0 3.4 5]\n",
+	)
 }
+
 func TestArray(t *testing.T) {
 	cltest.Expect(t, `
 		x := [4]float64{1, 2.3, 3.6}
 		println("x:", x)
 
 		y := [...]float64{1, 2.3, 3.6}
-		println("y:", y)`, "x: &[2.3 3.6 0 0]\ny: &[1 2.3 3.6]\n")
-}
-
-func TestArray2(t *testing.T) {
+		println("y:", y)
+		`,
+		"x: &[2.3 3.6 0 0]\ny: &[1 2.3 3.6]\n",
+	)
 	cltest.Expect(t, `
 		x := [...]float64{1, 3: 3.4, 5}
 		x[1] = 217
-		println("x:", x, "x[1]:", x[1])`,
-		"x: &[1 217 0 3.4 5] x[1]: 217\n")
+		println("x:", x, "x[1]:", x[1])
+		`,
+		"x: &[1 217 0 3.4 5] x[1]: 217\n",
+	)
 }
 
 func TestMap(t *testing.T) {
 	cltest.Expect(t, `
-	x := map[string]float64{"Hello": 1, "xsw": 3.4}
-	println("x:", x)`,
+		x := map[string]float64{"Hello": 1, "xsw": 3.4}
+		println("x:", x)
+		`,
 		"x: map[Hello:1 xsw:3.4]\n")
 }
 
 func TestMapLit(t *testing.T) {
 	cltest.Expect(t, `
 		x := {"Hello": 1, "xsw": 3.4}
-		println("x:", x)`,
-		"x: map[Hello:1 xsw:3.4]\n")
-}
-
-func TestMapLit2(t *testing.T) {
+		println("x:", x)
+		`,
+		"x: map[Hello:1 xsw:3.4]\n",
+	)
 	cltest.Expect(t, `
 		x := {"Hello": 1, "xsw": "3.4"}
 		println("x:", x)
 
-		println("empty map:", {})`,
-		"x: map[Hello:1 xsw:3.4]\nempty map: map[]\n")
+		println("empty map:", {})
+		`,
+		"x: map[Hello:1 xsw:3.4]\nempty map: map[]\n",
+	)
 }
 
 func TestMapIdx(t *testing.T) {
@@ -176,8 +212,10 @@ func TestMapIdx(t *testing.T) {
 		key := "xsw"
 		x["xsw"], y[i] = 3.1415926, q
 		println("x:", x, "y:", y)
-		println("x[key]:", x[key], "y[1]:", y[1])`,
-		"x: map[Hello:1 xsw:3.1415926] y: map[1:Q 5:Hi]\nx[key]: 3.1415926 y[1]: Q\n")
+		println("x[key]:", x[key], "y[1]:", y[1])
+		`,
+		"x: map[Hello:1 xsw:3.1415926] y: map[1:Q 5:Hi]\nx[key]: 3.1415926 y[1]: Q\n",
+	)
 }
 
 func TestSliceLit(t *testing.T) {
@@ -191,74 +229,68 @@ func TestSliceLit(t *testing.T) {
 		z := [1+2i, "xsw"]
 		println("z:", z)
 
-		println("empty slice:", [])`,
+		println("empty slice:", [])
+		`,
 		"x: [1 3.4]\ny: [1]\nz: [(1+2i) xsw]\nempty slice: []\n")
 }
 
 func TestSliceIdx(t *testing.T) {
 	cltest.Expect(t, `
-	x := [1, 3.4, 17]
-	n, m := 1, uint16(0)
-	x[1] = 32.7
-	x[m] = 36.86
-	println("x:", x[2], x[m], x[n])`,
+		x := [1, 3.4, 17]
+		n, m := 1, uint16(0)
+		x[1] = 32.7
+		x[m] = 36.86
+		println("x:", x[2], x[m], x[n])
+		`,
 		"x: 17 36.86 32.7\n")
 }
 
 func TestListComprehension(t *testing.T) {
 	cltest.Expect(t, `
 		y := [i+x for i, x <- [1, 2, 3, 4]]
-		println("y:", y)`,
+		println("y:", y)
+		`,
 		"y: [1 3 5 7]\n")
-}
-
-func TestListComprehension2(t *testing.T) {
 	cltest.Call(t, `
 		y := [i+x for i, x <- {3: 1, 5: 2, 7: 3, 11: 4}]
 		println("y:", y)
-	`, -2).Equal(15)
-}
-
-func TestListComprehensionFilter(t *testing.T) {
+		`, -2).Equal(15)
 	cltest.Call(t, `
 		y := [i+x for i, x <- {3: 1, 5: 2, 7: 3, 11: 4}, x % 2 == 1]
 		println("y:", y)
-	`, -2).Equal(10)
+		`, -2).Equal(10)
 }
 
 func TestMapComprehension(t *testing.T) {
 	cltest.Expect(t, `
 		y := {x: i for i, x <- [3, 5, 7, 11, 13]}
-		println("y:", y)`,
-		"y: map[3:0 5:1 7:2 11:3 13:4]\n")
-}
-
-func TestMapComprehensionFilter(t *testing.T) {
+		println("y:", y)
+		`,
+		"y: map[3:0 5:1 7:2 11:3 13:4]\n",
+	)
 	cltest.Expect(t, `
 		y := {x: i for i, x <- [3, 5, 7, 11, 13], i % 2 == 1}
-		println("y:", y)`,
-		"y: map[5:1 11:3]\n")
-}
-
-func TestMapComprehension2(t *testing.T) {
+		println("y:", y)
+		`,
+		"y: map[5:1 11:3]\n",
+	)
 	cltest.Expect(t, `
 		y := {v: k for k, v <- {"Hello": "xsw", "Hi": "glang"}}
-		println("y:", y)`,
-		"y: map[glang:Hi xsw:Hello]\n")
-}
-
-func TestMapComprehension3(t *testing.T) {
+		println("y:", y)
+		`,
+		"y: map[glang:Hi xsw:Hello]\n",
+	)
 	cltest.Expect(t, `
 		println({x: i for i, x <- [3, 5, 7, 11, 13]})
-		println({x: i for i, x <- [3, 5, 7, 11, 13]})`,
-		"map[3:0 5:1 7:2 11:3 13:4]\nmap[3:0 5:1 7:2 11:3 13:4]\n")
-}
-
-func TestMapComprehension4(t *testing.T) {
+		println({x: i for i, x <- [3, 5, 7, 11, 13]})
+		`,
+		"map[3:0 5:1 7:2 11:3 13:4]\nmap[3:0 5:1 7:2 11:3 13:4]\n",
+	)
 	cltest.Expect(t, `
 		arr := [1, 2, 3, 4, 5, 6]
 		x := [[a, b] for a <- arr, a < b for b <- arr, b > 2]
-		println("x:", x)`,
+		println("x:", x)
+		`,
 		"x: [[1 3] [2 3] [1 4] [2 4] [3 4] [1 5] [2 5] [3 5] [4 5] [1 6] [2 6] [3 6] [4 6] [5 6]]\n")
 }
 
@@ -266,10 +298,7 @@ func TestErrWrapExpr(t *testing.T) {
 	cltest.Call(t, `
 		x := println("Hello qiniu")!
 		x
-	`).Equal(12)
-}
-
-func TestErrWrapExpr2(t *testing.T) {
+		`).Equal(12)
 	cltest.Call(t, `
 		import (
 			"strconv"
@@ -281,7 +310,7 @@ func TestErrWrapExpr2(t *testing.T) {
 	
 		x := add("100", "23")!
 		x
-	`).Equal(123)
+		`).Equal(123)
 }
 
 func TestRational(t *testing.T) {
@@ -289,17 +318,11 @@ func TestRational(t *testing.T) {
 		x := 3/4r + 5/7r
 		x
 	`).Equal(big.NewRat(41, 28))
-}
-
-func TestRational2(t *testing.T) {
 	cltest.Call(t, `
 		a := 3/4r
 		x := a + 5/7r
 		x
 	`).Equal(big.NewRat(41, 28))
-}
-
-func TestRational3(t *testing.T) {
 	y, _ := new(big.Float).SetString(
 		"3.14159265358979323846264338327950288419716939937510582097494459")
 	y.Mul(y, big.NewFloat(2))
@@ -308,9 +331,6 @@ func TestRational3(t *testing.T) {
 		y *= 2
 		y
 	`).Equal(y)
-}
-
-func TestRational4(t *testing.T) {
 	cltest.Call(t, `
 		a := 3/4r
 		b := 5/7r
@@ -319,9 +339,6 @@ func TestRational4(t *testing.T) {
 		}
 		a
 	`).Equal(big.NewRat(7, 4))
-}
-
-func TestRational5(t *testing.T) {
 	cltest.Call(t, `
 		x := 1/3r + 1r*2r
 		x
@@ -330,7 +347,8 @@ func TestRational5(t *testing.T) {
 
 type testData struct {
 	clause string
-	wants  []string
+	want   string
+	panic  bool
 }
 
 var testDeleteClauses = map[string]testData{
@@ -342,7 +360,7 @@ var testDeleteClauses = map[string]testData{
 					println(m)
 					delete(m,2)
 					println(m)
-					`, []string{"map[2:2]", "map[2:2]", "map[]"}},
+					`, "map[2:2]\nmap[2:2]\nmap[]\n", false},
 	"delete_string_key": {`
 					m:={"hello":1,"Go+":2}
 					delete(m,"hello")
@@ -351,7 +369,7 @@ var testDeleteClauses = map[string]testData{
 					println(m)
 					delete(m,"Go+")
 					println(m)
-					`, []string{"map[Go+:2]", "map[Go+:2]", "map[]"}},
+					`, "map[Go+:2]\nmap[Go+:2]\nmap[]\n", false},
 	"delete_var_string_key": {`
 					m:={"hello":1,"Go+":2}
 					delete(m,"hello")
@@ -362,7 +380,7 @@ var testDeleteClauses = map[string]testData{
 					arr:=["Go+"]
 					delete(m,arr[0])
 					println(m)
-					`, []string{"map[Go+:2]", "map[Go+:2]", "map[]"}},
+					`, "map[Go+:2]\nmap[Go+:2]\nmap[]\n", false},
 	"delete_var_map_string_key": {`
 					ma:=[{"hello":1,"Go+":2}]
 					delete(ma[0],"hello")
@@ -373,19 +391,19 @@ var testDeleteClauses = map[string]testData{
 					arr:=["Go+"]
 					delete(ma[0],arr[0])
 					println(ma[0])
-					`, []string{"map[Go+:2]", "map[Go+:2]", "map[]"}},
+					`, "map[Go+:2]\nmap[Go+:2]\nmap[]\n", false},
 	"delete_no_key_panic": {`
 					m:={"hello":1,"Go+":2}
 					delete(m)
-					`, []string{"_panic"}},
+					`, "", true},
 	"delete_multi_key_panic": {`
 					m:={"hello":1,"Go+":2}
 					delete(m,"hi","hi")
-					`, []string{"_panic"}},
+					`, "", true},
 	"delete_not_map_panic": {`
 					m:=[1,2,3]
 					delete(m,1)
-					`, []string{"_panic"}},
+					`, "", true},
 }
 
 func TestDelete(t *testing.T) {
@@ -401,14 +419,14 @@ var testCopyClauses = map[string]testData{
 					n:=copy(b,a)
 					println(n)
 					println(b)
-					`, []string{"3", "[1 2 3]"}},
+					`, "3\n[1 2 3]\n", false},
 	"copy_string": {`
 					a:=["hello"]
 					b:=["hi"]
 					n:=copy(b,a)
 					println(n)
 					println(b)
-					`, []string{"1", "[hello]"}},
+					`, "1\n[hello]\n", false},
 	"copy_byte_string": {`
 					a:=[byte(65),byte(66),byte(67)]
 					println(string(a))
@@ -416,46 +434,46 @@ var testCopyClauses = map[string]testData{
 					println(n)
 					println(a)
 					println(string(a))
-					`, []string{"ABC", "3", "[97 98 99]", "abc"}},
+					`, "ABC\n3\n[97 98 99]\nabc\n", false},
 	"copy_first_not_slice_panic": {`
 					a:=1
 					b:=[1,2,3]
 					copy(a,b)
 					println(a)
-					`, []string{"_panic"}},
+					`, "", true},
 	"copy_second_not_slice_panic": {`
 					a:=1
 					b:=[1,2,3]
 					copy(b,a)
 					println(b)
-					`, []string{"_panic"}},
+					`, "", true},
 	"copy_one_args_panic": {`
 					a:=[1,2,3]
 					copy(a)
 					println(a)
-					`, []string{"_panic"}},
+					`, "", true},
 	"copy_multi_args_panic": {`
 					a:=[1,2,3]
 					copy(a,a,a)
 					println(a)
-					`, []string{"_panic"}},
+					`, "", true},
 	"copy_string_panic": {`
 					a:=[65,66,67]
 					copy(a,"abc")
 					println(a)
-					`, []string{"_panic"}},
+					`, "", true},
 	"copy_different_type_panic": {`
 					a:=[65,66,67]
 					b:=[1.2,1.5,1.7]
 					copy(b,a)
 					copy(b,a)
 					println(b)
-					`, []string{"_panic"}},
+					`, "", true},
 	"copy_with_operation": {`
 					a:=[65,66,67]
 					b:=[1]
 					println(copy(a,b)+copy(b,a)==2)
-					`, []string{"true"}},
+					`, "true\n", false},
 }
 
 func TestCopy(t *testing.T) {
@@ -465,23 +483,11 @@ func TestCopy(t *testing.T) {
 func testScripts(t *testing.T, testName string, scripts map[string]testData) {
 	for name, script := range scripts {
 		t.Log("Run " + testName + "---" + name)
-		testSingleScript(name, t, script.clause, script.wants)
-	}
-}
-
-func testSingleScript(name string, t *testing.T, script string, wants []string) {
-	defer func() {
-		if r := recover(); r != nil {
-			if len(wants) > 0 && wants[0] == "_panic" {
-				return
-			}
-			t.Fatal(name, "-", r)
+		var panicMsg []interface{}
+		if script.panic {
+			panicMsg = append(panicMsg, nil)
 		}
-	}()
-	if len(wants) > 0 && wants[0] != "_panic" {
-		cltest.Expect(t, script, strings.Join(wants, "\n")+"\n")
-	} else {
-		cltest.Expect(t, script, "", "")
+		cltest.Expect(t, script.clause, script.want, panicMsg...)
 	}
 }
 
diff --git a/cl/stmt_test.go b/cl/stmt_test.go
index 3f355e3f..cc7576ae 100644
--- a/cl/stmt_test.go
+++ b/cl/stmt_test.go
@@ -42,11 +42,11 @@ func TestSwitch(t *testing.T) {
 }
 
 var testSwitchIfScripts = map[string]testData{
-	"switch_into_case":            {testSwif, []string{"5"}},
-	"switch_into_default":         {testSwif2, []string{"7"}},
-	"switch_into_case_with_cond":  {testSw, []string{"5"}},
-	"switch_into_case_with_empty": {testSw2, []string{"5"}},
-	"switch_with_no_case":         {testSw3, []string{"7"}},
+	"switch_into_case":            {testSwif, "5\n", false},
+	"switch_into_default":         {testSwif2, "7\n", false},
+	"switch_into_case_with_cond":  {testSw, "5\n", false},
+	"switch_into_case_with_empty": {testSw2, "5\n", false},
+	"switch_with_no_case":         {testSw3, "7\n", false},
 }
 
 var testSwif = `
@@ -119,8 +119,8 @@ var testSw3 = `
 // -----------------------------------------------------------------------------
 
 var testIfScripts = map[string]testData{
-	"if_with_else":    {testIf, []string{"5"}},
-	"if_without_else": {testIf2, []string{"3"}},
+	"if_with_else":    {testIf, "5\n", false},
+	"if_without_else": {testIf2, "3\n", false},
 }
 
 var testIf = `
@@ -311,44 +311,44 @@ var testForRangeClauses = map[string]testData{
 						sum++
 					}
 					println(sum)
-					`, []string{"4"}},
+					`, "4\n", false},
 	"no_kv_range_map": {`sum:=0
 					for range {1:1,2:2,3:3} {
 						sum++
 					}
 					println(sum)
-					`, []string{"3"}},
+					`, "3\n", false},
 	"only_k_range_list": {`sum:=0
 					for k :=range [1,3,5,7]{
 						sum+=k
 					}
 					println(sum)
-					`, []string{"6"}},
+					`, "6\n", false},
 	"only_k_range_map": {`sum:=0
 					for k :=range {1:1,2:4,3:8,4:16}{
 						sum+=k
 					}
 					println(sum)
-					`, []string{"10"}},
+					`, "10\n", false},
 	"only_v_range_list": {`sum:=0
 					for _,v :=range [1,3,5,7]{
 						sum+=v
 					}
 					println(sum)
-					`, []string{"16"}},
+					`, "16\n", false},
 	"only_v_range_map": {`sum:=0
 					for _,v :=range {1:1,2:4,3:8,4:16}{
 						sum+=v
 					}
 					println(sum)
-					`, []string{"29"}},
+					`, "29\n", false},
 	"both_kv_range_list": {`sum:=0
 					for k,v:=range [1,3,5,7]{
 						// 0*1+1*3+2*5+3*7
 						sum+=k*v
 					}
 					println(sum)
-					`, []string{"34"}},
+					`, "34\n", false},
 	"both_kv_range_map": {`sum:=0
 					m:={1:2,2:4,3:8}
 					for k,v:=range m {
@@ -356,7 +356,7 @@ var testForRangeClauses = map[string]testData{
 						sum+=k*v
 					}
 					println(sum)
-					`, []string{"34"}},
+					`, "34\n", false},
 	"both_kv_assign_simple_range": {` sum:=0
 					k,v:=0,0
 					for k,v=range [1,2,3,4,5]{
@@ -365,7 +365,7 @@ var testForRangeClauses = map[string]testData{
 					println(k)
 					println(v)
 					println(sum)
-					`, []string{"4", "5", "25"}},
+					`, "4\n5\n25\n", false},
 	"both_kv_assign_range_list": {` sum:=0
 					m:={1:2,2:4,3:8}
 					arr:=[11,22]
@@ -375,7 +375,7 @@ var testForRangeClauses = map[string]testData{
 					println(m[1])
 					println(m[2])
 					println(sum)
-					`, []string{"1", "22", "34"}},
+					`, "1\n22\n34\n", false},
 	"both_kv_assign_range_map": {` sum:=0
 					m:={3:8}
 					arr:=[11,22]
@@ -385,7 +385,7 @@ var testForRangeClauses = map[string]testData{
 					println(arr[0])
 					println(arr[1])
 					println(sum)
-					`, []string{"3", "8", "11"}},
+					`, "3\n8\n11\n", false},
 	"only_v_assign_range": {` sum:=0
 					m:={3:8}
 					arr:=[11,22]
@@ -395,7 +395,7 @@ var testForRangeClauses = map[string]testData{
 					println(arr[0])
 					println(arr[1])
 					println(sum)
-					`, []string{"11", "8", "19"}},
+					`, "11\n8\n19\n", false},
 	"only_k_assign_range": {` sum:=0
 					m:={3:8}
 					arr:=[11,22]
@@ -405,7 +405,7 @@ var testForRangeClauses = map[string]testData{
 					println(arr[0])
 					println(arr[1])
 					println(sum)
-					`, []string{"3", "22", "25"}},
+					`, "3\n22\n25\n", false},
 	"none_kv_assign_range": {` sum:=0
 					m:={3:8}
 					arr:=[11,22]
@@ -415,7 +415,7 @@ var testForRangeClauses = map[string]testData{
 					println(arr[0])
 					println(arr[1])
 					println(sum)
-					`, []string{"11", "22", "33"}},
+					`, "11\n22\n33\n", false},
 }
 
 func TestRangeStmt(t *testing.T) {
@@ -467,7 +467,7 @@ var testNormalForClauses = map[string]testData{
 						sum+=arr[i]
 					}
 					println(sum)
-					`, []string{"16"}},
+					`, "16\n", false},
 	"for_with_cond_post": {`
 					sum := 0
 					arr := [1,3,5,7]
@@ -476,7 +476,7 @@ var testNormalForClauses = map[string]testData{
 						sum+=arr[i]
 					}
 					println(sum)
-					`, []string{"6"}},
+					`, "6\n", false},
 	"for_with_cond": {`
 					arr := [1,3,5,7]
 					i := 0
@@ -486,7 +486,7 @@ var testNormalForClauses = map[string]testData{
 						i++
 					}
 					println(sum)
-					`, []string{"4"}},
+					`, "4\n", false},
 	"for_with_init_cond": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -495,7 +495,7 @@ var testNormalForClauses = map[string]testData{
 						i++
 					}
 					println(sum)
-					`, []string{"16"}},
+					`, "16\n", false},
 	"for_with_continue": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -506,7 +506,7 @@ var testNormalForClauses = map[string]testData{
 						sum+=arr[i]
 					}
 					println(sum)
-					`, []string{"12"}},
+					`, "12\n", false},
 	"for_with_break": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -517,7 +517,7 @@ var testNormalForClauses = map[string]testData{
 						sum+=arr[i]
 					}
 					println(sum)
-					`, []string{"9"}},
+					`, "9\n", false},
 	"for_with_break_label": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -531,7 +531,7 @@ var testNormalForClauses = map[string]testData{
 						}
 					}
 					println(sum)
-					`, []string{"12"}}, // (1+1)+(1+3)+(1+5)
+					`, "12\n", false}, // (1+1)+(1+3)+(1+5)
 	"for_with_continue_label": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -545,7 +545,7 @@ var testNormalForClauses = map[string]testData{
 						}
 					}
 					println(sum)
-					`, []string{"48"}}, // (1+3+5+7)*2+(1+3)*4
+					`, "48\n", false}, // (1+3+5+7)*2+(1+3)*4
 	"for_with_continue_break": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -561,7 +561,7 @@ var testNormalForClauses = map[string]testData{
 						}
 					}
 					println(sum)
-					`, []string{"36"}},
+					`, "36\n", false},
 	"for_with_continue_break_continue": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -578,7 +578,7 @@ var testNormalForClauses = map[string]testData{
 						}
 					}
 					println(sum)
-					`, []string{"12"}},
+					`, "12\n", false},
 	"for_with_continue_panic": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -586,7 +586,7 @@ var testNormalForClauses = map[string]testData{
 					}
 					continue
 					println(sum)
-					`, []string{"_panic"}},
+					`, "", true},
 	"for_with_continue_no_label_panic": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -594,7 +594,7 @@ var testNormalForClauses = map[string]testData{
 						continue L
 					}
 					println(sum)
-					`, []string{"_panic"}},
+					`, "", true},
 	"for_with_break_panic": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -602,7 +602,7 @@ var testNormalForClauses = map[string]testData{
 					}
 					break
 					println(sum)
-					`, []string{"_panic"}},
+					`, "", true},
 	"for_with_break_label_panic": {`
 					arr := [1,3,5,7]
 					L:
@@ -611,7 +611,7 @@ var testNormalForClauses = map[string]testData{
 						break L
 					}
 					println(sum)
-					`, []string{"_panic"}},
+					`, "", true},
 	"for_with_continue_wrong_label_panic": {`
 					arr := [1,3,5,7]
 					L:
@@ -620,7 +620,7 @@ var testNormalForClauses = map[string]testData{
 						continue L
 					}
 					println(sum)
-					`, []string{"_panic"}},
+					`, "", true},
 	"for_with_many_labels": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -638,7 +638,7 @@ var testNormalForClauses = map[string]testData{
 						}
 					}
 					println(sum)
-					`, []string{"8"}},
+					`, "8\n", false},
 	"for_with_many_labels_break": {`
 					arr := [1,3,5,7]
 					sum := 0
@@ -656,7 +656,7 @@ var testNormalForClauses = map[string]testData{
 						}
 					}
 					println(sum)
-					`, []string{"18"}},
+					`, "18\n", false},
 }
 
 func TestNormalForStmt(t *testing.T) {
@@ -691,7 +691,7 @@ var testSwitchBranchClauses = map[string]testData{
 						x=7
 						println(x)
 					}
-					`, []string{"0", "1", "7"}},
+					`, "0\n1\n7\n", false},
 	"switch_one_fallthrough": {`
 					x:=0
 					switch x {
@@ -705,7 +705,7 @@ var testSwitchBranchClauses = map[string]testData{
 						x=7
 						println(x)
 					}
-					`, []string{"0", "1"}},
+					`, "0\n1\n", false},
 	"switch__fallthrough": {`
 					x:=0
 					switch x {
@@ -719,7 +719,7 @@ var testSwitchBranchClauses = map[string]testData{
 						x=7
 						println(x)
 					}
-					`, []string{"0", "1"}},
+					`, "0\n1\n", false},
 	"switch_no_tag_fallthrough": {`
 					x:=0
 					switch {
@@ -734,7 +734,7 @@ var testSwitchBranchClauses = map[string]testData{
 						x=7
 						println(x)
 					}
-					`, []string{"0", "1", "7"}},
+					`, "0\n1\n7\n", false},
 	"switch_no_tag_one_fallthrough": {`
 					x:=0
 					switch x {
@@ -748,7 +748,7 @@ var testSwitchBranchClauses = map[string]testData{
 						x=7
 						println(x)
 					}
-					`, []string{"0", "1"}},
+					`, "0\n1\n", false},
 	"switch_fallthrough_panic": {`
 					x:=0
 					switch x {
@@ -763,7 +763,7 @@ var testSwitchBranchClauses = map[string]testData{
 						println(x)
 					fallthrough
 					}
-					`, []string{"_panic"}},
+					`, "", true},
 	"switch_fallthrough_out_panic": {`
 					x:=0
 					switch x {
@@ -778,7 +778,7 @@ var testSwitchBranchClauses = map[string]testData{
 						println(x)
 					}
 					fallthrough
-					`, []string{"_panic"}},
+					`, "", true},
 	"switch_break": {`
 					x:=0
 					y:=2
@@ -793,7 +793,7 @@ var testSwitchBranchClauses = map[string]testData{
 						x=7
 						println(x)
 					}
-					`, []string{"2"}},
+					`, "2\n", false},
 	"switch_break_label": {`
 					x:=0
 					y:=2
@@ -809,7 +809,7 @@ var testSwitchBranchClauses = map[string]testData{
 						x=7
 						println(x)
 					}
-					`, []string{"2"}},
+					`, "2\n", false},
 	"switch_for_continue_label": {`
 					x:=0
 					y:=2
@@ -840,8 +840,7 @@ var testSwitchBranchClauses = map[string]testData{
 							println(x)
 						}
 					}
-
-					`, []string{"2", "0", "1", "2"}},
+					`, "2\n0\n1\n2\n", false},
 }
 
 func TestSwitchBranchStmt(t *testing.T) {
@@ -856,7 +855,7 @@ var testGotoLabelClauses = map[string]testData{
 					println("before")
 					L:
 					println("over")
-					`, []string{"over"}},
+					`, "over\n", false},
 	"goto_after_label": {`
 					i:=0
 					L:
@@ -866,7 +865,7 @@ var testGotoLabelClauses = map[string]testData{
 							goto L
 						}
 					println("over")
-					`, []string{"0", "1", "2", "over"}},
+					`, "0\n1\n2\nover\n", false},
 	"goto_multi_labels": {`
 					i:=0
 					L:
@@ -884,22 +883,22 @@ var testGotoLabelClauses = map[string]testData{
 					L3:
 					println("over")
 					L4:
-					`, []string{"0", "1", "2", "over"}},
+					`, "0\n1\n2\nover\n", false},
 	"goto_nil_label": {`
 					goto;
 					println("over")
-					`, []string{"_panic"}},
+					`, "", true},
 	"goto_not_define_label": {`
 					goto L
 					println("over")
-					`, []string{"_panic"}},
+					`, "", true},
 	"goto_illegal_block": {`
 					goto L
 					{
 						L:
 						println("L")
 					}
-					`, []string{"_panic"}},
+					`, "", true},
 	"goto_redefine_block": {`
 					{
 						L:
@@ -910,7 +909,7 @@ var testGotoLabelClauses = map[string]testData{
 						println("L")
 					}
 					goto L
-					`, []string{"_panic"}},
+					`, "", true},
 }
 
 func TestGotoLabelStmt(t *testing.T) {