Repository: goplus/gop
Pull request #1927 (2024-07-14 11:54:11)

v1.3.0-pre.1


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index f3e4bc2b..ab059676 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -26,11 +26,9 @@ jobs:
         - windows-latest
         - macos-latest
         go:
-        - 1.18.x
         - 1.19.x
         - 1.20.x
         - 1.21.x
-        - 1.22.x
     runs-on: ${{ matrix.os }}
     steps:
     - uses: actions/checkout@v4
@@ -38,7 +36,7 @@ jobs:
     - name: Set up Go
       uses: actions/setup-go@v5
       with:
-        go: ${{ matrix.go }}
+        go-version: ${{ matrix.go }}
 
     - name: Test Go+ installer
       run: |
@@ -53,3 +51,4 @@ jobs:
       uses: codecov/codecov-action@v4
       with:
         token: ${{ secrets.CODECOV_TOKEN }}
+        slug: goplus/gop
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 0c966198..6f10496b 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -70,7 +70,7 @@ jobs:
         password: ${{ github.token }}
 
     - name: Release with goreleaser
-      uses: goreleaser/goreleaser-action@v5
+      uses: goreleaser/goreleaser-action@v6
       with:
         # either 'goreleaser' (default) or 'goreleaser-pro'
         distribution: goreleaser
diff --git a/.gitignore b/.gitignore
index 29fa5fdb..053202fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,11 +5,13 @@
 *.txt
 *.cache
 .DS_Store
+test.db
 go.json
 x.mod
-gop
-gopfmt
-goptestgo
+
+# gop
+# gopfmt
+# goptestgo
 
 # Folders
 _obj
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index e5da561d..369c2a59 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -249,8 +249,8 @@ nfpms:
         dst: "/usr/lib/{{ .ProjectName }}/scanner"
       - src: "test"
         dst: "/usr/lib/{{ .ProjectName }}/test"
-      - src: "testdata"
-        dst: "/usr/lib/{{ .ProjectName }}/testdata"
+      - src: "demo"
+        dst: "/usr/lib/{{ .ProjectName }}/demo"
       - src: "tidy.go"
         dst: "/usr/lib/{{ .ProjectName }}/tidy.go"
       - src: "token"
@@ -338,8 +338,8 @@ snapcrafts:
         destination: "scanner"
       - source: "test"
         destination: "test"
-      - source: "testdata"
-        destination: "testdata"
+      - source: "demo"
+        destination: "demo"
       - source: "tidy.go"
         destination: "tidy.go"
       - source: "token"
diff --git a/README.md b/README.md
index 5833aceb..42035e4b 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,14 @@ The Go+ programming language is designed for engineering, STEM education, and da
 For more details, see [Quick Start](doc/docs.md).
 
 
+## Key Features of Go+
+
+* Approaching natural language expression and intuitive (see [Command Style Code](#command-style-code)).
+* Fully compatible with [Go](https://github.com/golang/go) and can mix Go/Go+ code in the same package (see [Go/Go+ Hybrid Programming](doc/docs.md#gogo-hybrid-programming)).
+* Integrating with the C ecosystem including Python and providing limitless possibilities (see [Support for C/C++ and Python](#support-for-cc-and-python)).
+* Does not support DSL (Domain-Specific Languages), but supports SDF (Specific Domain Friendliness) (see [Go+ Classfiles](#go-classfiles)).
+
+
 ## Command Style Code
 
 Different from the function call style of most languages, Go+ recommends command style code:
@@ -61,6 +69,79 @@ echo "Hello world"
 For more discussion on coding style, see https://tutorial.goplus.org/hello-world.
 
 
+## Support for C/C++ and Python
+
+Go+ can choose different Go compilers as its underlying support. Currently known supported Go compilers include:
+
+* [go](https://go.dev/) (The official Go compiler supported by Google)
+* [llgo](https://github.com/goplus/llgo) (The Go compiler supported by the Go+ team)
+* [tinygo](https://tinygo.org/) (A Go compiler for small places)
+
+Currently, Go+ defaults to using [go](https://go.dev/) as its underlying support, but in the future, it will be [llgo](https://github.com/goplus/llgo).
+
+LLGo is a Go compiler based on [LLVM](https://llvm.org/) in order to better integrate Go with the C ecosystem including Python. It aims to expand the boundaries of Go/Go+, providing limitless possibilities such as:
+
+* Game development
+* AI and data science
+* WebAssembly
+* Embedded development
+* ...
+
+If you wish to use [llgo](https://github.com/goplus/llgo), specify the `-llgo` flag when initializing a Go+ module:
+
+```sh
+gop mod init -llgo YourModulePath
+```
+
+This will generate a `go.mod` file with the following contents (It may vary slightly depending on the versions of local Go+ and LLGo):
+
+```go
+module YourModulePath
+
+go 1.21 // llgo 1.0
+
+require github.com/goplus/llgo v0.9.1
+```
+
+Based on LLGo, Go+ can support importing libraries written in C/C++ and Python.
+
+Here is an example (see [chello](demo/_llgo/chello/hello.gop)) of printing `Hello world` using C's `printf`:
+
+```go
+import "c"
+
+c.printf c"Hello world\n"
+```
+
+Here, `c"Hello world\n"` is a syntax supported by Go+, representing a null-terminated C-style string.
+
+To run this example, you can:
+
+```sh
+cd YourModulePath  # set work directory to your module
+gop mod tidy       # for generating go.sum file
+gop run .
+```
+
+And here is an example (see [pyhello](demo/_llgo/pyhello/hello.gop)) of printing `Hello world` using Python's `print`:
+
+```go
+import "py/std"
+
+std.print py"Hello world"
+```
+
+Here, `py"Hello world"` is a syntax supported by Go+, representing a Python string.
+
+Here are more examples of Go+ calling C/C++ and Python libraries:
+
+* [pytensor](demo/_llgo/pytensor/tensor.gop): a simple demo using [py/torch](https://pkg.go.dev/github.com/goplus/llgo/py/torch)
+* [tetris](demo/_llgo/tetris/tetris.gop): a tetris game based on [c/raylib](https://pkg.go.dev/github.com/goplus/llgo/c/raylib)
+* [sqlitedemo](demo/_llgo/sqlitedemo/sqlitedemo.gop): a demo using [c/sqlite](https://pkg.go.dev/github.com/goplus/llgo/c/sqlite)
+
+To find out more about LLGo/Go+'s support for C/C++ and Python in detail, please refer to homepage of [llgo](https://github.com/goplus/llgo).
+
+
 ## Go+ Classfiles
 
 ```
@@ -189,20 +270,10 @@ Don't need a `go.mod` file, just enter `gop run ./example.gsh` directly to run.
 See [gsh: Go+ DevOps Tools](https://github.com/qiniu/x/tree/main/gsh) for more details.
 
 
-## Key Features of Go+
-
-* A static typed language.
-* The simplest engineering language that can be mastered by children (script-like style).
-* Performance: as fast as Go (Go+'s main backend compiles to human-readable Go).
-* Fully compatible with [Go](https://github.com/golang/go) and can mix Go/Go+ code in the same package (see [Go/Go+ hybrid programming](doc/docs.md#gogo-hybrid-programming)).
-* No DSL (Domain Specific Language) support, but SDF ([Specific Domain Friendliness](doc/classfile.md)).
-* Support Go code generation (main backend) and [bytecode backend](https://github.com/goplus/igop) (REPL: see [iGo+](https://repl.goplus.org/)).
-* [Simplest way to interaction with C](doc/docs.md#calling-c-from-go) (cgo is supported but not recommended).
-* [Powerful built-in data processing capabilities](doc/docs.md#data-processing).
-
-
 ## How to install
 
+Note: Requires go1.19 or later
+
 ### on Windows
 
 ```sh
@@ -234,8 +305,6 @@ $ brew install goplus
 
 ### from source code
 
-Note: Requires go1.18 or later
-
 ```bash
 git clone https://github.com/goplus/gop.git
 cd gop
diff --git a/VERSION b/VERSION
deleted file mode 100644
index a6c5252c..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.2.6
diff --git a/ast/ast.go b/ast/ast.go
index 9f3753a5..1a87ed9b 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1156,8 +1156,12 @@ func (f *File) Pos() token.Pos {
 
 // End returns position of first character immediately after the node.
 func (f *File) End() token.Pos {
-	if n := len(f.Decls); n > 0 {
-		return f.Decls[n-1].End()
+	for n := len(f.Decls) - 1; n >= 0; n-- {
+		d := f.Decls[n]
+		if fn, ok := d.(*FuncDecl); ok && fn.Shadow && fn != f.ShadowEntry {
+			continue
+		}
+		return d.End()
 	}
 	return f.Name.End()
 }
diff --git a/cl/c.go b/cl/c.go
index ef5b8bff..d086d6f1 100644
--- a/cl/c.go
+++ b/cl/c.go
@@ -18,54 +18,41 @@ package cl
 
 import (
 	"strings"
-
-	"github.com/goplus/gogen"
-	"github.com/goplus/gop/ast"
 )
 
+// -----------------------------------------------------------------------------
+
 const (
-	c2goInvalid = iota
-	c2goStandard
-	c2goUserDef
+	pathLibc   = "github.com/goplus/llgo/c"
+	pathLibpy  = "github.com/goplus/llgo/py"
+	pathLibcpp = "github.com/goplus/llgo/cpp"
 )
 
-func checkC2go(pkgPath string) (realPkgPath string, kind int) {
-	if strings.HasPrefix(pkgPath, "C") {
-		if len(pkgPath) == 1 {
-			return "libc", c2goStandard
-		}
-		if pkgPath[1] == '/' {
-			realPkgPath = pkgPath[2:]
-			if strings.IndexByte(realPkgPath, '/') < 0 {
-				kind = c2goStandard
-			} else {
-				kind = c2goUserDef
-			}
-		}
+func simplifyGopPackage(pkgPath string) string {
+	if strings.HasPrefix(pkgPath, "gop/") {
+		return "github.com/goplus/" + pkgPath
 	}
-	return
+	return pkgPath
 }
 
-func c2goBase(base string) string {
-	if base == "" {
-		base = "github.com/goplus/"
-	} else if !strings.HasSuffix(base, "/") {
-		base += "/"
-	}
-	return base
-}
-
-// -----------------------------------------------------------------------------
-
-func loadC2goPkg(ctx *blockCtx, realPath string, src *ast.BasicLit) (ret gogen.PkgRef) {
-	cpkg, err := ctx.cpkgs.Import(realPath)
-	if err != nil {
-		ctx.handleErrorf(src.Pos(),
-			"%v not found or not a valid C package (c2go.a.pub file not found).\n", realPath)
-		return
+func simplifyPkgPath(pkgPath string) string {
+	switch pkgPath {
+	case "c":
+		return pathLibc
+	case "py":
+		return pathLibpy
+	default:
+		if strings.HasPrefix(pkgPath, "c/") {
+			return pathLibc + pkgPath[1:]
+		}
+		if strings.HasPrefix(pkgPath, "py/") {
+			return pathLibpy + pkgPath[2:]
+		}
+		if strings.HasPrefix(pkgPath, "cpp/") {
+			return pathLibcpp + pkgPath[3:]
+		}
+		return simplifyGopPackage(pkgPath)
 	}
-	ctx.clookups = append(ctx.clookups, cpkg)
-	return cpkg.Pkg()
 }
 
 // -----------------------------------------------------------------------------
diff --git a/cl/classfile.go b/cl/classfile.go
index 06572f0c..0356b363 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -97,11 +97,10 @@ func ClassNameAndExt(file string) (name, clsfile, ext string) {
 }
 
 // GetFileClassType get ast.File classType
-func GetFileClassType(file *ast.File, filename string, lookupClass func(ext string) (c *Project, ok bool)) (classType string, isTest bool, ok bool) {
+func GetFileClassType(file *ast.File, filename string, lookupClass func(ext string) (c *Project, ok bool)) (classType string, isTest bool) {
 	if file.IsClass {
 		var ext string
 		classType, _, ext = ClassNameAndExt(filename)
-		ok = true
 		if file.IsNormalGox {
 			isTest = strings.HasSuffix(ext, "_test.gox")
 			if !isTest && classType == "main" {
@@ -110,7 +109,7 @@ func GetFileClassType(file *ast.File, filename string, lookupClass func(ext stri
 		} else {
 			isTest = strings.HasSuffix(ext, "test.gox")
 		}
-		if file.IsProj {
+		if file.IsProj && classType == "main" {
 			if gt, ok := lookupClass(ext); ok {
 				classType = gt.Class
 			}
diff --git a/cl/compile.go b/cl/compile.go
index 8e11e163..37798899 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -27,7 +27,6 @@ import (
 	"strings"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gogen/cpackages"
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/ast/fromgo"
 	"github.com/goplus/gop/token"
@@ -127,6 +126,7 @@ type Recorder interface {
 	//     *ast.ImportSpec    *PkgName for imports without renames
 	//     *ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
 	//     *ast.Field         anonymous parameter *Var (incl. unnamed results)
+	//     *ast.FunLit        function literal in *ast.OverloadFuncDecl
 	//
 	Implicit(node ast.Node, obj types.Object)
 
@@ -181,14 +181,6 @@ type Config struct {
 	// RelativeBase is the root directory of relative path.
 	RelativeBase string
 
-	// C2goBase specifies base of standard c2go packages (optional).
-	// Default is github.com/goplus/.
-	C2goBase string
-
-	// LookupPub lookups the c2go package pubfile named c2go.a.pub (required).
-	// See gop/x/c2go.LookupPub.
-	LookupPub func(pkgPath string) (pubfile string, err error)
-
 	// LookupClass lookups a class by specified file extension (required).
 	// See (*github.com/goplus/mod/gopmod.Module).LookupClass.
 	LookupClass func(ext string) (c *Project, ok bool)
@@ -339,8 +331,8 @@ type pkgCtx struct {
 	*nodeInterp
 	projs    map[string]*gmxProject // .gmx => project
 	classes  map[*ast.File]*gmxClass
+	overpos  map[string]token.Pos // overload => pos
 	fset     *token.FileSet
-	cpkgs    *cpackages.Importer
 	syms     map[string]loader
 	lbinames []any // names that should load before initGopPkg (can be string/func or *ast.Ident/type)
 	inits    []func()
@@ -365,9 +357,9 @@ type blockCtx struct {
 	imports    map[string]pkgImp
 	autoimps   map[string]pkgImp
 	lookups    []gogen.PkgRef
-	clookups   []cpackages.PkgRef
 	tlookup    *typeParamLookup
-	c2goBase   string // default is `github.com/goplus/`
+	cstr_      gogen.Ref
+	pystr_     gogen.Ref
 	relBaseDir string
 
 	classRecv *ast.FieldList // available when isClass
@@ -381,6 +373,20 @@ type blockCtx struct {
 	isGopFile bool // is Go+ file or not
 }
 
+func (p *blockCtx) cstr() gogen.Ref {
+	if p.cstr_ == nil {
+		p.cstr_ = p.pkg.Import(pathLibc).Ref("Str")
+	}
+	return p.cstr_
+}
+
+func (p *blockCtx) pystr() gogen.Ref {
+	if p.pystr_ == nil {
+		p.pystr_ = p.pkg.Import(pathLibpy).Ref("Str")
+	}
+	return p.pystr_
+}
+
 func (p *blockCtx) recorder() *goxRecorder {
 	if p.isGopFile {
 		return p.rec
@@ -492,6 +498,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		nodeInterp: interp,
 		projs:      make(map[string]*gmxProject),
 		classes:    make(map[*ast.File]*gmxClass),
+		overpos:    make(map[string]token.Pos),
 		syms:       make(map[string]loader),
 		generics:   make(map[string]bool),
 	}
@@ -530,10 +537,6 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		p.CB().NewConstStart(nil, "_").Val(true).EndInit(1)
 	}
 
-	ctx.cpkgs = cpackages.NewImporter(&cpackages.Config{
-		Pkg: p, LookupPub: conf.LookupPub,
-	})
-
 	// sort files
 	type File struct {
 		*ast.File
@@ -562,8 +565,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), f.path)
 		ctx := &blockCtx{
 			pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir, fileScope: fileScope,
-			fileLine: fileLine, isClass: f.IsClass, rec: rec,
-			c2goBase: c2goBase(conf.C2goBase), imports: make(map[string]pkgImp), isGopFile: true,
+			fileLine: fileLine, isClass: f.IsClass, rec: rec, imports: make(map[string]pkgImp), isGopFile: true,
 		}
 		if rec := ctx.rec; rec != nil {
 			rec.Scope(f.File, fileScope)
@@ -662,7 +664,7 @@ func initGopPkg(ctx *pkgCtx, pkg *gogen.Package, gopSyms map[string]bool) {
 			ctx.loadType(lbi.(*ast.Ident).Name)
 		}
 	}
-	gogen.InitThisGopPkg(pkg.Types)
+	gogen.InitThisGopPkgEx(pkg.Types, ctx.overpos)
 }
 
 func loadFile(ctx *pkgCtx, f *ast.File) {
@@ -1077,56 +1079,107 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 
 		case *ast.OverloadFuncDecl:
 			var recv *ast.Ident
+			if ctx.classRecv != nil { // in class file (.spx/.gmx)
+				if recv := d.Recv; recv == nil || len(recv.List) == 0 {
+					d.Recv = &ast.FieldList{
+						List: []*ast.Field{
+							{
+								Type: ctx.classRecv.List[0].Type.(*ast.StarExpr).X,
+							},
+						},
+					}
+					d.IsClass = true
+				}
+			}
 			if d.Recv != nil {
-				otyp, ok := d.Recv.List[0].Type.(*ast.Ident)
+				var ok bool
+				recv, ok = d.Recv.List[0].Type.(*ast.Ident)
 				if !ok {
-					log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: invalid recv")
+					ctx.handleErrorf(d.Recv.List[0].Type.Pos(), "invalid recv type %v", ctx.LoadExpr(d.Recv.List[0].Type))
+					break
 				}
-				recv = otyp
+				ctx.lbinames = append(ctx.lbinames, recv)
 				if ctx.rec != nil {
-					ctx.rec.Refer(recv, recv.Name)
+					ctx.rec.ReferUse(recv, recv.Name)
 				}
 			}
 			onames := make([]string, 0, 4)
 			exov := false
 			name := d.Name
+		LoopFunc:
+
 			for idx, fn := range d.Funcs {
 				switch expr := fn.(type) {
 				case *ast.Ident:
-					checkOverloadFunc(d)
-					onames = append(onames, expr.Name)
-					ctx.lbinames = append(ctx.lbinames, expr.Name)
+					if d.Recv != nil && !d.Operator && !d.IsClass {
+						ctx.handleErrorf(expr.Pos(), "invalid method %v", ctx.LoadExpr(expr))
+						break LoopFunc
+					}
 					exov = true
+					if d.IsClass {
+						onames = append(onames, "."+expr.Name)
+					} else {
+						onames = append(onames, expr.Name)
+						ctx.lbinames = append(ctx.lbinames, expr.Name)
+					}
 					if ctx.rec != nil {
-						ctx.rec.Refer(expr, expr.Name)
+						if d.IsClass {
+							ctx.rec.ReferUse(expr, recv.Name+"."+expr.Name)
+						} else {
+							ctx.rec.ReferUse(expr, expr.Name)
+						}
 					}
 				case *ast.SelectorExpr:
-					checkOverloadMethod(d)
-					rtyp := checkOverloadMethodRecvType(recv, expr.X)
+					if d.Recv == nil || d.IsClass {
+						ctx.handleErrorf(expr.Pos(), "invalid func %v", ctx.LoadExpr(expr))
+						break LoopFunc
+					}
+					rtyp, ok := checkOverloadMethodRecvType(recv, expr.X)
+					if !ok {
+						ctx.handleErrorf(expr.Pos(), "invalid recv type %v", ctx.LoadExpr(expr.X))
+						break LoopFunc
+					}
+
 					onames = append(onames, "."+expr.Sel.Name)
-					ctx.lbinames = append(ctx.lbinames, recv)
 					exov = true
 					if ctx.rec != nil {
-						ctx.rec.Refer(rtyp, rtyp.Name)
-						ctx.rec.Refer(expr.Sel, rtyp.Name+"."+expr.Sel.Name)
+						ctx.rec.ReferUse(rtyp, rtyp.Name)
+						ctx.rec.ReferUse(expr.Sel, rtyp.Name+"."+expr.Sel.Name)
 					}
 				case *ast.FuncLit:
-					checkOverloadFunc(d)
+					if d.Recv != nil && !d.Operator && !d.IsClass {
+						ctx.handleErrorf(expr.Pos(), "invalid method %v", ctx.LoadExpr(expr))
+						break LoopFunc
+					}
 					name1 := overloadFuncName(name.Name, idx)
 					onames = append(onames, "") // const Gopo_xxx = "xxxInt,,xxxFloat"
 					ctx.lbinames = append(ctx.lbinames, name1)
+					id := &ast.Ident{NamePos: expr.Pos(), Name: name1}
+					if ctx.rec != nil {
+						ctx.rec.ReferDef(id, expr)
+					}
 					preloadFuncDecl(&ast.FuncDecl{
 						Doc:  d.Doc,
-						Name: &ast.Ident{NamePos: expr.Pos(), Name: name1},
+						Name: id,
 						Type: expr.Type,
 						Body: expr.Body,
 					})
 				default:
-					log.Panicf("TODO - cl.preloadFile OverloadFuncDecl: unknown func - %T\n", expr)
+					ctx.handleErrorf(expr.Pos(), "unknown func %v", ctx.LoadExpr(expr))
+					break LoopFunc
 				}
 			}
 			if exov { // need Gopo_xxx
-				oname := overloadName(recv, name.Name, d.Operator)
+				oname, err := overloadName(recv, name.Name, d.Operator)
+				if err != nil {
+					ctx.handleErrorf(name.NamePos, "%v", err)
+					break
+				}
+				if recv != nil {
+					ctx.overpos[recv.Name+"."+name.Name] = name.NamePos
+				} else {
+					ctx.overpos[name.Name] = name.NamePos
+				}
 				oval := strings.Join(onames, ",")
 				preloadConst(&ast.GenDecl{
 					Doc: d.Doc,
@@ -1139,6 +1192,11 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 					},
 				})
 				ctx.lbinames = append(ctx.lbinames, oname)
+			} else {
+				ctx.overpos[name.Name] = name.NamePos
+			}
+			if ctx.rec != nil {
+				ctx.rec.ReferDef(d.Name, d)
 			}
 
 		default:
@@ -1147,25 +1205,16 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 	}
 }
 
-func checkOverloadFunc(d *ast.OverloadFuncDecl) {
-	if d.Recv != nil && !d.Operator {
-		log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc")
-	}
-}
-
-func checkOverloadMethod(d *ast.OverloadFuncDecl) {
-	if d.Recv == nil {
-		log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod")
+func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) (*ast.Ident, bool) {
+	rtyp, _, ok := getRecvType(recv)
+	if !ok {
+		return nil, false
 	}
-}
-
-func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) *ast.Ident {
-	rtyp, _ := getRecvType(recv)
 	rt, ok := rtyp.(*ast.Ident)
 	if !ok || ot.Name != rt.Name {
-		log.Panicln("TODO - checkOverloadMethodRecvType:", recv)
+		return nil, false
 	}
-	return rt
+	return rt, true
 }
 
 const (
@@ -1176,12 +1225,12 @@ func overloadFuncName(name string, idx int) string {
 	return name + "__" + indexTable[idx:idx+1]
 }
 
-func overloadName(recv *ast.Ident, name string, isOp bool) string {
+func overloadName(recv *ast.Ident, name string, isOp bool) (string, error) {
 	if isOp {
 		if oname, ok := binaryGopNames[name]; ok {
 			name = oname
 		} else {
-			log.Panicln("TODO - can't overload operator", name)
+			return "", fmt.Errorf("invalid overload operator %v", name)
 		}
 	}
 	sep := "_"
@@ -1192,7 +1241,7 @@ func overloadName(recv *ast.Ident, name string, isOp bool) string {
 	if recv != nil {
 		typ = recv.Name + sep
 	}
-	return "Gopo" + sep + typ + name
+	return "Gopo" + sep + typ + name, nil
 }
 
 func staticMethod(tname, name string) string {
@@ -1365,13 +1414,6 @@ func loadFuncBody(ctx *blockCtx, fn *gogen.Func, body *ast.BlockStmt, sigBase *t
 	cb.End(src)
 }
 
-func simplifyGopPackage(pkgPath string) string {
-	if strings.HasPrefix(pkgPath, "gop/") {
-		return "github.com/goplus/" + pkgPath
-	}
-	return pkgPath
-}
-
 func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 	if enableRecover {
 		defer func() {
@@ -1380,18 +1422,8 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 			}
 		}()
 	}
-	var pkg gogen.PkgRef
-	var pkgPath = toString(spec.Path)
-	if realPath, kind := checkC2go(pkgPath); kind != c2goInvalid {
-		if kind == c2goStandard {
-			realPath = ctx.c2goBase + realPath
-		}
-		if pkg = loadC2goPkg(ctx, realPath, spec.Path); pkg.Types == nil {
-			return
-		}
-	} else {
-		pkg = ctx.pkg.Import(simplifyGopPackage(pkgPath), spec)
-	}
+	pkgPath := simplifyPkgPath(toString(spec.Path))
+	pkg := ctx.pkg.Import(pkgPath, spec)
 
 	var pos token.Pos
 	var name string
diff --git a/cl/internal/libc/libc.go b/cl/compile_testdir_test.go
similarity index 63%
rename from cl/internal/libc/libc.go
rename to cl/compile_testdir_test.go
index 4ff8dbc3..122cf720 100644
--- a/cl/internal/libc/libc.go
+++ b/cl/compile_testdir_test.go
@@ -14,28 +14,18 @@
  * limitations under the License.
  */
 
-package libc
+package cl_test
 
 import (
-	"fmt"
-	"unsafe"
+	"testing"
+
+	"github.com/goplus/gop/cl/cltest"
 )
 
-func gostring(s *int8) string {
-	n, arr := 0, (*[1 << 20]byte)(unsafe.Pointer(s))
-	for arr[n] != 0 {
-		n++
-	}
-	return string(arr[:n])
+func TestTestc(t *testing.T) {
+	cltest.FromDir(t, "", "./_testc")
 }
 
-func Printf(format *int8, args ...interface{}) int32 {
-	goformat := gostring(format)
-	for i, arg := range args {
-		if v, ok := arg.(*int8); ok {
-			args[i] = gostring(v)
-		}
-	}
-	fmt.Printf(goformat, args...)
-	return 0
+func TestTestpy(t *testing.T) {
+	cltest.FromDir(t, "", "./_testpy")
 }
diff --git a/cl/expr.go b/cl/expr.go
index aaf7eb13..1337ad31 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -29,7 +29,6 @@ import (
 	"syscall"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gogen/cpackages"
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/printer"
 	"github.com/goplus/gop/token"
@@ -72,7 +71,6 @@ const (
 const (
 	objNormal = iota
 	objPkgRef
-	objCPkgRef
 	objGopExecOrEnv
 
 	objGopEnv  = objGopExecOrEnv
@@ -125,10 +123,6 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg gogen.PkgRef,
 
 	// pkgRef object
 	if (flags & clIdentSelectorExpr) != 0 {
-		if name == "C" && len(ctx.clookups) > 0 {
-			kind = objCPkgRef
-			return
-		}
 		if pi, ok := ctx.findImport(name); ok {
 			if rec := ctx.recorder(); rec != nil {
 				rec.Use(ident, pi.pkgName)
@@ -545,18 +539,6 @@ func lookupPkgRef(ctx *blockCtx, pkg gogen.PkgRef, x *ast.Ident, pkgKind int) (o
 				pkg, o, alias = at, o2, alias2
 			}
 		}
-	} else {
-		var cpkg cpackages.PkgRef
-		for _, at := range ctx.clookups {
-			if o2 := at.Lookup(x.Name); o2 != nil {
-				if o != nil {
-					panic(ctx.newCodeErrorf(
-						x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
-						x.Name, at.Pkg().Types.Path(), cpkg.Pkg().Types.Path()))
-				}
-				cpkg, o = at, o2
-			}
-		}
 	}
 	return
 }
@@ -988,24 +970,24 @@ func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) {
 
 func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) {
 	cb := ctx.cb
-	switch v.Kind {
+	switch kind := v.Kind; kind {
 	case token.RAT:
 		val := v.Value
 		bi, _ := new(big.Int).SetString(val[:len(val)-1], 10) // remove r suffix
 		cb.UntypedBigInt(bi, v)
-	case token.CSTRING:
+	case token.CSTRING, token.PYSTRING:
 		s, err := strconv.Unquote(v.Value)
 		if err != nil {
 			log.Panicln("compileBasicLit:", err)
 		}
-		n := len(s)
-		tyInt8 := types.Typ[types.Int8]
-		typ := types.NewArray(tyInt8, int64(n+1))
-		cb.Typ(types.NewPointer(tyInt8)).Typ(types.Typ[types.UnsafePointer])
-		for i := 0; i < n; i++ {
-			cb.Val(rune(s[i]))
+		var xstr gogen.Ref
+		switch kind {
+		case token.CSTRING:
+			xstr = ctx.cstr()
+		default:
+			xstr = ctx.pystr()
 		}
-		cb.Val(rune(0)).ArrayLit(typ, n+1).UnaryOp(gotoken.AND).Call(1).Call(1)
+		cb.Val(xstr).Val(s).Call(1)
 	default:
 		if v.Extra == nil {
 			basicLit(cb, v)
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 31b2b357..637359b6 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -36,7 +36,7 @@ func toRecv(ctx *blockCtx, recv *ast.FieldList) *types.Var {
 	if len(v.Names) > 0 {
 		name = v.Names[0].Name
 	}
-	typ, star := getRecvType(v.Type)
+	typ, star, _ := getRecvType(v.Type)
 	id, ok := typ.(*ast.Ident)
 	if !ok {
 		panic("TODO: getRecvType")
@@ -56,7 +56,7 @@ func toRecv(ctx *blockCtx, recv *ast.FieldList) *types.Var {
 }
 
 func getRecvTypeName(ctx *pkgCtx, recv *ast.FieldList, handleErr bool) (string, bool) {
-	typ, _ := getRecvType(recv.List[0].Type)
+	typ, _, _ := getRecvType(recv.List[0].Type)
 	if t, ok := typ.(*ast.Ident); ok {
 		return t.Name, true
 	}
diff --git a/cl/internal/libc/c2go.a.pub b/cl/internal/libc/c2go.a.pub
deleted file mode 100644
index f3be370b..00000000
--- a/cl/internal/libc/c2go.a.pub
+++ /dev/null
@@ -1,1 +0,0 @@
-printf
diff --git a/cl/internal/llgo-hello/hello.go b/cl/internal/llgo-hello/hello.go
new file mode 100644
index 00000000..d1da2e92
--- /dev/null
+++ b/cl/internal/llgo-hello/hello.go
@@ -0,0 +1,7 @@
+package hello
+
+import "github.com/goplus/llgo/c"
+
+func Main() {
+	c.Printf(c.Str("Hello world\n"))
+}
diff --git a/cl/outline/outline.go b/cl/outline/outline.go
index 1ed4d215..0b4efd9c 100644
--- a/cl/outline/outline.go
+++ b/cl/outline/outline.go
@@ -37,13 +37,6 @@ type Config struct {
 	// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
 	Fset *token.FileSet
 
-	// C2goBase specifies base of standard c2go packages.
-	// Default is github.com/goplus/.
-	C2goBase string
-
-	// LookupPub lookups the c2go package pubfile (named c2go.a.pub).
-	LookupPub func(pkgPath string) (pubfile string, err error)
-
 	// LookupClass lookups a class by specified file extension.
 	LookupClass func(ext string) (c *Project, ok bool)
 
@@ -60,8 +53,6 @@ type Package struct {
 func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (_ Package, err error) {
 	ret, err := cl.NewPackage(pkgPath, pkg, &cl.Config{
 		Fset:           conf.Fset,
-		C2goBase:       conf.C2goBase,
-		LookupPub:      conf.LookupPub,
 		LookupClass:    conf.LookupClass,
 		Importer:       conf.Importer,
 		NoFileLine:     true,
diff --git a/cl/recorder.go b/cl/recorder.go
index 4ac4c627..a69f21bf 100644
--- a/cl/recorder.go
+++ b/cl/recorder.go
@@ -29,24 +29,58 @@ import (
 
 type goxRecorder struct {
 	Recorder
-	types  map[ast.Expr]types.TypeAndValue
-	refers map[string][]*ast.Ident
+	types     map[ast.Expr]types.TypeAndValue
+	referDefs map[*ast.Ident]ast.Node
+	referUses map[string][]*ast.Ident
 }
 
 func newRecorder(rec Recorder) *goxRecorder {
 	types := make(map[ast.Expr]types.TypeAndValue)
-	refers := make(map[string][]*ast.Ident)
-	return &goxRecorder{rec, types, refers}
+	referDefs := make(map[*ast.Ident]ast.Node)
+	referUses := make(map[string][]*ast.Ident)
+	return &goxRecorder{rec, types, referDefs, referUses}
 }
 
-// Refer maps identifiers to name for ast.OverloadFuncDecl.
-func (p *goxRecorder) Refer(ident *ast.Ident, name string) {
-	p.refers[name] = append(p.refers[name], ident)
+// Refer uses maps identifiers to name for ast.OverloadFuncDecl.
+func (p *goxRecorder) ReferUse(ident *ast.Ident, name string) {
+	p.referUses[name] = append(p.referUses[name], ident)
+}
+
+// Refer def maps for ast.FuncLit or ast.OverloadFuncDecl.
+func (p *goxRecorder) ReferDef(ident *ast.Ident, node ast.Node) {
+	p.referDefs[ident] = node
 }
 
 // Complete computes the types record.
 func (p *goxRecorder) Complete(scope *types.Scope) {
-	for name, idents := range p.refers {
+	for id, node := range p.referDefs {
+		switch fn := node.(type) {
+		case *ast.FuncLit:
+			if obj := scope.Lookup(id.Name); obj != nil {
+				p.recordFuncLit(fn, obj.Type())
+				p.Implicit(node, obj)
+			}
+		case *ast.OverloadFuncDecl:
+			if fn.Recv == nil {
+				if obj := scope.Lookup(id.Name); obj != nil {
+					p.Def(id, obj)
+				}
+			} else {
+				if obj := scope.Lookup(fn.Recv.List[0].Type.(*ast.Ident).Name); obj != nil {
+					if named, ok := obj.Type().(*types.Named); ok {
+						n := named.NumMethods()
+						for i := 0; i < n; i++ {
+							if m := named.Method(i); m.Name() == id.Name {
+								p.Def(id, m)
+								break
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	for name, idents := range p.referUses {
 		pos := strings.Index(name, ".")
 		if pos == -1 {
 			if obj := scope.Lookup(name); obj != nil {
@@ -71,7 +105,8 @@ func (p *goxRecorder) Complete(scope *types.Scope) {
 		}
 	}
 	p.types = nil
-	p.refers = nil
+	p.referDefs = nil
+	p.referUses = nil
 }
 
 // Member maps identifiers to the objects they denote.
diff --git a/cl/typeparams.go b/cl/typeparams.go
index aeb82608..95a3df90 100644
--- a/cl/typeparams.go
+++ b/cl/typeparams.go
@@ -161,8 +161,8 @@ func initType(ctx *blockCtx, named *types.Named, spec *ast.TypeSpec) {
 	named.SetUnderlying(typ)
 }
 
-func getRecvType(typ ast.Expr) (ast.Expr, bool) {
-	var ptr bool
+func getRecvType(expr ast.Expr) (typ ast.Expr, ptr bool, ok bool) {
+	typ = expr
 L:
 	for {
 		switch t := typ.(type) {
@@ -170,7 +170,8 @@ L:
 			typ = t.X
 		case *ast.StarExpr:
 			if ptr {
-				panic("TODO: getRecvType")
+				ok = false
+				return
 			}
 			ptr = true
 			typ = t.X
@@ -184,7 +185,8 @@ L:
 	case *ast.IndexListExpr:
 		typ = t.X
 	}
-	return typ, ptr
+	ok = true
+	return
 }
 
 func collectTypeParams(ctx *blockCtx, list *ast.FieldList) []*types.TypeParam {
diff --git a/cmd/gopfmt/gopfmt.go b/cmd/chore/gopfmt/gopfmt.go
similarity index 100%
rename from cmd/gopfmt/gopfmt.go
rename to cmd/chore/gopfmt/gopfmt.go
diff --git a/cmd/goptestgo/goptestgo.go b/cmd/chore/goptestgo/goptestgo.go
similarity index 100%
rename from cmd/goptestgo/goptestgo.go
rename to cmd/chore/goptestgo/goptestgo.go
diff --git a/cmd/gop/main.go b/cmd/gop/main.go
index aa552ce3..af8b98fb 100644
--- a/cmd/gop/main.go
+++ b/cmd/gop/main.go
@@ -28,7 +28,6 @@ import (
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/cmd/internal/bug"
 	"github.com/goplus/gop/cmd/internal/build"
-	"github.com/goplus/gop/cmd/internal/c2go"
 	"github.com/goplus/gop/cmd/internal/clean"
 	"github.com/goplus/gop/cmd/internal/doc"
 	"github.com/goplus/gop/cmd/internal/env"
@@ -68,7 +67,6 @@ func init() {
 		serve.Cmd,
 		watch.Cmd,
 		env.Cmd,
-		c2go.Cmd,
 		bug.Cmd,
 		version.Cmd,
 	}
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index 5fe447fb..db10d6e5 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -80,7 +80,7 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 	defer conf.UpdateCache()
 
-	confCmd := &gocmd.BuildConfig{Gop: conf.Gop}
+	confCmd := conf.NewGoCmdConf()
 	if *flagOutput != "" {
 		output, err := filepath.Abs(*flagOutput)
 		if err != nil {
diff --git a/cmd/internal/c2go/c2go.go b/cmd/internal/c2go/c2go.go
deleted file mode 100644
index f4d388e2..00000000
--- a/cmd/internal/c2go/c2go.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package c2go
-
-import (
-	"os"
-
-	"github.com/goplus/gop/cmd/internal/base"
-
-	c2go "github.com/goplus/c2go/cmd/c2go/impl"
-)
-
-// gop c2go
-var Cmd = &base.Command{
-	UsageLine: "gop c" + c2go.ShortUsage[4:],
-	Short:     "Run c2go (convert C to Go) tools",
-}
-
-func init() {
-	Cmd.Flag.Usage = func() {
-		Cmd.Usage(os.Stderr)
-	}
-	Cmd.Run = runCmd
-}
-
-func runCmd(cmd *base.Command, args []string) {
-	c2go.Main(&Cmd.Flag, args)
-}
diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go
index b343885a..09166c84 100644
--- a/cmd/internal/install/install.go
+++ b/cmd/internal/install/install.go
@@ -77,7 +77,7 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 	defer conf.UpdateCache()
 
-	confCmd := &gocmd.Config{Gop: conf.Gop}
+	confCmd := conf.NewGoCmdConf()
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
 		install(proj, conf, confCmd)
diff --git a/cmd/internal/mod/init.go b/cmd/internal/mod/init.go
index e134b66c..33887e0c 100644
--- a/cmd/internal/mod/init.go
+++ b/cmd/internal/mod/init.go
@@ -17,25 +17,39 @@
 package mod
 
 import (
+	"log"
+	"path/filepath"
 	"runtime"
 	"strings"
 
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/env"
+	"github.com/goplus/mod/gopmod"
 	"github.com/goplus/mod/modload"
 )
 
 // gop mod init
 var cmdInit = &base.Command{
-	UsageLine: "gop mod init [module]",
+	UsageLine: "gop mod init [-llgo -tinygo] module-path",
 	Short:     "initialize new module in current directory",
 }
 
+var (
+	flagInit   = &cmdInit.Flag
+	flagLLGo   = flagInit.Bool("llgo", false, "use llgo as the compiler")
+	flagTinyGo = flagInit.Bool("tinygo", false, "use tinygo as the compiler")
+)
+
 func init() {
 	cmdInit.Run = runInit
 }
 
 func runInit(cmd *base.Command, args []string) {
+	err := flagInit.Parse(args)
+	if err != nil {
+		log.Fatalln("parse input arguments failed:", err)
+	}
+	args = flagInit.Args()
 	switch len(args) {
 	case 0:
 		fatal(`Example usage:
@@ -52,6 +66,13 @@ Run 'gop help mod init' for more information.`)
 	mod, err := modload.Create(".", modPath, goMainVer(), env.MainVersion)
 	check(err)
 
+	if *flagLLGo {
+		mod.AddCompiler("llgo", "1.0")
+		mod.AddRequire("github.com/goplus/llgo", llgoVer(), false)
+	} else if *flagTinyGo {
+		mod.AddCompiler("tinygo", "0.32")
+	}
+
 	err = mod.Save()
 	check(err)
 }
@@ -66,3 +87,12 @@ func goMainVer() string {
 	}
 	return ver
 }
+
+func llgoVer() string {
+	if modGop, e1 := gopmod.LoadFrom(filepath.Join(env.GOPROOT(), "go.mod"), ""); e1 == nil {
+		if pkg, e2 := modGop.Lookup("github.com/goplus/llgo"); e2 == nil {
+			return pkg.Real.Version
+		}
+	}
+	return "v0.9.0"
+}
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index 9bcb3925..96cf53aa 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -89,7 +89,7 @@ func runCmd(cmd *base.Command, args []string) {
 	if !conf.Mod.HasModfile() { // if no go.mod, check GopDeps
 		conf.GopDeps = new(int)
 	}
-	confCmd := &gocmd.Config{Gop: conf.Gop}
+	confCmd := conf.NewGoCmdConf()
 	confCmd.Flags = pass.Args
 	run(proj, args, !noChdir, conf, confCmd)
 }
diff --git a/cmd/make.go b/cmd/make.go
index 6ecf458a..866d4d4a 100644
--- a/cmd/make.go
+++ b/cmd/make.go
@@ -554,7 +554,7 @@ func runRegtests() {
 	cmd := exec.Command(filepath.Join(gopRoot, "bin/"+gopBinFiles[0]), "go", "./...")
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
-	cmd.Dir = filepath.Join(gopRoot, "testdata")
+	cmd.Dir = filepath.Join(gopRoot, "demo")
 	err := cmd.Run()
 	if err != nil {
 		code := cmd.ProcessState.ExitCode()
diff --git a/demo/_llgo/callpy/callpy.gop b/demo/_llgo/callpy/callpy.gop
new file mode 100644
index 00000000..b7759703
--- /dev/null
+++ b/demo/_llgo/callpy/callpy.gop
@@ -0,0 +1,8 @@
+import (
+	"c"
+	"py"
+	"py/math"
+)
+
+x := math.sqrt(py.float(2))
+c.printf c"sqrt(2) = %f\n", x.float64
diff --git a/demo/_llgo/chello/hello.gop b/demo/_llgo/chello/hello.gop
new file mode 100644
index 00000000..3ea999eb
--- /dev/null
+++ b/demo/_llgo/chello/hello.gop
@@ -0,0 +1,3 @@
+import "c"
+
+c.printf c"Hello world\n"
diff --git a/demo/_llgo/cpphello/cpphello.gop b/demo/_llgo/cpphello/cpphello.gop
new file mode 100644
index 00000000..49aaef78
--- /dev/null
+++ b/demo/_llgo/cpphello/cpphello.gop
@@ -0,0 +1,3 @@
+import "cpp/std"
+
+println std.str("Hello world").str
diff --git a/demo/_llgo/defer/defer.gop b/demo/_llgo/defer/defer.gop
new file mode 100644
index 00000000..f75767d1
--- /dev/null
+++ b/demo/_llgo/defer/defer.gop
@@ -0,0 +1,15 @@
+import "c"
+
+func f(s string) bool {
+	return len(s) > 2
+}
+
+defer func() {
+	c.printf c"hi\n"
+}()
+if s := "hello"; f(s) {
+	defer c.printf(c"%s\n", c.allocaCStr(s))
+} else {
+	defer c.printf(c"world\n")
+}
+defer c.printf(c"bye\n")
diff --git a/demo/_llgo/errors/errors.gop b/demo/_llgo/errors/errors.gop
new file mode 100644
index 00000000..10c26ba6
--- /dev/null
+++ b/demo/_llgo/errors/errors.gop
@@ -0,0 +1,21 @@
+import (
+	"c"
+)
+
+// New returns an error that formats as the given text.
+// Each call to New returns a distinct error value even if the text is identical.
+func New(text string) error {
+	return &errorString{text}
+}
+
+// errorString is a trivial implementation of error.
+type errorString struct {
+	s string
+}
+
+func (e *errorString) Error() string {
+	return e.s
+}
+
+e := new("an error")
+c.printf c.allocaCStr(e.error+"\n")
diff --git a/demo/_llgo/go.mod b/demo/_llgo/go.mod
new file mode 100644
index 00000000..6fe826d8
--- /dev/null
+++ b/demo/_llgo/go.mod
@@ -0,0 +1,5 @@
+module llgoexample
+
+go 1.18 // llgo 1.0
+
+require github.com/goplus/llgo v0.9.1
diff --git a/demo/_llgo/go.sum b/demo/_llgo/go.sum
new file mode 100644
index 00000000..4468d02d
--- /dev/null
+++ b/demo/_llgo/go.sum
@@ -0,0 +1,2 @@
+github.com/goplus/llgo v0.9.1 h1:f9JsHjWQK1pJKIZVd/O/p0p2g9HTp963NSB8s4DCcAI=
+github.com/goplus/llgo v0.9.1/go.mod h1:zsrtWZapL4aklZc99xBSZRynGzLTIT1mLRjP0VSn9iw=
diff --git a/demo/_llgo/goroutine/goroutine.gop b/demo/_llgo/goroutine/goroutine.gop
new file mode 100644
index 00000000..2c9633cc
--- /dev/null
+++ b/demo/_llgo/goroutine/goroutine.gop
@@ -0,0 +1,10 @@
+import "c"
+
+done := false
+go func() {
+	c.printf c"Hello, goroutine\n"
+	done = true
+}()
+for !done {
+	c.printf c"."
+}
diff --git a/demo/_llgo/hello/hello.gop b/demo/_llgo/hello/hello.gop
new file mode 100644
index 00000000..bba153ac
--- /dev/null
+++ b/demo/_llgo/hello/hello.gop
@@ -0,0 +1,1 @@
+echo "Hello world"
diff --git a/demo/_llgo/hellollgo/README.md b/demo/_llgo/hellollgo/README.md
new file mode 100644
index 00000000..14bd8316
--- /dev/null
+++ b/demo/_llgo/hellollgo/README.md
@@ -0,0 +1,36 @@
+This is an example to show how Go+ interacts with C.
+
+```go
+import "c"
+
+c.printf c"Hello, llgo!\n"
+c.fprintf c.Stderr, c"Hi, %6.1f\n", 3.14
+```
+
+Here we use `import "c"` to import libc. It's an abbreviation for `import "github.com/goplus/llgo/c"`. It is equivalent to the following code:
+
+```go
+import "github.com/goplus/llgo/c"
+
+c.printf c"Hello, llgo!\n"
+c.fprintf c.Stderr, c"Hi, %7.1f\n", 3.14
+```
+
+In this example we call two C standard functions `printf` and `fprintf`, pass a C variable `stderr` and two C strings in the form of `c"xxx"`.
+
+To run this demo, you need to set the `GOP_GOCMD` environment variable first.
+
+```sh
+export GOP_GOCMD=llgo  # default is `go`
+```
+
+Then execute `gop run .` to see the output of this example:
+
+```
+Hello, llgo!
+Hi,    3.1
+```
+
+### Give a Star! ⭐
+
+If you like or are using Go+ to learn or start your projects, please give it a star. Thanks!
diff --git a/demo/_llgo/hellollgo/hello.gop b/demo/_llgo/hellollgo/hello.gop
new file mode 100644
index 00000000..4cd048eb
--- /dev/null
+++ b/demo/_llgo/hellollgo/hello.gop
@@ -0,0 +1,4 @@
+import "c"
+
+c.printf c"Hello, llgo!\n"
+c.fprintf c.Stderr, c"Hi, %6.1f\n", 3.14
diff --git a/demo/_llgo/matrix/matrix.gop b/demo/_llgo/matrix/matrix.gop
new file mode 100644
index 00000000..ad4fb3fa
--- /dev/null
+++ b/demo/_llgo/matrix/matrix.gop
@@ -0,0 +1,18 @@
+import (
+	"c"
+	"py"
+	"py/numpy"
+)
+
+a := py.list(
+	py.list(1.0, 2.0, 3.0),
+	py.list(4.0, 5.0, 6.0),
+	py.list(7.0, 8.0, 9.0),
+)
+b := py.list(
+	py.list(9.0, 8.0, 7.0),
+	py.list(6.0, 5.0, 4.0),
+	py.list(3.0, 2.0, 1.0),
+)
+x := numpy.add(a, b)
+c.printf c"a+b = %s\n", x.str.cStr
diff --git a/demo/_llgo/pyhello/hello.gop b/demo/_llgo/pyhello/hello.gop
new file mode 100644
index 00000000..a30148a6
--- /dev/null
+++ b/demo/_llgo/pyhello/hello.gop
@@ -0,0 +1,3 @@
+import "py/std"
+
+std.print py"Hello world"
diff --git a/demo/_llgo/pymax/pymax.gop b/demo/_llgo/pymax/pymax.gop
new file mode 100644
index 00000000..df489487
--- /dev/null
+++ b/demo/_llgo/pymax/pymax.gop
@@ -0,0 +1,11 @@
+import (
+	"py"
+	"py/std"
+)
+
+x := std.max(py.float(3.0), py.float(9.0), py.float(23.0), py.float(100.0))
+std.print(x)
+
+list := py.list(3.0, 9.0, 23.0, 100.0)
+y := std.max(std.iter(list))
+std.print(y)
diff --git a/demo/_llgo/pyprint/print.gop b/demo/_llgo/pyprint/print.gop
new file mode 100644
index 00000000..56ba39e4
--- /dev/null
+++ b/demo/_llgo/pyprint/print.gop
@@ -0,0 +1,7 @@
+import (
+	"py"
+	"py/std"
+)
+
+x := py.float(3.14)
+std.print(x)
diff --git a/demo/_llgo/pytensor/tensor.gop b/demo/_llgo/pytensor/tensor.gop
new file mode 100644
index 00000000..da44beee
--- /dev/null
+++ b/demo/_llgo/pytensor/tensor.gop
@@ -0,0 +1,12 @@
+import (
+	"py"
+	"py/std"
+	"py/torch"
+)
+
+data := py.list(
+	py.list(1.0, 2.0),
+	py.list(3.0, 4.0),
+)
+x := torch.tensor(data)
+std.print(x)
diff --git a/demo/_llgo/qsort/qsort.gop b/demo/_llgo/qsort/qsort.gop
new file mode 100644
index 00000000..0203fd26
--- /dev/null
+++ b/demo/_llgo/qsort/qsort.gop
@@ -0,0 +1,12 @@
+import (
+	"c"
+	"unsafe"
+)
+
+a := [100, 8, 23, 2, 7]
+c.qsort unsafe.Pointer(&a[0]), 5, unsafe.Sizeof(0), (a, b) => {
+	return c.Int(*(*int)(a) - *(*int)(b))
+}
+for v <- a {
+	c.printf c"%d\n", v
+}
diff --git a/demo/_llgo/reflect/reflect.gop b/demo/_llgo/reflect/reflect.gop
new file mode 100644
index 00000000..3be53152
--- /dev/null
+++ b/demo/_llgo/reflect/reflect.gop
@@ -0,0 +1,12 @@
+import (
+	"c"
+	"reflect"
+)
+
+tyIntSlice := reflect.sliceOf(reflect.typeOf(0))
+v := reflect.zero(tyIntSlice)
+v = reflect.append(v, reflect.valueOf(1), reflect.valueOf(2), reflect.valueOf(3))
+for i, n := 0, v.len; i < n; i++ {
+	item := v.index(i)
+	c.Printf c"%d\n", item.int
+}
diff --git a/demo/_llgo/sqlitedemo/sqlitedemo.gop b/demo/_llgo/sqlitedemo/sqlitedemo.gop
new file mode 100644
index 00000000..46b62094
--- /dev/null
+++ b/demo/_llgo/sqlitedemo/sqlitedemo.gop
@@ -0,0 +1,58 @@
+import (
+	"c"
+	"c/sqlite"
+	"c/os"
+)
+
+func check(err sqlite.Errno, db *sqlite.Sqlite3, at string) {
+	if err != sqlite.OK {
+		c.printf c"==> %s Error: (%d) %s\n", c.allocaCStr(at), err, db.errmsg
+		c.exit 1
+	}
+}
+
+func checkDone(err sqlite.Errno, db *sqlite.Sqlite3, at string) {
+	if err != sqlite.Done {
+		check err, db, at
+	}
+}
+
+os.remove c"test.db"
+
+db, err := sqlite.open(c"test.db")
+check err, db, "sqlite: Open"
+
+err = db.exec(c"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", nil, nil, nil)
+check err, db, "sqlite: Exec CREATE TABLE"
+
+stmt, err := db.prepareV3("INSERT INTO users (id, name) VALUES (?, ?)", 0, nil)
+check err, db, "sqlite: PrepareV3 INSERT"
+
+stmt.bindInt 1, 100
+stmt.bindText 2, c"Hello World", -1, nil
+
+err = stmt.step
+checkDone err, db, "sqlite: Step INSERT 1"
+
+stmt.reset
+stmt.bindInt 1, 200
+stmt.bindText 2, c"This is llgo", -1, nil
+
+err = stmt.step
+checkDone err, db, "sqlite: Step INSERT 2"
+
+stmt.close
+
+stmt, err = db.prepareV3("SELECT * FROM users", 0, nil)
+check err, db, "sqlite: PrepareV3 SELECT"
+
+for {
+	if err = stmt.step; err != sqlite.HasRow {
+		break
+	}
+	c.printf c"==> id=%d, name=%s\n", stmt.columnInt(0), stmt.columnText(1)
+}
+checkDone err, db, "sqlite: Step done"
+
+stmt.close
+db.close
diff --git a/demo/_llgo/statistics/statistics.gop b/demo/_llgo/statistics/statistics.gop
new file mode 100644
index 00000000..5e2e1950
--- /dev/null
+++ b/demo/_llgo/statistics/statistics.gop
@@ -0,0 +1,9 @@
+import (
+	"c"
+	"py"
+	"py/statistics"
+)
+
+list := py.list(1.0, 2.0, 3.0, 4.0, 4.0)
+mean := statistics.mean(list)
+c.printf c"mean(1, 2, 3, 4, 4) = %f\n", mean.float64
diff --git a/demo/_llgo/tetris/tetris.gop b/demo/_llgo/tetris/tetris.gop
new file mode 100644
index 00000000..5d54faeb
--- /dev/null
+++ b/demo/_llgo/tetris/tetris.gop
@@ -0,0 +1,175 @@
+import (
+	"c"
+	"c/raylib"
+)
+
+const (
+	BOARD_WIDTH  = 10
+	BOARD_HEIGHT = 20
+	BLOCK_SIZE   = 30
+
+	SCREENWIDTH  = 300
+	SCREENHEIGHT = 600
+)
+
+const MAX_BLOCKS = 4
+
+type Shape struct {
+	Blocks [MAX_BLOCKS]raylib.Vector2
+	Color  raylib.Color
+}
+
+var SHAPES = []Shape{
+	{Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 2, Y: 0}, {X: 3, Y: 0}}, Color: raylib.SKYBLUE},
+	{Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}}, Color: raylib.YELLOW},
+	{Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 1, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.PURPLE},
+	{Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 1, Y: 0}, {X: 2, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}}, Color: raylib.GREEN},
+	{Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.RED},
+	{Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.BLUE},
+	{Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 2, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.ORANGE},
+}
+
+var board [BOARD_HEIGHT][BOARD_WIDTH]raylib.Color
+
+var curShape Shape
+var curPos raylib.Vector2
+
+var fallTime = c.Float(0)
+var fallSpeed = c.Float(0.2)
+var score = 0
+var scoreText = (*c.Char)(c.malloc(20))
+var gameOver = false
+
+func genShape() {
+	curShape = SHAPES[raylib.getRandomValue(0, 6)]
+	curPos = raylib.Vector2{BOARD_WIDTH/2 - 1, 0}
+}
+
+func checkCollision() bool {
+	for i := 0; i < MAX_BLOCKS; i++ {
+		x := int(curPos.X + curShape.Blocks[i].X)
+		y := int(curPos.Y + curShape.Blocks[i].Y)
+		if x < 0 || x >= BOARD_WIDTH || y >= BOARD_HEIGHT || (y >= 0 && board[y][x] != raylib.BLANK) {
+			return true
+		}
+	}
+	return false
+}
+
+func lockShape() {
+	for i := 0; i < MAX_BLOCKS; i++ {
+		x := int(curPos.X + curShape.Blocks[i].X)
+		y := int(curPos.Y + curShape.Blocks[i].Y)
+		if y >= 0 {
+			board[y][x] = curShape.Color
+		}
+	}
+}
+
+func rotateShape() {
+	rotated := curShape
+	for i := 0; i < MAX_BLOCKS; i++ {
+		x := rotated.Blocks[i].X
+		rotated.Blocks[i].X = -rotated.Blocks[i].Y
+		rotated.Blocks[i].Y = x
+	}
+
+	temp := curShape
+	curShape = rotated
+	if checkCollision() {
+		curShape = temp
+	}
+}
+
+func clearLines() int {
+	linesCleared := 0
+	for y := BOARD_HEIGHT - 1; y >= 0; y-- {
+		lineFull := true
+		for x := 0; x < BOARD_WIDTH; x++ {
+			if board[y][x] == raylib.BLANK {
+				lineFull = false
+				break
+			}
+		}
+		if lineFull {
+			for yy := y; yy > 0; yy-- {
+				for x := 0; x < BOARD_WIDTH; x++ {
+					board[yy][x] = board[yy-1][x]
+				}
+			}
+			for x := 0; x < BOARD_WIDTH; x++ {
+				board[0][x] = raylib.BLANK
+			}
+			y += 1
+			linesCleared += 1
+		}
+	}
+	return linesCleared
+}
+
+func keyPressed(key c.Int) bool {
+	return raylib.isKeyPressed(key) || raylib.isKeyPressedRepeat(key)
+}
+
+raylib.initWindow SCREENWIDTH, SCREENHEIGHT, c"tetris (powered by raylib and Go+)"
+raylib.setTargetFPS 60
+genShape
+
+for !raylib.windowShouldClose && !gameOver {
+	fallTime += raylib.getFrameTime
+	if fallTime >= fallSpeed {
+		fallTime = 0
+		curPos.Y += 1
+		if checkCollision() {
+			curPos.Y -= 1
+			lockShape
+			linesCleared := clearLines()
+			score += linesCleared * 100
+			genShape
+			if checkCollision() {
+				gameOver = true
+			}
+		}
+	}
+
+	if keyPressed(raylib.KEY_LEFT) {
+		curPos.X--
+		if checkCollision() {
+			curPos.X++
+		}
+	}
+	if keyPressed(raylib.KEY_RIGHT) {
+		curPos.X++
+		if checkCollision() {
+			curPos.X--
+		}
+	}
+	if keyPressed(raylib.KEY_SPACE) || keyPressed(raylib.KEY_UP) || keyPressed(raylib.KEY_DOWN) {
+		rotateShape
+	}
+
+	raylib.beginDrawing
+	raylib.clearBackground raylib.RAYWHITE
+	for y := 0; y < BOARD_HEIGHT; y++ {
+		for x := 0; x < BOARD_WIDTH; x++ {
+			raylib.drawRectangle c.Int(x*BLOCK_SIZE), c.Int(y*BLOCK_SIZE), c.Int(BLOCK_SIZE-1), c.Int(BLOCK_SIZE-1), board[y][x]
+		}
+	}
+
+	for i := 0; i < MAX_BLOCKS; i++ {
+		raylib.drawRectangle c.Int((curPos.X+curShape.Blocks[i].X)*BLOCK_SIZE), c.Int((curPos.Y+curShape.Blocks[i].Y)*BLOCK_SIZE),
+			BLOCK_SIZE-1, BLOCK_SIZE-1, curShape.Color
+	}
+
+	c.sprintf scoreText, c"Score:%d", score
+	raylib.drawText scoreText, 10, 10, 20, raylib.BLACK
+	raylib.endDrawing
+}
+
+for !raylib.windowShouldClose {
+	raylib.beginDrawing
+	raylib.clearBackground raylib.RAYWHITE
+	raylib.drawText c"Game Over", SCREENWIDTH/2-50, SCREENHEIGHT/2-10, 20, raylib.RED
+	raylib.drawText scoreText, SCREENWIDTH/2-50, SCREENHEIGHT/2+10, 20, raylib.BLACK
+	raylib.endDrawing
+}
diff --git a/demo/_tinygo/go.mod b/demo/_tinygo/go.mod
new file mode 100644
index 00000000..d947a558
--- /dev/null
+++ b/demo/_tinygo/go.mod
@@ -0,0 +1,3 @@
+module tinygoexample
+
+go 1.18 // tinygo 0.32
diff --git a/demo/_tinygo/sortdemo/sort.gop b/demo/_tinygo/sortdemo/sort.gop
new file mode 100644
index 00000000..0f259a08
--- /dev/null
+++ b/demo/_tinygo/sortdemo/sort.gop
@@ -0,0 +1,19 @@
+import "sort"
+
+vals := [32, 58, 25, 92, 45, 78]
+sort.ints vals
+for v <- vals {
+	println v
+}
+
+texts := ["apple", "banana", "cherry", "date", "elderberry", "fig"]
+sort.slice texts, (i, j) => {
+	leni, lenj := len(texts[i]), len(texts[j])
+	if leni != lenj {
+		return leni < lenj
+	}
+	return texts[i] < texts[j]
+}
+for v <- texts {
+	println v
+}
diff --git a/testdata/gop-sample/a.gop b/demo/gop-sample/a.gop
similarity index 100%
rename from testdata/gop-sample/a.gop
rename to demo/gop-sample/a.gop
diff --git a/demo/gop-sample/b.gop b/demo/gop-sample/b.gop
new file mode 100644
index 00000000..9a068c0c
--- /dev/null
+++ b/demo/gop-sample/b.gop
@@ -0,0 +1,9 @@
+package main
+
+import (
+	ab "github.com/goplus/gop/demo/gop-sample/cpkag/b"
+)
+
+a()
+Ab()
+ab.Ab()
diff --git a/testdata/gop-sample/cpkag/b/ab.go b/demo/gop-sample/cpkag/b/ab.go
similarity index 100%
rename from testdata/gop-sample/cpkag/b/ab.go
rename to demo/gop-sample/cpkag/b/ab.go
diff --git a/testdata/gsh-exec/exec.gsh b/demo/gsh-exec/exec.gsh
similarity index 100%
rename from testdata/gsh-exec/exec.gsh
rename to demo/gsh-exec/exec.gsh
diff --git a/testdata/gsh-exec/foo/foo.gop b/demo/gsh-exec/foo/foo.gop
similarity index 100%
rename from testdata/gsh-exec/foo/foo.gop
rename to demo/gsh-exec/foo/foo.gop
diff --git a/testdata/mixgo-complex/bar.go b/demo/mixgo-complex/bar.go
similarity index 100%
rename from testdata/mixgo-complex/bar.go
rename to demo/mixgo-complex/bar.go
diff --git a/testdata/mixgo-complex/foo.gop b/demo/mixgo-complex/foo.gop
similarity index 100%
rename from testdata/mixgo-complex/foo.gop
rename to demo/mixgo-complex/foo.gop
diff --git a/testdata/mixgo/README.md b/demo/mixgo/README.md
similarity index 100%
rename from testdata/mixgo/README.md
rename to demo/mixgo/README.md
diff --git a/testdata/mixgo/a.go b/demo/mixgo/a.go
similarity index 100%
rename from testdata/mixgo/a.go
rename to demo/mixgo/a.go
diff --git a/testdata/mixgo/b.gop b/demo/mixgo/b.gop
similarity index 100%
rename from testdata/mixgo/b.gop
rename to demo/mixgo/b.gop
diff --git a/testdata/overloadfunc1/add.gop b/demo/overloadfunc1/add.gop
similarity index 100%
rename from testdata/overloadfunc1/add.gop
rename to demo/overloadfunc1/add.gop
diff --git a/testdata/overloadfunc2/mul.gop b/demo/overloadfunc2/mul.gop
similarity index 100%
rename from testdata/overloadfunc2/mul.gop
rename to demo/overloadfunc2/mul.gop
diff --git a/testdata/overloadmethod/method.gop b/demo/overloadmethod/method.gop
similarity index 100%
rename from testdata/overloadmethod/method.gop
rename to demo/overloadmethod/method.gop
diff --git a/testdata/overloadop1/overloadop.gop b/demo/overloadop1/overloadop.gop
similarity index 100%
rename from testdata/overloadop1/overloadop.gop
rename to demo/overloadop1/overloadop.gop
diff --git a/testdata/overloadop2/overloadop.gop b/demo/overloadop2/overloadop.gop
similarity index 100%
rename from testdata/overloadop2/overloadop.gop
rename to demo/overloadop2/overloadop.gop
diff --git a/testdata/typeasparamsfunc/col.go b/demo/typeasparamsfunc/col.go
similarity index 100%
rename from testdata/typeasparamsfunc/col.go
rename to demo/typeasparamsfunc/col.go
diff --git a/testdata/typeasparamsfunc/typeAsParamsFunc.gop b/demo/typeasparamsfunc/typeAsParamsFunc.gop
similarity index 100%
rename from testdata/typeasparamsfunc/typeAsParamsFunc.gop
rename to demo/typeasparamsfunc/typeAsParamsFunc.gop
diff --git a/testdata/typeasparamsmethod/col.go b/demo/typeasparamsmethod/col.go
similarity index 100%
rename from testdata/typeasparamsmethod/col.go
rename to demo/typeasparamsmethod/col.go
diff --git a/testdata/typeasparamsmethod/typeAsParamsMethod.gop b/demo/typeasparamsmethod/typeAsParamsMethod.gop
similarity index 100%
rename from testdata/typeasparamsmethod/typeAsParamsMethod.gop
rename to demo/typeasparamsmethod/typeAsParamsMethod.gop
diff --git a/testdata/typeparamscast/foo.go b/demo/typeparamscast/foo.go
similarity index 100%
rename from testdata/typeparamscast/foo.go
rename to demo/typeparamscast/foo.go
diff --git a/testdata/typeparamscast/typecast.gop b/demo/typeparamscast/typecast.gop
similarity index 100%
rename from testdata/typeparamscast/typecast.gop
rename to demo/typeparamscast/typecast.gop
diff --git a/testdata/unit-test/foo.gop b/demo/unit-test/foo.gop
similarity index 100%
rename from testdata/unit-test/foo.gop
rename to demo/unit-test/foo.gop
diff --git a/testdata/unit-test/foo_test.gox b/demo/unit-test/foo_test.gox
similarity index 100%
rename from testdata/unit-test/foo_test.gox
rename to demo/unit-test/foo_test.gox
diff --git a/doc/classfile.md b/doc/classfile.md
index a9598609..0e7bc2ee 100644
--- a/doc/classfile.md
+++ b/doc/classfile.md
@@ -147,7 +147,7 @@ func foo(v int) int {
 }
 ```
 
-Then you can create a `foo_test.gox` file to test it (see [unit-test/foo_test.gox](../testdata/unit-test/foo_test.gox)):
+Then you can create a `foo_test.gox` file to test it (see [unit-test/foo_test.gox](../demo/unit-test/foo_test.gox)):
 
 ```go
 if v := foo(50); v != 100 {
diff --git a/doc/docs.md b/doc/docs.md
index daf3d879..b7e0a5f9 100644
--- a/doc/docs.md
+++ b/doc/docs.md
@@ -20,6 +20,8 @@ The Go+ programming language is designed for engineering, STEM education, and da
 
 ## How to install
 
+Note: Requires go1.19 or later
+
 ### on Windows
 
 ```sh
@@ -51,8 +53,6 @@ $ brew install goplus
 
 ### from source code
 
-Note: Requires go1.18 or later
-
 ```bash
 git clone https://github.com/goplus/gop.git
 cd gop
@@ -1111,31 +1111,29 @@ By default `gop watch` does not convert test files (normally ending with `_test.
 
 ## Calling C from Go+
 
-- The `gop c` command (equivalent to the stand-alone `c2go` command) can be used to convert a C project to a Go project.
-- `import "C"` and `import "C/xxx"` are used to import a C project converted by c2go. where `import "C"` is short for `import "C/github.com/goplus/libc"`.
-- The `C"xxx"` syntax represents C-style string constants.
-
-Here is [an example to show how Go+ interacts with C](https://github.com/goplus/gop/tree/v1.1/testdata/helloc2go).
+Here is [an example to show how Go+ interacts with C](https://github.com/goplus/gop/tree/main/demo/_llgo/hellollgo).
 
 ```go
-import "C"
+import "c"
 
-C.printf C"Hello, c2go!\n"
-C.fprintf C.stderr, C"Hi, %7.1f\n", 3.14
+c.printf c"Hello, llgo!\n"
+c.fprintf c.Stderr, c"Hi, %6.1f\n", 3.14
 ```
 
-In this example we call two C standard functions `printf` and `fprintf`, passing a C variable `stderr` and two C strings in the form of `C"xxx"` (a Go+ syntax to represent C-style strings).
+Here `import "c"` is used to import libc. In this example we call two C standard functions `printf` and `fprintf`, passing a C variable `stderr` and two C strings in the form of `c"xxx"` (a Go+ syntax to represent C-style strings).
 
-Run `gop run .` to see the output of this example:
+To run this demo, you need to set the `GOP_GOCMD` environment variable first.
 
-```
-Hello, c2go!
-Hi,     3.1
+```sh
+export GOP_GOCMD=llgo  # default is `go`
 ```
 
-Of course, the current Go+ support for C is only a preview version, not to the extent that it is actually available in engineering. As far as libc is concerned, the current migration progress is only about 5%, and it is just the beginning.
+Then execute `gop run .` to see the output of this example:
 
-In the upcoming Go+ v1.2 version planning, complete support for C is listed as a top priority. Of course, support for cgo and Go templates is also under planning, which is a crucial capability enhancement for Go/Go+ hybrid projects.
+```
+Hello, llgo!
+Hi,    3.1
+```
 
 <h5 align="right"><a href="#table-of-contents">⬆ back to toc</a></h5>
 
diff --git a/doc/overload.md b/doc/overload.md
index d977c562..6e608d47 100644
--- a/doc/overload.md
+++ b/doc/overload.md
@@ -1,9 +1,9 @@
-Overload Func/Method/Operator/Types in Go+
+Overload Func/Method/Operator/Types
 =====
 
 ### Overload Funcs
 
-Define `overload func` in `inline func literal` style (see [overloadfunc1/add.gop](testdata/overloadfunc1/add.gop)):
+Define `overload func` in `inline func literal` style (see [overloadfunc1/add.gop](demo/overloadfunc1/add.gop)):
 
 ```go
 func add = (
@@ -19,7 +19,7 @@ println add(100, 7)
 println add("Hello", "World")
 ```
 
-Define `overload func` in `ident` style (see [overloadfunc2/mul.gop](testdata/overloadfunc2/mul.gop)):
+Define `overload func` in `ident` style (see [overloadfunc2/mul.gop](demo/overloadfunc2/mul.gop)):
 
 ```go
 func mulInt(a, b int) int {
@@ -41,7 +41,7 @@ println mul(1.2, 3.14)
 
 ### Overload Methods
 
-Define `overload method` (see [overloadmethod/method.gop](testdata/overloadmethod/method.gop)):
+Define `overload method` (see [overloadmethod/method.gop](demo/overloadmethod/method.gop)):
 
 ```go
 type foo struct {
@@ -69,7 +69,7 @@ var d = a.mul(c)
 
 ### Overload Unary Operators
 
-Define `overload unary operator` (see [overloadop1/overloadop.gop](testdata/overloadop1/overloadop.gop)):
+Define `overload unary operator` (see [overloadop1/overloadop.gop](demo/overloadop1/overloadop.gop)):
 
 ```go
 type foo struct {
@@ -91,7 +91,7 @@ a++
 
 ### Overload Binary Operators
 
-Define `overload binary operator` (see [overloadop1/overloadop.gop](testdata/overloadop1/overloadop.gop)):
+Define `overload binary operator` (see [overloadop1/overloadop.gop](demo/overloadop1/overloadop.gop)):
 
 ```go
 type foo struct {
@@ -112,7 +112,7 @@ var c = a * b
 var d = a != b
 ```
 
-However, `binary operator` usually need to support interoperability between multiple types. In this case it becomes more complex (see [overloadop2/overloadop.gop](testdata/overloadop2/overloadop.gop)):
+However, `binary operator` usually need to support interoperability between multiple types. In this case it becomes more complex (see [overloadop2/overloadop.gop](demo/overloadop2/overloadop.gop)):
 
 ```go
 type foo struct {
diff --git a/go.mod b/go.mod
index cfc58966..a1e034da 100644
--- a/go.mod
+++ b/go.mod
@@ -4,19 +4,16 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.26
-	github.com/goplus/gogen v1.15.2
-	github.com/goplus/mod v0.13.10
+	github.com/goplus/gogen v1.16.0
+	github.com/goplus/llgo v0.9.1
+	github.com/goplus/mod v0.13.12
 	github.com/qiniu/x v1.13.10
-	golang.org/x/tools v0.19.0
+	golang.org/x/tools v0.22.0
 )
 
 require (
-	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
-	github.com/modern-go/reflect2 v1.0.2 // indirect
-	golang.org/x/mod v0.17.0 // indirect
-	golang.org/x/sys v0.18.0 // indirect
+	golang.org/x/mod v0.19.0 // indirect
+	golang.org/x/sys v0.21.0 // indirect
 )
 
 retract v1.1.12
diff --git a/go.sum b/go.sum
index d36cd4cc..7fcc63a7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,74 +1,17 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.26 h1:YmihhrbUC6+5RmdRqYzul4igfsyofwj4mM2iwkTTOuc=
-github.com/goplus/c2go v0.7.26/go.mod h1:ePAStubV/ls8mmdPGQo6VfADTVd46rKuBemE4zzBDnA=
-github.com/goplus/gogen v1.15.2 h1:Q6XaSx/Zi5tWnjfAziYsQI6Jv6MgODRpFtOYqNkiiqM=
-github.com/goplus/gogen v1.15.2/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
-github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE=
-github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c=
-github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
-github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/goplus/gogen v1.16.0 h1:hAK2ZX8vCjH+Y2QoJl9viSZ8Gw9pzE0vCz5voYBYnv4=
+github.com/goplus/gogen v1.16.0/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
+github.com/goplus/llgo v0.9.1 h1:f9JsHjWQK1pJKIZVd/O/p0p2g9HTp963NSB8s4DCcAI=
+github.com/goplus/llgo v0.9.1/go.mod h1:zsrtWZapL4aklZc99xBSZRynGzLTIT1mLRjP0VSn9iw=
+github.com/goplus/mod v0.13.12 h1:Trwk6j3i9VvBuW6/9ZxmkoFlEL2v3HKQu0Na1c6DAdw=
+github.com/goplus/mod v0.13.12/go.mod h1:fyCcoiL02uUQK9CWxGK9pQzuJT+rZIvRKaaG+hSa2bk=
 github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE=
 github.com/qiniu/x v1.13.10/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
-golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
-golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
-golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
-golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
+golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
+golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
diff --git a/load.go b/load.go
index 127d3397..f6c3cf46 100644
--- a/load.go
+++ b/load.go
@@ -28,7 +28,7 @@ import (
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/token"
-	"github.com/goplus/gop/x/c2go"
+	"github.com/goplus/gop/x/gocmd"
 	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/mod/env"
 	"github.com/goplus/mod/gopmod"
@@ -168,6 +168,17 @@ func NewDefaultConf(dir string, flags ConfFlags) (conf *Config, err error) {
 	return
 }
 
+func (conf *Config) NewGoCmdConf() *gocmd.Config {
+	if cl := conf.Mod.Opt.Compiler; cl != nil {
+		if os.Getenv("GOP_GOCMD") == "" {
+			os.Setenv("GOP_GOCMD", cl.Name)
+		}
+	}
+	return &gocmd.Config{
+		Gop: conf.Gop,
+	}
+}
+
 // UpdateCache updates the cache.
 func (conf *Config) UpdateCache(verbose ...bool) {
 	if conf.CacheFile != "" {
@@ -260,7 +271,6 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 		RelativeBase: relativeBaseOf(mod),
 		Importer:     imp,
 		LookupClass:  mod.LookupClass,
-		LookupPub:    c2go.LookupPub(mod),
 	}
 
 	for name, pkg := range pkgs {
@@ -336,7 +346,7 @@ func relativeBaseOf(mod *gopmod.Module) string {
 
 // -----------------------------------------------------------------------------
 
-// LoadDir loads a Go+ package from specified files.
+// LoadFiles loads a Go+ package from specified files.
 func LoadFiles(dir string, files []string, conf *Config) (out *gogen.Package, err error) {
 	if conf == nil {
 		conf = new(Config)
@@ -381,7 +391,6 @@ func LoadFiles(dir string, files []string, conf *Config) (out *gogen.Package, er
 			RelativeBase: relativeBaseOf(mod),
 			Importer:     imp,
 			LookupClass:  mod.LookupClass,
-			LookupPub:    c2go.LookupPub(mod),
 		}
 		out, err = cl.NewPackage("", pkg, clConf)
 		if err != nil {
@@ -405,7 +414,7 @@ var (
 // -----------------------------------------------------------------------------
 
 // GetFileClassType get gop module file classType.
-func GetFileClassType(mod *gopmod.Module, file *ast.File, filename string) (classType string, isTest bool, ok bool) {
+func GetFileClassType(mod *gopmod.Module, file *ast.File, filename string) (classType string, isTest bool) {
 	return cl.GetFileClassType(file, filename, mod.LookupClass)
 }
 
diff --git a/outline.go b/outline.go
index 050b8a80..99b059ff 100644
--- a/outline.go
+++ b/outline.go
@@ -27,7 +27,6 @@ import (
 
 	"github.com/goplus/gop/cl/outline"
 	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/x/c2go"
 	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/mod/gopmod"
 	"github.com/qiniu/x/errors"
@@ -103,7 +102,6 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 			Fset:        fset,
 			Importer:    imp,
 			LookupClass: mod.LookupClass,
-			LookupPub:   c2go.LookupPub(mod),
 		})
 		if err != nil {
 			return
diff --git a/parser/parser.go b/parser/parser.go
index 040b3b51..9b8ea67f 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1724,7 +1724,7 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 		}
 		return
 
-	case token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
+	case token.STRING, token.CSTRING, token.PYSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
 		bl := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
 		if p.tok == token.STRING && len(p.lit) > 1 {
 			bl.Extra = p.stringLit(p.pos, p.lit)
@@ -2353,7 +2353,8 @@ func (p *parser) isCmd(x ast.Expr) bool {
 func (p *parser) checkCmd() bool {
 	switch p.tok {
 	case token.IDENT, token.DRARROW,
-		token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT,
+		token.STRING, token.CSTRING, token.PYSTRING,
+		token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT,
 		token.FUNC, token.GOTO, token.MAP, token.INTERFACE, token.CHAN, token.STRUCT, token.ENV:
 		return true
 	case token.SUB, token.AND, token.MUL, token.ARROW, token.XOR, token.ADD:
@@ -3388,7 +3389,7 @@ func (p *parser) parseStmt(allowCmd bool) (s ast.Stmt) {
 		s = &ast.DeclStmt{Decl: p.parseGenDecl(p.tok, p.parseValueSpec)}
 	case
 		// tokens that may start an expression
-		token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING, token.FUNC, token.LPAREN, // operands
+		token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING, token.PYSTRING, token.FUNC, token.LPAREN, // operands
 		token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT, token.ENV, // unary operators
 		token.LBRACK, token.STRUCT, token.CHAN, token.INTERFACE: // composite types
 		allowCmd = false
diff --git a/printer/printer.go b/printer/printer.go
index 0c15852b..fbc3a266 100644
--- a/printer/printer.go
+++ b/printer/printer.go
@@ -312,8 +312,11 @@ func (p *printer) writeString(pos token.Position, s string, isLit bool) {
 		// tabwriter.Escape bytes since they do not appear in legal
 		// UTF-8 sequences.
 		p.output = append(p.output, tabwriter.Escape)
-		if p.lastTok == token.CSTRING {
-			p.output = append(p.output, 'C')
+		switch p.lastTok {
+		case token.CSTRING:
+			p.output = append(p.output, 'c')
+		case token.PYSTRING:
+			p.output = append(p.output, 'p', 'y')
 		}
 	}
 
diff --git a/scanner/scanner.go b/scanner/scanner.go
index f84fa245..3c791483 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -835,10 +835,17 @@ scanAgain:
 			// keywords are longer than one letter - avoid lookup otherwise
 			tok = token.Lookup(lit)
 			switch tok {
-			case token.IDENT, token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN:
+			case token.IDENT:
+				insertSemi = true
+				if lit == "py" && s.ch == '"' { // py"..."
+					s.next()
+					tok = token.PYSTRING
+					lit = s.scanString()
+				}
+			case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN:
 				insertSemi = true
 			}
-		} else if lit == "C" && s.ch == '"' { // C"..."
+		} else if (lit == "c" || lit == "C") && s.ch == '"' { // c"..."
 			s.next()
 			insertSemi = true
 			tok = token.CSTRING
diff --git a/token/token.go b/token/token.go
index f13a2f22..56e3a958 100644
--- a/token/token.go
+++ b/token/token.go
@@ -143,9 +143,14 @@ const (
 	additional_end
 	additional_end2
 
+	additional_literal_beg = 96
+	additional_literal_end = 97
+
 	ENV = additional_end2 // ${name}
 
-	CSTRING  = literal_beg  // C"Hello"
+	PYSTRING = additional_literal_beg // py"Hello"
+
+	CSTRING  = literal_beg  // c"Hello"
 	RAT      = literal_end  // 123.5r
 	DRARROW  = operator_beg // => (double right arrow)
 	QUESTION = operator_end // ?
@@ -163,14 +168,15 @@ var tokens = [...]string{
 	EOF:     "EOF",
 	COMMENT: "COMMENT",
 
-	IDENT:   "IDENT",
-	INT:     "INT",
-	FLOAT:   "FLOAT",
-	IMAG:    "IMAG",
-	CHAR:    "CHAR",
-	STRING:  "STRING",
-	CSTRING: "CSTRING",
-	RAT:     "RAT",
+	IDENT:    "IDENT",
+	INT:      "INT",
+	FLOAT:    "FLOAT",
+	IMAG:     "IMAG",
+	CHAR:     "CHAR",
+	STRING:   "STRING",
+	CSTRING:  "CSTRING",
+	PYSTRING: "PYSTRING",
+	RAT:      "RAT",
 
 	ADD: "+",
 	SUB: "-",
@@ -333,7 +339,7 @@ func Lookup(ident string) Token {
 // IsLiteral returns true for tokens corresponding to identifiers
 // and basic type literals; it returns false otherwise.
 func (tok Token) IsLiteral() bool {
-	return literal_beg <= tok && tok <= literal_end
+	return literal_beg <= tok && tok <= literal_end || tok == PYSTRING
 }
 
 // IsOperator returns true for tokens corresponding to operators and
diff --git a/x/c2go/c2go.go b/x/c2go/c2go.go
deleted file mode 100644
index 33116cc8..00000000
--- a/x/c2go/c2go.go
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package c2go
-
-import (
-	"path/filepath"
-
-	"github.com/goplus/mod/gopmod"
-)
-
-// -----------------------------------------------------------------------------
-
-// LookupPub returns a anonymous function required by cl.NewPackage.
-func LookupPub(mod *gopmod.Module) func(pkgPath string) (pubfile string, err error) {
-	return func(pkgPath string) (pubfile string, err error) {
-		pkg, err := mod.Lookup(pkgPath)
-		if err == nil {
-			pubfile = filepath.Join(pkg.Dir, "c2go.a.pub")
-		}
-		return
-	}
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/langserver/server.go b/x/langserver/server.go
index 71f12099..1e26e6be 100644
--- a/x/langserver/server.go
+++ b/x/langserver/server.go
@@ -138,15 +138,19 @@ func GenGo(pattern ...string) (err error) {
 	if err != nil {
 		return
 	}
+	conf, _ := gop.NewDefaultConf(".", 0)
+	if conf != nil {
+		defer conf.UpdateCache()
+	}
 	for _, proj := range projs {
 		switch v := proj.(type) {
 		case *gopprojs.DirProj:
-			gop.GenGoEx(v.Dir, nil, true, 0)
+			gop.GenGoEx(v.Dir, conf, true, 0)
 		case *gopprojs.PkgPathProj:
 			if v.Path == "builtin" {
 				continue
 			}
-			gop.GenGoPkgPathEx("", v.Path, nil, true, 0)
+			gop.GenGoPkgPathEx("", v.Path, conf, true, 0)
 		}
 	}
 	return
diff --git a/x/typesutil/check.go b/x/typesutil/check.go
index 3f148446..256e9375 100644
--- a/x/typesutil/check.go
+++ b/x/typesutil/check.go
@@ -26,7 +26,6 @@ import (
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/token"
-	"github.com/goplus/gop/x/c2go"
 	"github.com/goplus/gop/x/typesutil/internal/typesutil"
 	"github.com/goplus/mod/gopmod"
 	"github.com/qiniu/x/errors"
@@ -73,10 +72,6 @@ type Config struct {
 	// If WorkingDir is not set, os.Getwd() is used.
 	WorkingDir string
 
-	// C2goBase specifies base of standard c2go packages (optional).
-	// Default is github.com/goplus/.
-	C2goBase string
-
 	// Mod represents a Go+ module (optional).
 	Mod *gopmod.Module
 
@@ -150,8 +145,6 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error)
 	_, err = cl.NewPackage(pkgTypes.Path(), pkg, &cl.Config{
 		Types:          pkgTypes,
 		Fset:           fset,
-		C2goBase:       opts.C2goBase,
-		LookupPub:      c2go.LookupPub(mod),
 		LookupClass:    mod.LookupClass,
 		Importer:       conf.Importer,
 		Recorder:       NewRecorder(p.gopInfo),
diff --git a/x/typesutil/gopinfo.go b/x/typesutil/gopinfo.go
index e2c00d71..2416041e 100644
--- a/x/typesutil/gopinfo.go
+++ b/x/typesutil/gopinfo.go
@@ -91,6 +91,7 @@ type Info struct {
 	//     *ast.ImportSpec    *PkgName for imports without renames
 	//     *ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
 	//     *ast.Field         anonymous parameter *Var (incl. unnamed results)
+	//     *ast.FunLit        function literal in *ast.OverloadFuncDecl
 	//
 	Implicits map[ast.Node]types.Object
 
@@ -135,8 +136,8 @@ type Info struct {
 	// appear in this list.
 	// InitOrder []*Initializer
 
-	// Overloads maps identifiers to the overload objects.
-	Overloads map[*ast.Ident][]types.Object
+	// Overloads maps identifiers to the overload decl object.
+	Overloads map[*ast.Ident]types.Object
 }
 
 // ObjectOf returns the object denoted by the specified id,
@@ -167,6 +168,18 @@ func (info *Info) TypeOf(e ast.Expr) types.Type {
 	return nil
 }
 
+// Returns the overloaded function declaration corresponding to the ident and its overloaded function members
+func (info *Info) OverloadOf(id *ast.Ident) (types.Object, []types.Object) {
+	if obj := info.Overloads[id]; obj != nil {
+		if sig, ok := obj.Type().(*types.Signature); ok {
+			if _, objs := gogen.CheckSigFuncExObjects(sig); len(objs) > 1 {
+				return obj, objs
+			}
+		}
+	}
+	return nil, nil
+}
+
 // -----------------------------------------------------------------------------
 
 type gopRecorder struct {
@@ -254,11 +267,11 @@ func (info gopRecorder) Use(id *ast.Ident, obj types.Object) {
 	}
 	if info.Overloads != nil {
 		if sig, ok := obj.Type().(*types.Signature); ok {
-			if ext, objs := gogen.CheckSigFuncExObjects(sig); len(objs) > 1 {
+			if ext, ok := gogen.CheckSigFuncEx(sig); ok {
 				if debugVerbose {
 					log.Println("==> Overloads:", id, ext)
 				}
-				info.Overloads[id] = objs
+				info.Overloads[id] = obj
 			}
 		}
 	}


Test patch:
diff --git a/cl/_testc/hello/in.gop b/cl/_testc/hello/in.gop
new file mode 100644
index 00000000..4e7251d4
--- /dev/null
+++ b/cl/_testc/hello/in.gop
@@ -0,0 +1,3 @@
+import "c"
+
+c.Printf c"Hello, world!\n"
diff --git a/cl/_testc/hello/out.go b/cl/_testc/hello/out.go
new file mode 100644
index 00000000..64416224
--- /dev/null
+++ b/cl/_testc/hello/out.go
@@ -0,0 +1,7 @@
+package main
+
+import "github.com/goplus/llgo/c"
+
+func main() {
+	c.Printf(c.Str("Hello, world!\n"))
+}
diff --git a/cl/_testgop/matrix/in.gop b/cl/_testgop/matrix/in.gop
new file mode 100644
index 00000000..b46adba7
--- /dev/null
+++ b/cl/_testgop/matrix/in.gop
@@ -0,0 +1,4 @@
+echo [
+	1, 2, 3
+	4, 5, 6
+]
diff --git a/cl/_testpy/_matrix/in.gop b/cl/_testpy/_matrix/in.gop
new file mode 100644
index 00000000..c5ff9c62
--- /dev/null
+++ b/cl/_testpy/_matrix/in.gop
@@ -0,0 +1,17 @@
+import (
+	"py/numpy"
+	"py/std"
+)
+
+a := [
+	[1.0, 2.0, 3.0],
+	[4.0, 5.0, 6.0],
+	[7.0, 8.0, 9.0],
+]
+b := [
+	[9.0, 8.0, 7.0],
+	[6.0, 5.0, 4.0],
+	[3.0, 2.0, 1.0],
+]
+x := numpy.add(a, b)
+std.print "a+b =", x
diff --git a/cl/_testpy/hello/in.gop b/cl/_testpy/hello/in.gop
new file mode 100644
index 00000000..2669fa1e
--- /dev/null
+++ b/cl/_testpy/hello/in.gop
@@ -0,0 +1,5 @@
+import (
+	"py/std"
+)
+
+std.print py"Hello, World!"
diff --git a/cl/_testpy/hello/out.go b/cl/_testpy/hello/out.go
new file mode 100644
index 00000000..fcd6a1ff
--- /dev/null
+++ b/cl/_testpy/hello/out.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+	"github.com/goplus/llgo/py"
+	"github.com/goplus/llgo/py/std"
+)
+
+func main() {
+	std.Print(py.Str("Hello, World!"))
+}
diff --git a/cl/_testpy/pycall/in.gop b/cl/_testpy/pycall/in.gop
new file mode 100644
index 00000000..b7759703
--- /dev/null
+++ b/cl/_testpy/pycall/in.gop
@@ -0,0 +1,8 @@
+import (
+	"c"
+	"py"
+	"py/math"
+)
+
+x := math.sqrt(py.float(2))
+c.printf c"sqrt(2) = %f\n", x.float64
diff --git a/cl/_testpy/pycall/out.go b/cl/_testpy/pycall/out.go
new file mode 100644
index 00000000..21ec21ab
--- /dev/null
+++ b/cl/_testpy/pycall/out.go
@@ -0,0 +1,12 @@
+package main
+
+import (
+	"github.com/goplus/llgo/c"
+	"github.com/goplus/llgo/py"
+	"github.com/goplus/llgo/py/math"
+)
+
+func main() {
+	x := math.Sqrt(py.Float(2))
+	c.Printf(c.Str("sqrt(2) = %f\n"), x.Float64())
+}
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index ce7133df..88cd15e7 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -17,15 +17,14 @@
 package cl
 
 import (
-	"errors"
 	"go/types"
 	"log"
 	"testing"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gogen/cpackages"
 	"github.com/goplus/gogen/packages"
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/mod/modfile"
 )
@@ -40,6 +39,15 @@ func getGoxConf() *gogen.Config {
 	return &gogen.Config{Fset: fset, Importer: imp}
 }
 
+func TestSimplifyPkgPath(t *testing.T) {
+	if simplifyPkgPath("c/lua") != "github.com/goplus/llgo/c/lua" {
+		t.Fatal("simplifyPkgPath: c/lua")
+	}
+	if simplifyPkgPath("cpp/std") != "github.com/goplus/llgo/cpp/std" {
+		t.Fatal("simplifyPkgPath: cpp/std")
+	}
+}
+
 func TestCompileLambdaExpr(t *testing.T) {
 	ctx := &blockCtx{
 		pkgCtx: &pkgCtx{},
@@ -154,85 +162,6 @@ func TestErrStringLit(t *testing.T) {
 func TestErrPreloadFile(t *testing.T) {
 	pkg := gogen.NewPackage("", "foo", goxConf)
 	ctx := &blockCtx{pkgCtx: &pkgCtx{}}
-	t.Run("overloadName", func(t *testing.T) {
-		defer func() {
-			if e := recover(); e == nil || e != "TODO - can't overload operator ++\n" {
-				t.Fatal("TestErrPreloadFile:", e)
-			}
-		}()
-		overloadName(&ast.Ident{}, "++", true)
-	})
-	t.Run("checkOverloadFunc", func(t *testing.T) {
-		defer func() {
-			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc\n" {
-				t.Fatal("TestErrPreloadFile:", e)
-			}
-		}()
-		checkOverloadFunc(&ast.OverloadFuncDecl{
-			Recv: &ast.FieldList{},
-		})
-	})
-	t.Run("checkOverloadMethod", func(t *testing.T) {
-		defer func() {
-			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod\n" {
-				t.Fatal("TestErrPreloadFile:", e)
-			}
-		}()
-		checkOverloadMethod(&ast.OverloadFuncDecl{})
-	})
-	t.Run("checkOverloadMethodRecvType1", func(t *testing.T) {
-		defer func() {
-			if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: bar\n" {
-				t.Fatal("TestErrPreloadFile:", e)
-			}
-		}()
-		checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, &ast.Ident{Name: "bar"})
-	})
-	t.Run("checkOverloadMethodRecvType2", func(t *testing.T) {
-		defer func() {
-			if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: &{0 INT 123 <nil>}\n" {
-				t.Fatal("TestErrPreloadFile:", e)
-			}
-		}()
-		expr := &ast.BasicLit{Kind: token.INT, Value: "123"}
-		checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, expr)
-	})
-	t.Run("OverloadFuncDecl: invalid recv", func(t *testing.T) {
-		defer func() {
-			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: invalid recv\n" {
-				t.Fatal("TestErrPreloadFile:", e)
-			}
-		}()
-		decls := []ast.Decl{
-			&ast.OverloadFuncDecl{
-				Name: &ast.Ident{Name: "add"},
-				Funcs: []ast.Expr{
-					&ast.FuncLit{},
-				},
-				Recv: &ast.FieldList{List: []*ast.Field{
-					{Type: &ast.StarExpr{}},
-				}},
-			},
-		}
-		preloadFile(pkg, ctx, &ast.File{Decls: decls}, "", true)
-	})
-	t.Run("OverloadFuncDecl: unknown func", func(t *testing.T) {
-		defer func() {
-			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: unknown func - *ast.BasicLit\n" {
-				t.Fatal("TestErrPreloadFile:", e)
-			}
-		}()
-		decls := []ast.Decl{
-			&ast.OverloadFuncDecl{
-				Name: &ast.Ident{Name: "add"},
-				Funcs: []ast.Expr{
-					&ast.BasicLit{},
-				},
-				Operator: true,
-			},
-		}
-		preloadFile(pkg, ctx, &ast.File{Decls: decls}, "", true)
-	})
 	t.Run("unknown decl", func(t *testing.T) {
 		defer func() {
 			if e := recover(); e == nil || e != "TODO - cl.preloadFile: unknown decl - *ast.BadDecl\n" {
@@ -350,6 +279,10 @@ func TestFileClassType(t *testing.T) {
 		{true, false, false, "Abc_yap.gox", "Abc", false, true},
 		{true, false, true, "main_yap.gox", "App", false, true},
 
+		{true, false, true, "abc_yap.gox", "abc", false, true},
+		{true, false, true, "Abc_yap.gox", "Abc", false, true},
+		{true, false, true, "main_yap.gox", "App", false, true},
+
 		{true, false, false, "abc_ytest.gox", "case_abc", true, true},
 		{true, false, false, "Abc_ytest.gox", "caseAbc", true, true},
 		{true, false, true, "main_ytest.gox", "App", true, true},
@@ -374,11 +307,9 @@ func TestFileClassType(t *testing.T) {
 		return
 	}
 	for _, test := range tests {
+		_ = test.found
 		f := &ast.File{IsClass: test.isClass, IsNormalGox: test.isNormalGox, IsProj: test.isProj}
-		classType, isTest, found := GetFileClassType(f, test.fileName, lookupClass)
-		if found != test.found {
-			t.Fatalf("%v found classType want %v, got %v.", test.fileName, test.found, found)
-		}
+		classType, isTest := GetFileClassType(f, test.fileName, lookupClass)
 		if isTest != test.isTest {
 			t.Fatalf("%v check classType isTest want %v, got %v.", test.fileName, test.isTest, isTest)
 		}
@@ -389,14 +320,12 @@ func TestFileClassType(t *testing.T) {
 }
 
 func TestErrMultiStarRecv(t *testing.T) {
-	defer func() {
-		if e := recover(); e == nil {
-			t.Fatal("TestErrMultiStarRecv: no panic?")
-		}
-	}()
-	getRecvType(&ast.StarExpr{
+	_, _, ok := getRecvType(&ast.StarExpr{
 		X: &ast.StarExpr{},
 	})
+	if ok {
+		t.Fatal("TestErrMultiStarRecv: no error?")
+	}
 }
 
 func TestErrAssign(t *testing.T) {
@@ -632,12 +561,6 @@ func TestGetGoFile(t *testing.T) {
 	}
 }
 
-func TestC2goBase(t *testing.T) {
-	if c2goBase("") != "github.com/goplus/" {
-		t.Fatal("c2goBase failed")
-	}
-}
-
 func TestErrNewType(t *testing.T) {
 	testPanic(t, `bar redeclared in this block
 	previous declaration at <TODO>
@@ -648,25 +571,6 @@ func TestErrNewType(t *testing.T) {
 	})
 }
 
-func TestErrLoadImport(t *testing.T) {
-	testPanic(t, "-: unknownpkg not found or not a valid C package (c2go.a.pub file not found).\n", func() {
-		pkg := &pkgCtx{
-			nodeInterp: &nodeInterp{
-				fset: token.NewFileSet(),
-			},
-			cpkgs: cpackages.NewImporter(
-				&cpackages.Config{LookupPub: func(pkgPath string) (pubfile string, err error) {
-					return "", errors.New("not found")
-				}})}
-		ctx := &blockCtx{pkgCtx: pkg}
-		spec := &ast.ImportSpec{
-			Path: &ast.BasicLit{Kind: token.STRING, Value: `"C/unknownpkg"`},
-		}
-		loadImport(ctx, spec)
-		panic(ctx.errs[0].Error())
-	})
-}
-
 func TestErrCompileBasicLit(t *testing.T) {
 	testPanic(t, "compileBasicLit: invalid syntax\n", func() {
 		ctx := &blockCtx{cb: new(gogen.CodeBuilder)}
@@ -687,4 +591,16 @@ func testPanic(t *testing.T, panicMsg string, doPanic func()) {
 	})
 }
 
+func TestClassFileEnd(t *testing.T) {
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "get.yap", `json {"id": ${id} }`, parser.ParseGoPlusClass)
+	if err != nil {
+		t.Fatal(err)
+	}
+	f.Decls = append(f.Decls, astFnClassfname(&gmxClass{clsfile: "get"}))
+	if f.End() != f.ShadowEntry.End() {
+		t.Fatal("class file end not shadow entry")
+	}
+}
+
 // -----------------------------------------------------------------------------
diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go
new file mode 100644
index 00000000..a2c7f3e9
--- /dev/null
+++ b/cl/cltest/cltest.go
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cltest
+
+import (
+	"bytes"
+	"io/fs"
+	"log"
+	"os"
+	"path"
+	"strings"
+	"testing"
+
+	"github.com/goplus/gogen"
+	"github.com/goplus/gop"
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx"
+	"github.com/goplus/gop/parser/fsx/memfs"
+	"github.com/goplus/gop/scanner"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/mod/env"
+	"github.com/goplus/mod/modfile"
+)
+
+var (
+	Gop  *env.Gop
+	Conf *cl.Config
+)
+
+func init() {
+	Gop = &env.Gop{Version: "1.0"}
+	gogen.SetDebug(gogen.DbgFlagAll)
+	cl.SetDebug(cl.DbgFlagAll | cl.FlagNoMarkAutogen)
+	fset := token.NewFileSet()
+	imp := gop.NewImporter(nil, Gop, fset)
+	Conf = &cl.Config{
+		Fset:          fset,
+		Importer:      imp,
+		Recorder:      gopRecorder{},
+		LookupClass:   LookupClass,
+		NoFileLine:    true,
+		NoAutoGenMain: true,
+	}
+}
+
+// -----------------------------------------------------------------------------
+
+func LookupClass(ext string) (c *modfile.Project, ok bool) {
+	switch ext {
+	case ".tgmx", ".tspx":
+		return &modfile.Project{
+			Ext: ".tgmx", Class: "*MyGame",
+			Works:    []*modfile.Class{{Ext: ".tspx", Class: "Sprite"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx", "math"}}, true
+	case ".t2gmx", ".t2spx", ".t2spx2":
+		return &modfile.Project{
+			Ext: ".t2gmx", Class: "Game",
+			Works: []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"},
+				{Ext: ".t2spx2", Class: "Sprite2"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
+	case "_t3spx.gox", ".t3spx2":
+		return &modfile.Project{
+			Works: []*modfile.Class{{Ext: "_t3spx.gox", Class: "Sprite"},
+				{Ext: ".t3spx2", Class: "Sprite2"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
+	case "_spx.gox":
+		return &modfile.Project{
+			Ext: "_spx.gox", Class: "Game",
+			Works:    []*modfile.Class{{Ext: "_spx.gox", Class: "Sprite"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx3", "math"},
+			Import:   []*modfile.Import{{Path: "github.com/goplus/gop/cl/internal/spx3/jwt"}}}, true
+	case "_xtest.gox":
+		return &modfile.Project{
+			Ext: "_xtest.gox", Class: "App",
+			Works:    []*modfile.Class{{Ext: "_xtest.gox", Class: "Case"}},
+			PkgPaths: []string{"github.com/goplus/gop/test", "testing"}}, true
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+func Named(t *testing.T, name string, gopcode, expected string) {
+	t.Run(name, func(t *testing.T) {
+		Do(t, gopcode, expected)
+	})
+}
+
+func Do(t *testing.T, gopcode, expected string) {
+	DoExt(t, Conf, "main", gopcode, expected)
+}
+
+func DoWithFname(t *testing.T, gopcode, expected string, fname string) {
+	fs := memfs.SingleFile("/foo", fname, gopcode)
+	DoFS(t, Conf, fs, "/foo", nil, "main", expected)
+}
+
+func DoExt(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) {
+	fs := memfs.SingleFile("/foo", "bar.gop", gopcode)
+	DoFS(t, conf, fs, "/foo", nil, pkgname, expected)
+}
+
+func Mixed(t *testing.T, pkgname, gocode, gopcode, expected string, outline ...bool) {
+	conf := *Conf
+	conf.Outline = (outline != nil && outline[0])
+	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
+	DoFS(t, &conf, fs, "/foo", nil, pkgname, expected)
+}
+
+// -----------------------------------------------------------------------------
+
+func DoFS(
+	t *testing.T, conf *cl.Config,
+	fs parser.FileSystem, dir string, filter func(fs.FileInfo) bool, pkgname, expected string) {
+	cl.SetDisableRecover(true)
+	defer cl.SetDisableRecover(false)
+
+	fset := conf.Fset
+	pkgs, err := parser.ParseFSDir(fset, fs, dir, parser.Config{
+		Mode:   parser.ParseComments,
+		Filter: filter,
+	})
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("ParseFSDir:", err)
+	}
+	bar := pkgs[pkgname]
+	pkg, err := cl.NewPackage("github.com/goplus/gop/cl", bar, conf)
+	if err != nil {
+		t.Fatal("NewPackage:", err)
+	}
+	var b bytes.Buffer
+	err = pkg.WriteTo(&b)
+	if err != nil {
+		t.Fatal("gogen.WriteTo failed:", err)
+	}
+	result := b.String()
+	if result != expected {
+		t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
+	}
+}
+
+// -----------------------------------------------------------------------------
+
+func FromDir(t *testing.T, sel, relDir string) {
+	dir, err := os.Getwd()
+	if err != nil {
+		t.Fatal("Getwd failed:", err)
+	}
+	dir = path.Join(dir, relDir)
+	fis, err := os.ReadDir(dir)
+	if err != nil {
+		t.Fatal("ReadDir failed:", err)
+	}
+	for _, fi := range fis {
+		name := fi.Name()
+		if !fi.IsDir() || strings.HasPrefix(name, "_") {
+			continue
+		}
+		t.Run(name, func(t *testing.T) {
+			testFrom(t, dir+"/"+name, sel)
+		})
+	}
+}
+
+func testFrom(t *testing.T, pkgDir, sel string) {
+	if sel != "" && !strings.Contains(pkgDir, sel) {
+		return
+	}
+	log.Println("Parsing", pkgDir)
+	out := pkgDir + "/out.go"
+	b, err := os.ReadFile(out)
+	if err != nil {
+		t.Fatal("ReadFile failed:", err)
+	}
+	expected := string(b)
+	filter := func(fi fs.FileInfo) bool {
+		return fi.Name() == "in.gop"
+	}
+	DoFS(t, Conf, fsx.Local, pkgDir, filter, "main", expected)
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/cltest/error_msg.go b/cl/cltest/error_msg.go
new file mode 100644
index 00000000..8b695cf4
--- /dev/null
+++ b/cl/cltest/error_msg.go
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cltest
+
+import (
+	"os"
+	"testing"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx/memfs"
+	"github.com/goplus/gop/scanner"
+)
+
+func Error(t *testing.T, msg, src string) {
+	ErrorEx(t, "main", "bar.gop", msg, src)
+}
+
+func ErrorEx(t *testing.T, pkgname, filename, msg, src string) {
+	fs := memfs.SingleFile("/foo", filename, src)
+	pkgs, err := parser.ParseFSDir(Conf.Fset, fs, "/foo", parser.Config{})
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("parser.ParseFSDir failed")
+	}
+	conf := *Conf
+	conf.NoFileLine = false
+	conf.RelativeBase = "/foo"
+	bar := pkgs[pkgname]
+	_, err = cl.NewPackage("", bar, &conf)
+	if err == nil {
+		t.Fatal("no error?")
+	}
+	if ret := err.Error(); ret != msg {
+		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
+	}
+}
+
+func ErrorAst(t *testing.T, pkgname, filename, msg, src string) {
+	f, _ := parser.ParseFile(Conf.Fset, filename, src, parser.AllErrors)
+	pkg := &ast.Package{
+		Name:  pkgname,
+		Files: map[string]*ast.File{filename: f},
+	}
+	conf := *Conf
+	conf.NoFileLine = false
+	conf.RelativeBase = "/foo"
+	_, err := cl.NewPackage("", pkg, &conf)
+	if err == nil {
+		t.Fatal("no error?")
+	}
+	if ret := err.Error(); ret != msg {
+		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
+	}
+}
diff --git a/cl/recorder_test.go b/cl/cltest/recorder.go
similarity index 86%
rename from cl/recorder_test.go
rename to cl/cltest/recorder.go
index 1e85a573..953331be 100644
--- a/cl/recorder_test.go
+++ b/cl/cltest/recorder.go
@@ -1,4 +1,20 @@
-package cl_test
+/*
+ * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cltest
 
 import (
 	"go/types"
diff --git a/cl/cltest/spx.go b/cl/cltest/spx.go
new file mode 100644
index 00000000..d291bc19
--- /dev/null
+++ b/cl/cltest/spx.go
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cltest
+
+import (
+	"bytes"
+	"os"
+	"testing"
+
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx/memfs"
+	"github.com/goplus/gop/scanner"
+	"github.com/goplus/mod/modfile"
+)
+
+func spxParserConf() parser.Config {
+	return parser.Config{
+		ClassKind: func(fname string) (isProj bool, ok bool) {
+			ext := modfile.ClassExt(fname)
+			c, ok := LookupClass(ext)
+			if ok {
+				isProj = c.IsProj(ext, fname)
+			}
+			return
+		},
+	}
+}
+
+func Spx(t *testing.T, gmx, spxcode, expected string) {
+	SpxEx(t, gmx, spxcode, expected, "index.tgmx", "bar.tspx")
+}
+
+func SpxEx(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile string) {
+	SpxWithConf(t, "gopSpxTest", Conf, gmx, spxcode, expected, gmxfile, spxfile, "")
+}
+
+func SpxEx2(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) {
+	SpxWithConf(t, "gopSpxTest", Conf, gmx, spxcode, expected, gmxfile, spxfile, resultFile)
+}
+
+func SpxWithConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) {
+	t.Run(name, func(t *testing.T) {
+		cl.SetDisableRecover(true)
+		defer cl.SetDisableRecover(false)
+
+		fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
+		if gmxfile == "" {
+			fs = memfs.SingleFile("/foo", spxfile, spxcode)
+		}
+		pkgs, err := parser.ParseFSDir(Conf.Fset, fs, "/foo", spxParserConf())
+		if err != nil {
+			scanner.PrintError(os.Stderr, err)
+			t.Fatal("ParseFSDir:", err)
+		}
+		bar := pkgs["main"]
+		pkg, err := cl.NewPackage("", bar, conf)
+		if err != nil {
+			t.Fatal("NewPackage:", err)
+		}
+		var b bytes.Buffer
+		err = pkg.WriteTo(&b, resultFile)
+		if err != nil {
+			t.Fatal("gogen.WriteTo failed:", err)
+		}
+		result := b.String()
+		if result != expected {
+			t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
+		}
+	})
+}
+
+func SpxErrorEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string) {
+	fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
+	pkgs, err := parser.ParseFSDir(Conf.Fset, fs, "/foo", spxParserConf())
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("ParseFSDir:", err)
+	}
+	conf := *Conf
+	conf.RelativeBase = "/foo"
+	conf.Recorder = nil
+	conf.NoFileLine = false
+	bar := pkgs["main"]
+	_, err = cl.NewPackage("", bar, &conf)
+	if err == nil {
+		t.Fatal("no error?")
+	}
+	if ret := err.Error(); ret != msg {
+		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
+	}
+}
diff --git a/cl/compile_c2go_test.go b/cl/compile_c2go_test.go
deleted file mode 100644
index 786397b8..00000000
--- a/cl/compile_c2go_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package cl_test
-
-import (
-	"path/filepath"
-	"strings"
-	"testing"
-)
-
-func lookupPub(pkgPath string) (pubfile string, err error) {
-	relPath := strings.TrimPrefix(pkgPath, "github.com/goplus/gop/")
-	return filepath.Join(gopRootDir, relPath, "c2go.a.pub"), nil
-}
-
-func TestHelloC2go(t *testing.T) {
-	gopClTest(t, `
-import "C"
-
-C.printf C"Hello, world!\n"
-`, `package main
-
-import (
-	"github.com/goplus/gop/cl/internal/libc"
-	"unsafe"
-)
-
-func main() {
-	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
-}
-`)
-}
-
-func TestHelloC2go2(t *testing.T) {
-	gopClTest(t, `
-import "C/github.com/goplus/gop/cl/internal/libc"
-
-C.printf C"Hello, world!\n"
-`, `package main
-
-import (
-	"github.com/goplus/gop/cl/internal/libc"
-	"unsafe"
-)
-
-func main() {
-	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
-}
-`)
-}
-
-func TestHelloC2go3(t *testing.T) {
-	gopClTest(t, `
-import "C/libc"
-
-C.printf C"Hello, world!\n"
-`, `package main
-
-import (
-	"github.com/goplus/gop/cl/internal/libc"
-	"unsafe"
-)
-
-func main() {
-	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
-}
-`)
-}
-
-func TestErrHelloC2go(t *testing.T) {
-	codeErrorTest(t, `bar.gop:7:3: confliction: printf declared both in "github.com/goplus/gop/cl/internal/libc" and "github.com/goplus/gop/cl/internal/libc"`, `
-import (
-	"C"
-	"C/libc"
-)
-
-C.printf C"Hello, world!\n"
-`)
-}
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index d205537b..e6a2539e 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -17,125 +17,30 @@
 package cl_test
 
 import (
-	"bytes"
-	"os"
 	"testing"
 
 	"github.com/goplus/gop/cl"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/fsx/memfs"
-	"github.com/goplus/gop/scanner"
-	"github.com/goplus/mod/modfile"
+	"github.com/goplus/gop/cl/cltest"
 )
 
-func lookupClass(ext string) (c *modfile.Project, ok bool) {
-	switch ext {
-	case ".tgmx", ".tspx":
-		return &modfile.Project{
-			Ext: ".tgmx", Class: "*MyGame",
-			Works:    []*modfile.Class{{Ext: ".tspx", Class: "Sprite"}},
-			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx", "math"}}, true
-	case ".t2gmx", ".t2spx", ".t2spx2":
-		return &modfile.Project{
-			Ext: ".t2gmx", Class: "Game",
-			Works: []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"},
-				{Ext: ".t2spx2", Class: "Sprite2"}},
-			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
-	case "_t3spx.gox", ".t3spx2":
-		return &modfile.Project{
-			Works: []*modfile.Class{{Ext: "_t3spx.gox", Class: "Sprite"},
-				{Ext: ".t3spx2", Class: "Sprite2"}},
-			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
-	case "_spx.gox":
-		return &modfile.Project{
-			Ext: "_spx.gox", Class: "Game",
-			Works:    []*modfile.Class{{Ext: "_spx.gox", Class: "Sprite"}},
-			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx3", "math"},
-			Import:   []*modfile.Import{{Path: "github.com/goplus/gop/cl/internal/spx3/jwt"}}}, true
-	case "_xtest.gox":
-		return &modfile.Project{
-			Ext: "_xtest.gox", Class: "App",
-			Works:    []*modfile.Class{{Ext: "_xtest.gox", Class: "Case"}},
-			PkgPaths: []string{"github.com/goplus/gop/test", "testing"}}, true
-	}
-	return
-}
-
-func spxParserConf() parser.Config {
-	return parser.Config{
-		ClassKind: func(fname string) (isProj bool, ok bool) {
-			ext := modfile.ClassExt(fname)
-			c, ok := lookupClass(ext)
-			if ok {
-				isProj = c.IsProj(ext, fname)
-			}
-			return
-		},
-	}
-}
-
 func gopSpxTest(t *testing.T, gmx, spxcode, expected string) {
-	gopSpxTestEx(t, gmx, spxcode, expected, "index.tgmx", "bar.tspx")
+	cltest.SpxEx(t, gmx, spxcode, expected, "index.tgmx", "bar.tspx")
 }
 
 func gopSpxTestEx(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile string) {
-	gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile, "")
+	cltest.SpxWithConf(t, "gopSpxTest", cltest.Conf, gmx, spxcode, expected, gmxfile, spxfile, "")
 }
 
 func gopSpxTestEx2(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) {
-	gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile, resultFile)
+	cltest.SpxWithConf(t, "gopSpxTest", cltest.Conf, gmx, spxcode, expected, gmxfile, spxfile, resultFile)
 }
 
 func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) {
-	t.Run(name, func(t *testing.T) {
-		cl.SetDisableRecover(true)
-		defer cl.SetDisableRecover(false)
-
-		fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
-		if gmxfile == "" {
-			fs = memfs.SingleFile("/foo", spxfile, spxcode)
-		}
-		pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
-		if err != nil {
-			scanner.PrintError(os.Stderr, err)
-			t.Fatal("ParseFSDir:", err)
-		}
-		bar := pkgs["main"]
-		pkg, err := cl.NewPackage("", bar, conf)
-		if err != nil {
-			t.Fatal("NewPackage:", err)
-		}
-		var b bytes.Buffer
-		err = pkg.WriteTo(&b, resultFile)
-		if err != nil {
-			t.Fatal("gogen.WriteTo failed:", err)
-		}
-		result := b.String()
-		if result != expected {
-			t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
-		}
-	})
+	cltest.SpxWithConf(t, name, conf, gmx, spxcode, expected, gmxfile, spxfile, resultFile)
 }
 
 func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string) {
-	fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
-	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
-	if err != nil {
-		scanner.PrintError(os.Stderr, err)
-		t.Fatal("ParseFSDir:", err)
-	}
-	conf := *gblConf
-	conf.RelativeBase = "/foo"
-	conf.Recorder = nil
-	conf.NoFileLine = false
-	bar := pkgs["main"]
-	_, err = cl.NewPackage("", bar, &conf)
-	if err == nil {
-		t.Fatal("no error?")
-	}
-	if ret := err.Error(); ret != msg {
-		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
-	}
+	cltest.SpxErrorEx(t, msg, gmx, spxcode, gmxfile, spxfile)
 }
 
 func TestSpxNoGame(t *testing.T) {
@@ -305,16 +210,6 @@ func main() {
 `)
 }
 
-func _TestMatrix1(t *testing.T) {
-	gopSpxTest(t, `
-echo [
-	1, 2, 3
-	4, 5, 6
-]
-`, ``, `
-`)
-}
-
 func TestEnvOp(t *testing.T) {
 	gopSpxTest(t, `
 echo ${PATH}, $id
@@ -803,7 +698,7 @@ func (this *Kai) Main() {
 }
 
 func TestSpxMainEntry(t *testing.T) {
-	conf := *gblConf
+	conf := *cltest.Conf
 	conf.Importer = nil
 	conf.NoAutoGenMain = false
 
@@ -1397,6 +1292,42 @@ type foo struct {
 `, "foo.gox")
 }
 
+func TestGopxOverload(t *testing.T) {
+	gopClTestFile(t, `
+func addString(a, b string) string {
+	return a + b
+}
+
+func addInt(a, b int) int {
+	return a + b
+}
+
+func add = (
+	addInt
+	func(a, b float64) float64 {
+		return a + b
+	}
+	addString
+)
+`, `package main
+
+const Gopo_Rect_add = ".addInt,,.addString"
+
+type Rect struct {
+}
+
+func (this *Rect) addString(a string, b string) string {
+	return a + b
+}
+func (this *Rect) addInt(a int, b int) int {
+	return a + b
+}
+func (this *Rect) add__1(a float64, b float64) float64 {
+	return a + b
+}
+`, "Rect.gox")
+}
+
 func TestClassFileGopx(t *testing.T) {
 	gopClTestFile(t, `
 var (
diff --git a/cl/compile_test.go b/cl/compile_test.go
index d709cf44..851e1530 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -17,19 +17,13 @@
 package cl_test
 
 import (
-	"bytes"
 	"os"
 	"sync"
 	"testing"
 
 	"github.com/goplus/gogen"
-	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/fsx/memfs"
-	"github.com/goplus/gop/scanner"
-	"github.com/goplus/gop/token"
-	"github.com/goplus/mod/env"
+	"github.com/goplus/gop/cl/cltest"
 )
 
 const (
@@ -37,88 +31,40 @@ const (
 )
 
 var (
-	gblFset     *token.FileSet
-	gblConf     *cl.Config
 	gblConfLine *cl.Config
 )
 
 func init() {
-	gogen.SetDebug(gogen.DbgFlagAll)
-	cl.SetDebug(cl.DbgFlagAll | cl.FlagNoMarkAutogen)
-	gblFset = token.NewFileSet()
-	imp := gop.NewImporter(nil, &env.Gop{Root: gopRootDir, Version: "1.0"}, gblFset)
-	gblConf = &cl.Config{
-		Fset:          gblFset,
-		Importer:      imp,
-		Recorder:      gopRecorder{},
-		LookupClass:   lookupClass,
-		LookupPub:     lookupPub,
-		C2goBase:      "github.com/goplus/gop/cl/internal",
-		NoFileLine:    true,
-		NoAutoGenMain: true,
-	}
+	cltest.Gop.Root = gopRootDir
+	conf := cltest.Conf
 	gblConfLine = &cl.Config{
-		Fset:          gblFset,
-		Importer:      imp,
-		Recorder:      gopRecorder{},
-		LookupClass:   lookupClass,
-		LookupPub:     lookupPub,
-		C2goBase:      "github.com/goplus/gop/cl/internal",
+		Fset:          conf.Fset,
+		Importer:      conf.Importer,
+		Recorder:      cltest.Conf.Recorder,
+		LookupClass:   cltest.LookupClass,
 		NoFileLine:    false,
 		NoAutoGenMain: true,
 	}
 }
 
 func gopClNamedTest(t *testing.T, name string, gopcode, expected string) {
-	t.Run(name, func(t *testing.T) {
-		gopClTest(t, gopcode, expected)
-	})
+	cltest.Named(t, name, gopcode, expected)
 }
 
 func gopClTest(t *testing.T, gopcode, expected string) {
-	gopClTestEx(t, gblConf, "main", gopcode, expected)
+	cltest.DoExt(t, cltest.Conf, "main", gopcode, expected)
 }
 
 func gopClTestFile(t *testing.T, gopcode, expected string, fname string) {
-	fs := memfs.SingleFile("/foo", fname, gopcode)
-	gopClTestFS(t, gblConf, fs, "main", expected)
+	cltest.DoWithFname(t, gopcode, expected, fname)
 }
 
 func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) {
-	fs := memfs.SingleFile("/foo", "bar.gop", gopcode)
-	gopClTestFS(t, conf, fs, pkgname, expected)
+	cltest.DoExt(t, conf, pkgname, gopcode, expected)
 }
 
 func gopMixedClTest(t *testing.T, pkgname, gocode, gopcode, expected string, outline ...bool) {
-	conf := *gblConf
-	conf.Outline = (outline != nil && outline[0])
-	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
-	gopClTestFS(t, &conf, fs, pkgname, expected)
-}
-
-func gopClTestFS(t *testing.T, conf *cl.Config, fs parser.FileSystem, pkgname, expected string) {
-	cl.SetDisableRecover(true)
-	defer cl.SetDisableRecover(false)
-
-	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{Mode: parser.ParseComments})
-	if err != nil {
-		scanner.PrintError(os.Stderr, err)
-		t.Fatal("ParseFSDir:", err)
-	}
-	bar := pkgs[pkgname]
-	pkg, err := cl.NewPackage("github.com/goplus/gop/cl", bar, conf)
-	if err != nil {
-		t.Fatal("NewPackage:", err)
-	}
-	var b bytes.Buffer
-	err = pkg.WriteTo(&b)
-	if err != nil {
-		t.Fatal("gogen.WriteTo failed:", err)
-	}
-	result := b.String()
-	if result != expected {
-		t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
-	}
+	cltest.Mixed(t, pkgname, gocode, gopcode, expected, outline...)
 }
 
 func TestTypeDoc(t *testing.T) {
@@ -3670,7 +3616,7 @@ func main() {
 	fmt.Println("init")
 }
 `)
-	gopClTestEx(t, gblConf, "bar", `package bar
+	gopClTestEx(t, cltest.Conf, "bar", `package bar
 println("init")
 `, `package bar
 
@@ -3773,7 +3719,7 @@ func main() {
 }
 
 func TestMainEntry(t *testing.T) {
-	conf := *gblConf
+	conf := *cltest.Conf
 	conf.NoAutoGenMain = false
 
 	gopClTestEx(t, &conf, "main", `
@@ -3899,7 +3845,7 @@ func main() {
 }
 
 func TestCommentLineRoot(t *testing.T) {
-	conf := *gblConf
+	conf := *cltest.Conf
 	conf.NoFileLine = false
 	conf.RelativeBase = "/foo/root"
 	var src = `
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index ddc65610..a5c838ce 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -18,58 +18,23 @@ package cl_test
 
 import (
 	"fmt"
-	"os"
 	"path/filepath"
 	"runtime"
 	"testing"
 
-	"github.com/goplus/gop/ast"
-	"github.com/goplus/gop/cl"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/parser/fsx/memfs"
-	"github.com/goplus/gop/scanner"
+	"github.com/goplus/gop/cl/cltest"
 )
 
 func codeErrorTest(t *testing.T, msg, src string) {
-	codeErrorTestEx(t, "main", "bar.gop", msg, src)
+	cltest.ErrorEx(t, "main", "bar.gop", msg, src)
 }
 
 func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) {
-	fs := memfs.SingleFile("/foo", filename, src)
-	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
-	if err != nil {
-		scanner.PrintError(os.Stderr, err)
-		t.Fatal("parser.ParseFSDir failed")
-	}
-	conf := *gblConf
-	conf.NoFileLine = false
-	conf.RelativeBase = "/foo"
-	bar := pkgs[pkgname]
-	_, err = cl.NewPackage("", bar, &conf)
-	if err == nil {
-		t.Fatal("no error?")
-	}
-	if ret := err.Error(); ret != msg {
-		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
-	}
+	cltest.ErrorEx(t, pkgname, filename, msg, src)
 }
 
 func codeErrorTestAst(t *testing.T, pkgname, filename, msg, src string) {
-	f, _ := parser.ParseFile(gblFset, filename, src, parser.AllErrors)
-	pkg := &ast.Package{
-		Name:  pkgname,
-		Files: map[string]*ast.File{filename: f},
-	}
-	conf := *gblConf
-	conf.NoFileLine = false
-	conf.RelativeBase = "/foo"
-	_, err := cl.NewPackage("", pkg, &conf)
-	if err == nil {
-		t.Fatal("no error?")
-	}
-	if ret := err.Error(); ret != msg {
-		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
-	}
+	cltest.ErrorAst(t, pkgname, filename, msg, src)
 }
 
 func TestErrVargCommand(t *testing.T) {
@@ -1063,3 +1028,46 @@ func _() {
 }
 `)
 }
+
+func TestOverloadFuncDecl(t *testing.T) {
+	codeErrorTest(t, "bar.gop:3:2: invalid func (foo).mulInt", `
+func mul = (
+	(foo).mulInt
+)
+`)
+	codeErrorTest(t, "bar.gop:2:7: invalid recv type *foo", `
+func (*foo).mul = (
+	(foo).mulInt
+)
+`)
+	codeErrorTest(t, "bar.gop:3:2: invalid recv type (foo2)", `
+func (foo).mul = (
+	(foo2).mulInt
+)
+`)
+	codeErrorTest(t, "bar.gop:3:2: invalid method mulInt", `
+func (foo).mul = (
+	mulInt
+)
+`)
+	codeErrorTest(t, "bar.gop:3:2: invalid recv type (**foo)", `
+func (foo).mul = (
+	(**foo).mulInt
+)
+`)
+	codeErrorTest(t, `bar.gop:3:9: unknown func ("ok")`, `
+func mul = (
+	println("ok")
+)
+`)
+	codeErrorTest(t, "bar.gop:3:2: invalid method func(){}", `
+func (foo).mul = (
+	func(){}
+)
+`)
+	codeErrorTest(t, "bar.gop:2:12: invalid overload operator ++", `
+func (foo).++ = (
+	mulInt
+)
+`)
+}
diff --git a/cl/run_test.go b/cl/run_test.go
index 502497d2..8531231c 100644
--- a/cl/run_test.go
+++ b/cl/run_test.go
@@ -12,6 +12,7 @@ import (
 	"testing"
 
 	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/cl/cltest"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/scanner"
@@ -84,7 +85,7 @@ func genGo(t *testing.T, conf *cl.Config, gopcode string) []byte {
 	defer cl.SetDisableRecover(false)
 
 	fs := memfs.SingleFile("/foo", "bar.gop", gopcode)
-	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{Mode: parser.ParseComments})
+	pkgs, err := parser.ParseFSDir(cltest.Conf.Fset, fs, "/foo", parser.Config{Mode: parser.ParseComments})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
 		t.Fatal("ParseFSDir:", err)
@@ -102,7 +103,7 @@ func genGo(t *testing.T, conf *cl.Config, gopcode string) []byte {
 }
 
 func testRun(t *testing.T, gopcode, expected string) {
-	code := genGo(t, gblConf, gopcode)
+	code := genGo(t, cltest.Conf, gopcode)
 	result := goRun(t, code)
 	if result != expected {
 		t.Fatalf("=> Result:\n%s\n=> Expected:\n%s\n", result, expected)
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index 4d35387f..40a079d3 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -7,6 +7,7 @@ import (
 	"testing"
 
 	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/cl/cltest"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/parser/fsx/memfs"
 )
@@ -223,12 +224,12 @@ func mixedErrorTest(t *testing.T, msg, gocode, gopcode string) {
 
 func mixedErrorTestEx(t *testing.T, pkgname, msg, gocode, gopcode string) {
 	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
-	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
+	pkgs, err := parser.ParseFSDir(cltest.Conf.Fset, fs, "/foo", parser.Config{})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
 		t.Fatal("parser.ParseFSDir failed")
 	}
-	conf := *gblConf
+	conf := *cltest.Conf
 	conf.NoFileLine = false
 	conf.RelativeBase = "/foo"
 	bar := pkgs[pkgname]
diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go
index 7a070f3c..e9bf58c8 100644
--- a/cmd/internal/test/test.go
+++ b/cmd/internal/test/test.go
@@ -75,7 +75,7 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 	defer conf.UpdateCache()
 
-	confCmd := &gocmd.Config{Gop: conf.Gop}
+	confCmd := conf.NewGoCmdConf()
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
 		test(proj, conf, confCmd)
diff --git a/parser/_testdata/c2gohello/hello.gop b/parser/_testdata/c2gohello/hello.gop
index fb671ed3..d40ddf9c 100644
--- a/parser/_testdata/c2gohello/hello.gop
+++ b/parser/_testdata/c2gohello/hello.gop
@@ -1,3 +1,3 @@
 import "C"
 
-C.printf C"Hello, world!\n"
+C.printf c"Hello, world!\n"
diff --git a/parser/_testdata/mytest/mytest.gop b/parser/_testdata/mytest/mytest.gop
index 8147f3bf..5b49b97b 100644
--- a/parser/_testdata/mytest/mytest.gop
+++ b/parser/_testdata/mytest/mytest.gop
@@ -1,4 +1,4 @@
-package testdata
+package demo
 
 import (
 	"go/token"
diff --git a/parser/_testdata/mytest/parser.expect b/parser/_testdata/mytest/parser.expect
index 4f92c2d9..8cfb4a85 100644
--- a/parser/_testdata/mytest/parser.expect
+++ b/parser/_testdata/mytest/parser.expect
@@ -1,4 +1,4 @@
-package testdata
+package demo
 
 file mytest.gop
 ast.GenDecl:
diff --git a/parser/_testdata/pystr/parser.expect b/parser/_testdata/pystr/parser.expect
new file mode 100644
index 00000000..4bfa5329
--- /dev/null
+++ b/parser/_testdata/pystr/parser.expect
@@ -0,0 +1,25 @@
+package main
+
+file pystr.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: print
+              Args:
+                ast.BasicLit:
+                  Kind: PYSTRING
+                  Value: "Hello"
diff --git a/parser/_testdata/pystr/pystr.gop b/parser/_testdata/pystr/pystr.gop
new file mode 100644
index 00000000..a9356c78
--- /dev/null
+++ b/parser/_testdata/pystr/pystr.gop
@@ -0,0 +1,1 @@
+print py"Hello"
diff --git a/printer/printer_test.go b/printer/printer_test.go
index d38d48bf..8f7976e9 100644
--- a/printer/printer_test.go
+++ b/printer/printer_test.go
@@ -29,7 +29,7 @@ import (
 )
 
 const (
-	dataDir  = "testdata"
+	dataDir  = "demo"
 	tabwidth = 8
 )
 
@@ -501,7 +501,7 @@ func TestWriteErrors(t *testing.T) {
 	}
 }
 
-// TextX is a skeleton test that can be filled in for debugging one-off cases.
+// TestX is a skeleton test that can be filled in for debugging one-off cases.
 // Do not remove.
 func TestX(t *testing.T) {
 	const src = `
diff --git a/testdata/_helloc2go/README.md b/testdata/_helloc2go/README.md
deleted file mode 100644
index 2071d303..00000000
--- a/testdata/_helloc2go/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-This is an example to show how Go+ interacts with C.
-
-```go
-import "C"
-
-C.printf C"Hello, c2go!\n"
-C.fprintf C.stderr, C"Hi, %7.1f\n", 3.14
-```
-
-Here we use `import "C"` to import libc. It's an abbreviation for `import "C/github.com/goplus/libc"`. It is equivalent to the following code:
-
-```go
-import "C/github.com/goplus/libc"
-
-C.printf C"Hello, c2go!\n"
-C.fprintf C.stderr, C"Hi, %7.1f\n", 3.14
-```
-
-In this example we call two C standard functions `printf` and `fprintf`, pass a C variable `stderr` and two C strings in the form of `C"xxx"`.
-
-Run `gop run .` to see the output of this example:
-
-```
-Hello, c2go!
-Hi,     3.1
-```
-
-### Give a Star! ⭐
-
-If you like or are using Go+ to learn or start your projects, please give it a star. Thanks!
diff --git a/testdata/_helloc2go/go.mod b/testdata/_helloc2go/go.mod
deleted file mode 100644
index 49ca4aeb..00000000
--- a/testdata/_helloc2go/go.mod
+++ /dev/null
@@ -1,31 +0,0 @@
-module hello2go
-
-go 1.21.1
-
-require github.com/goplus/libc v0.3.15
-
-require (
-	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/google/gofuzz v1.0.0 // indirect
-	github.com/goplus/c2go v0.7.15 // indirect
-	github.com/goplus/gogen v1.12.0 // indirect
-	github.com/goplus/mod v0.11.5 // indirect
-	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
-	github.com/modern-go/reflect2 v1.0.2 // indirect
-	github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/qiniu/x v1.13.0 // indirect
-	github.com/stretchr/objx v0.1.0 // indirect
-	github.com/stretchr/testify v1.3.0 // indirect
-	github.com/yuin/goldmark v1.4.13 // indirect
-	golang.org/x/crypto v0.13.0 // indirect
-	golang.org/x/mod v0.12.0 // indirect
-	golang.org/x/net v0.15.0 // indirect
-	golang.org/x/sync v0.3.0 // indirect
-	golang.org/x/sys v0.12.0 // indirect
-	golang.org/x/term v0.12.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
-	golang.org/x/tools v0.13.0 // indirect
-	golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect
-)
diff --git a/testdata/_helloc2go/go.sum b/testdata/_helloc2go/go.sum
deleted file mode 100644
index 6339eb2c..00000000
--- a/testdata/_helloc2go/go.sum
+++ /dev/null
@@ -1,60 +0,0 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.15/go.mod h1:SKSY0OvbO9JBkHRjyiJz3vz0rHpwPETjNj6AQ/rQZOg=
-github.com/goplus/gogen v1.12.0/go.mod h1:Ek4bXv4xzfBiFuHC6yfkzHXrhUHXfrM9QyXtMusdkGo=
-github.com/goplus/libc v0.3.15 h1:xfo4Lc66Sf7Dyel+gAT+5fCG0XOB7PxRE4892MH5zgg=
-github.com/goplus/libc v0.3.15/go.mod h1:ExS/KlOepeNJkY10ykY1Qgg6GPql+AItHNNsXuIZwgw=
-github.com/goplus/mod v0.11.5/go.mod h1:NDC5E+XOT8vcJCMjqKhLDJHTHX7lyVN4Vbfi2U7dBhs=
-github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0=
-github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/qiniu/x v1.13.0/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
-golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/testdata/_helloc2go/hello.gop b/testdata/_helloc2go/hello.gop
deleted file mode 100644
index b857fa56..00000000
--- a/testdata/_helloc2go/hello.gop
+++ /dev/null
@@ -1,4 +0,0 @@
-import "C"
-
-C.printf C"Hello, c2go!\n"
-C.fprintf C.stderr, C"Hi, %7.1f\n", 3.14
diff --git a/testdata/gop-sample/b.gop b/testdata/gop-sample/b.gop
deleted file mode 100644
index f87ce8ec..00000000
--- a/testdata/gop-sample/b.gop
+++ /dev/null
@@ -1,9 +0,0 @@
-package main
-
-import (
-	ab "github.com/goplus/gop/testdata/gop-sample/cpkag/b"
-)
-
-a()
-Ab()
-ab.Ab()
diff --git a/x/build/build_test.go b/x/build/build_test.go
index 2d26bfe9..9944747b 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -500,11 +500,11 @@ func TestError(t *testing.T) {
 	if err == nil {
 		t.Fatal("BuildFile: no error?")
 	}
-	_, err = ctx.BuildDir("./testdata/nofound")
+	_, err = ctx.BuildDir("./demo/nofound")
 	if err == nil {
 		t.Fatal("BuildDir: no error?")
 	}
-	_, err = ctx.BuildFSDir(fsx.Local, "./testdata/nofound")
+	_, err = ctx.BuildFSDir(fsx.Local, "./demo/nofound")
 	if err == nil {
 		t.Fatal("BuildDir: no error?")
 	}
@@ -516,7 +516,7 @@ func TestError(t *testing.T) {
 	if err == nil {
 		t.Fatal("ParseFile: no error?")
 	}
-	_, err = ctx.ParseFile("./testdata/nofound/main.gop", nil)
+	_, err = ctx.ParseFile("./demo/nofound/main.gop", nil)
 	if err == nil {
 		t.Fatal("ParseFile: no error?")
 	}
diff --git a/x/typesutil/check_test.go b/x/typesutil/check_test.go
index 5e784e16..07da8446 100644
--- a/x/typesutil/check_test.go
+++ b/x/typesutil/check_test.go
@@ -78,7 +78,7 @@ func checkInfo(fset *token.FileSet, files []*ast.File, gofiles []*goast.File) (*
 		Implicits:  make(map[ast.Node]types.Object),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 		Scopes:     make(map[ast.Node]*types.Scope),
-		Overloads:  make(map[*ast.Ident][]types.Object),
+		Overloads:  make(map[*ast.Ident]types.Object),
 	}
 	ginfo := &types.Info{
 		Types:      make(map[goast.Expr]types.TypeAndValue),
@@ -196,3 +196,83 @@ func TestBadFile(t *testing.T) {
 	_ = checker.Files([]*goast.File{{Name: goast.NewIdent("main")}},
 		[]*ast.File{{Name: ast.NewIdent("main")}})
 }
+
+func TestCheckOverload(t *testing.T) {
+	fset := token.NewFileSet()
+	info, ginfo, err := checkFiles(fset, "main.gop", `
+type foo struct {
+}
+
+func (a *foo) mulInt(b int) *foo {
+	return a
+}
+
+func (a *foo) mulFoo(b *foo) *foo {
+	return a
+}
+
+func (foo).mul = (
+	(foo).mulInt
+	(foo).mulFoo
+)
+func addInt0() {
+}
+
+func addInt1(i int) {
+}
+
+func addInt2(i, j int) {
+}
+
+var addInt3 = func(i, j, k int) {
+}
+
+func add = (
+	addInt0
+	addInt1
+	addInt2
+	addInt3
+	func(a, b string) string {
+		return a + b
+	}
+)
+
+var a, b *foo
+var c = a.mul(100)
+var d = a.mul(c)
+
+func init() {
+	add 100, 200
+	add 100, 200, 300
+	add("hello", "world")
+}
+`, "", "", "", "")
+	if err != nil || info == nil || ginfo == nil {
+		t.Fatalf("check failed: %v", err)
+	}
+	for use, ovDeclObj := range info.Overloads {
+		o := info.ObjectOf(use)
+		declObj, ovObjs := info.OverloadOf(use)
+		if ovDeclObj != declObj {
+			t.Fatal("bad overload", o)
+		}
+		found := false
+		for _, ovObj := range ovObjs {
+			if o == ovObj {
+				found = true
+				break
+			}
+		}
+		if !found {
+			t.Fatal("bad overload", o)
+		}
+	}
+	for use, o := range info.Uses {
+		declObj, ovObjs := info.OverloadOf(use)
+		if declObj != nil && ovObjs != nil {
+			if info.Overloads[use] == nil {
+				t.Fatal("bad overload", o)
+			}
+		}
+	}
+}
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index d70fbe67..ef1ca10e 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -91,7 +91,7 @@ func parseMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string,
 		Implicits:  make(map[ast.Node]types.Object),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 		Scopes:     make(map[ast.Node]*types.Scope),
-		Overloads:  make(map[*ast.Ident][]types.Object),
+		Overloads:  make(map[*ast.Ident]types.Object),
 	}
 	ginfo := &types.Info{
 		Types:      make(map[goast.Expr]types.TypeAndValue),
@@ -129,7 +129,7 @@ func parseSource(fset *token.FileSet, filename string, src interface{}, mode par
 		Implicits:  make(map[ast.Node]types.Object),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 		Scopes:     make(map[ast.Node]*types.Scope),
-		Overloads:  make(map[*ast.Ident][]types.Object),
+		Overloads:  make(map[*ast.Ident]types.Object),
 	}
 	check := typesutil.NewChecker(conf, chkOpts, nil, info)
 	err = check.Files(nil, []*ast.File{f})
@@ -179,6 +179,10 @@ func testGopInfoEx(t *testing.T, mod *gopmod.Module, name string, src string, go
 	list = append(list, defsList(fset, info.Defs, true)...)
 	list = append(list, "== uses ==")
 	list = append(list, usesList(fset, info.Uses)...)
+	if len(info.Overloads) > 0 {
+		list = append(list, "== overloads ==")
+		list = append(list, overloadsList(fset, info.Overloads)...)
+	}
 	result := strings.Join(list, "\n")
 	t.Log(result)
 	if result != expect {
@@ -341,6 +345,20 @@ func goUsesList(fset *token.FileSet, uses map[*goast.Ident]types.Object) []strin
 	return sortItems(items)
 }
 
+func overloadsList(fset *token.FileSet, overloads map[*ast.Ident]types.Object) []string {
+	var items []string
+	for expr, obj := range overloads {
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s",
+			posn.Line, posn.Column, expr,
+			obj)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
 /*
 func selectionList(fset *token.FileSet, sels map[*ast.SelectorExpr]*types.Selection) []string {
 	var items []string
@@ -1163,7 +1181,21 @@ func OnKey__a(a, b string, v ...int) {
 023: 33:26 | x                   | var x int
 024: 34: 9 | x                   | var x int
 025: 36: 1 | OnKey               | func main.OnKey__a(a string, b string, v ...int)
-026: 37: 1 | OnKey               | func main.OnKey__a(a string, b string, v ...int)`)
+026: 37: 1 | OnKey               | func main.OnKey__a(a string, b string, v ...int)
+== overloads ==
+000: 14: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+001: 16: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+002: 18: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+003: 20: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+004: 22: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+005: 24: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+006: 26: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+007: 28: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+008: 30: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+009: 32: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+010: 33: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+011: 36: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})
+012: 37: 1 | OnKey               | func main.OnKey(__gop_overload_args__ interface{_()})`)
 }
 
 func TestMixedOverload2(t *testing.T) {
@@ -1387,7 +1419,18 @@ func OnKey__a(a, b string, v ...int) {
 028: 31: 3 | onKey               | func (*main.N).OnKey__6(a []string, b []string, fn func(key string))
 029: 31:16 | nil                 | nil
 030: 33: 1 | n                   | var n *main.N
-031: 33: 3 | onKey               | func (*main.N).OnKey__8(x int, y int)`)
+031: 33: 3 | onKey               | func (*main.N).OnKey__8(x int, y int)
+== overloads ==
+000: 15: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+001: 17: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+002: 19: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+003: 21: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+004: 23: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+005: 25: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+006: 27: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+007: 29: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+008: 31: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})
+009: 33: 3 | onKey               | func (main.N).OnKey(__gop_overload_args__ interface{_()})`)
 }
 
 func TestMixedOverload3(t *testing.T) {
@@ -1432,7 +1475,12 @@ func (p *N) Test__1(n int) {
 003:  5: 1 | n                   | var n main.N
 004:  5: 3 | test                | func (*main.N).Test__0()
 005:  6: 1 | n                   | var n main.N
-006:  6: 3 | test                | func (*main.N).Test__1(n int)`)
+006:  6: 3 | test                | func (*main.N).Test__1(n int)
+== overloads ==
+000:  2: 1 | Test                | func main.Test(__gop_overload_args__ interface{_()})
+001:  3: 1 | Test                | func main.Test(__gop_overload_args__ interface{_()})
+002:  5: 3 | test                | func (main.N).Test(__gop_overload_args__ interface{_()})
+003:  6: 3 | test                | func (main.N).Test(__gop_overload_args__ interface{_()})`)
 }
 
 func TestOverloadNamed(t *testing.T) {
@@ -1476,7 +1524,12 @@ d := bar.Var(bar.M)
 010:  7: 6 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
 011:  7:10 | Var                 | func github.com/goplus/gop/cl/internal/overload/bar.Gopx_Var_Cast__1[T map[string]any]() *github.com/goplus/gop/cl/internal/overload/bar.Var__1[T]
 012:  7:14 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
-013:  7:18 | M                   | type github.com/goplus/gop/cl/internal/overload/bar.M = map[string]any`)
+013:  7:18 | M                   | type github.com/goplus/gop/cl/internal/overload/bar.M = map[string]any
+== overloads ==
+000:  4:11 | Var                 | type github.com/goplus/gop/cl/internal/overload/bar.Var = func(__gop_overload_args__ interface{_()})
+001:  5:11 | Var                 | type github.com/goplus/gop/cl/internal/overload/bar.Var = func(__gop_overload_args__ interface{_()})
+002:  6:10 | Var                 | type github.com/goplus/gop/cl/internal/overload/bar.Var = func(__gop_overload_args__ interface{_()})
+003:  7:10 | Var                 | type github.com/goplus/gop/cl/internal/overload/bar.Var = func(__gop_overload_args__ interface{_()})`)
 }
 
 func TestMixedOverloadNamed(t *testing.T) {
@@ -1536,7 +1589,12 @@ func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
 004:  4: 6 | Var                 | func main.Gopx_Var_Cast__0[T main.basetype]() *main.Var__0[T]
 005:  4:10 | string              | type string
 006:  5: 6 | Var                 | func main.Gopx_Var_Cast__1[T map[string]any]() *main.Var__1[T]
-007:  5:10 | M                   | type main.M = map[string]any`)
+007:  5:10 | M                   | type main.M = map[string]any
+== overloads ==
+000:  2: 7 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
+001:  3: 7 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
+002:  4: 6 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
+003:  5: 6 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})`)
 }
 
 func TestMixedRawNamed(t *testing.T) {
@@ -1675,7 +1733,11 @@ func onCloned() {
 009: 14: 8 | info                | type main.info struct{x int; y int}
 010: 15: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
 011: 15: 9 | info                | type main.info struct{x int; y int}
-012: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)`)
+012: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)
+== overloads ==
+000: 13: 2 | clone               | func (github.com/goplus/gop/cl/internal/spx.Sprite).Clone(__gop_overload_args__ interface{_()})
+001: 14: 2 | clone               | func (github.com/goplus/gop/cl/internal/spx.Sprite).Clone(__gop_overload_args__ interface{_()})
+002: 15: 2 | clone               | func (github.com/goplus/gop/cl/internal/spx.Sprite).Clone(__gop_overload_args__ interface{_()})`)
 }
 
 func TestScopesInfo(t *testing.T) {
@@ -2018,22 +2080,26 @@ Mul 100,200,300
 008:  7: 9 | a                   *ast.Ident                     | var     : float64 | variable
 009:  7: 9 | a * b               *ast.BinaryExpr                | value   : float64 | value
 010:  7:13 | b                   *ast.Ident                     | var     : float64 | variable
-011: 13:15 | int                 *ast.Ident                     | type    : int | type
-012: 13:20 | int                 *ast.Ident                     | type    : int | type
-013: 14:10 | x                   *ast.Ident                     | var     : int | variable
-014: 14:10 | x * y               *ast.BinaryExpr                | value   : int | value
-015: 14:10 | x * y * z           *ast.BinaryExpr                | value   : int | value
-016: 14:14 | y                   *ast.Ident                     | var     : int | variable
-017: 14:18 | z                   *ast.Ident                     | var     : int | variable
-018: 18: 1 | Mul                 *ast.Ident                     | value   : func(a int, b int) int | value
-019: 18: 1 | Mul 100, 200        *ast.CallExpr                  | value   : int | value
-020: 18: 5 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
-021: 18: 9 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
-022: 19: 1 | Mul                 *ast.Ident                     | value   : func(x int, y int, z int) int | value
-023: 19: 1 | Mul 100, 200, 300   *ast.CallExpr                  | value   : int | value
-024: 19: 5 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
-025: 19: 9 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
-026: 19:13 | 300                 *ast.BasicLit                  | value   : untyped int = 300 | constant
+011: 13: 2 | func(x, y, z int) int *ast.FuncType                  | type    : func(x int, y int, z int) int | type
+012: 13: 2 | func(x, y, z int) int {
+	return x * y * z
+} *ast.FuncLit                   | value   : func(x int, y int, z int) int | value
+013: 13:15 | int                 *ast.Ident                     | type    : int | type
+014: 13:20 | int                 *ast.Ident                     | type    : int | type
+015: 14:10 | x                   *ast.Ident                     | var     : int | variable
+016: 14:10 | x * y               *ast.BinaryExpr                | value   : int | value
+017: 14:10 | x * y * z           *ast.BinaryExpr                | value   : int | value
+018: 14:14 | y                   *ast.Ident                     | var     : int | variable
+019: 14:18 | z                   *ast.Ident                     | var     : int | variable
+020: 18: 1 | Mul                 *ast.Ident                     | value   : func(a int, b int) int | value
+021: 18: 1 | Mul 100, 200        *ast.CallExpr                  | value   : int | value
+022: 18: 5 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+023: 18: 9 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
+024: 19: 1 | Mul                 *ast.Ident                     | value   : func(x int, y int, z int) int | value
+025: 19: 1 | Mul 100, 200, 300   *ast.CallExpr                  | value   : int | value
+026: 19: 5 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+027: 19: 9 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
+028: 19:13 | 300                 *ast.BasicLit                  | value   : untyped int = 300 | constant
 == defs ==
 000:  0: 0 | Gopo_Mul            | const main.Gopo_Mul untyped string
 001:  2: 6 | MulInt              | func main.MulInt(a int, b int) int
@@ -2042,11 +2108,12 @@ Mul 100,200,300
 004:  6: 6 | MulFloat            | func main.MulFloat(a float64, b float64) float64
 005:  6:15 | a                   | var a float64
 006:  6:18 | b                   | var b float64
-007: 13: 2 | Mul__2              | func main.Mul__2(x int, y int, z int) int
-008: 13: 7 | x                   | var x int
-009: 13:10 | y                   | var y int
-010: 13:13 | z                   | var z int
-011: 18: 1 | main                | func main.main()
+007: 10: 6 | Mul                 | func main.Mul(__gop_overload_args__ interface{_()})
+008: 13: 2 | Mul__2              | func main.Mul__2(x int, y int, z int) int
+009: 13: 7 | x                   | var x int
+010: 13:10 | y                   | var y int
+011: 13:13 | z                   | var z int
+012: 18: 1 | main                | func main.main()
 == uses ==
 000:  2:18 | int                 | type int
 001:  2:23 | int                 | type int
@@ -2064,7 +2131,10 @@ Mul 100,200,300
 013: 14:14 | y                   | var y int
 014: 14:18 | z                   | var z int
 015: 18: 1 | Mul                 | func main.MulInt(a int, b int) int
-016: 19: 1 | Mul                 | func main.Mul__2(x int, y int, z int) int`)
+016: 19: 1 | Mul                 | func main.Mul__2(x int, y int, z int) int
+== overloads ==
+000: 18: 1 | Mul                 | func main.Mul(__gop_overload_args__ interface{_()})
+001: 19: 1 | Mul                 | func main.Mul(__gop_overload_args__ interface{_()})`)
 
 	testGopInfo(t, `
 type foo struct {
@@ -2120,10 +2190,11 @@ var d = a.mul(c)
 005:  9: 7 | a                   | var a *main.foo
 006:  9:15 | mulFoo              | func (*main.foo).mulFoo(b *main.foo) *main.foo
 007:  9:22 | b                   | var b *main.foo
-008: 18: 5 | a                   | var main.a *main.foo
-009: 18: 8 | b                   | var main.b *main.foo
-010: 19: 5 | c                   | var main.c *main.foo
-011: 20: 5 | d                   | var main.d *main.foo
+008: 13:12 | mul                 | func (main.foo).mul(__gop_overload_args__ interface{_()})
+009: 18: 5 | a                   | var main.a *main.foo
+010: 18: 8 | b                   | var main.b *main.foo
+011: 19: 5 | c                   | var main.c *main.foo
+012: 20: 5 | d                   | var main.d *main.foo
 == uses ==
 000:  5:10 | foo                 | type main.foo struct{}
 001:  5:24 | int                 | type int
@@ -2143,5 +2214,237 @@ var d = a.mul(c)
 015: 19:11 | mul                 | func (*main.foo).mulInt(b int) *main.foo
 016: 20: 9 | a                   | var main.a *main.foo
 017: 20:11 | mul                 | func (*main.foo).mulFoo(b *main.foo) *main.foo
-018: 20:15 | c                   | var main.c *main.foo`)
+018: 20:15 | c                   | var main.c *main.foo
+== overloads ==
+000: 19:11 | mul                 | func (main.foo).mul(__gop_overload_args__ interface{_()})
+001: 20:11 | mul                 | func (main.foo).mul(__gop_overload_args__ interface{_()})`)
+
+}
+
+func TestGopOverloadDecl(t *testing.T) {
+	testGopInfo(t, `
+func addInt0() {
+}
+
+func addInt1(i int) {
+}
+
+func addInt2(i, j int) {
+}
+
+var addInt3 = func(i, j, k int) {
+}
+
+func add = (
+	addInt0
+	addInt1
+	addInt2
+	addInt3
+	func(a, b string) string {
+		return a + b
+	}
+)
+
+func init() {
+	add 100, 200
+	add 100, 200, 300
+	add("hello", "world")
+}
+`, ``, `== types ==
+000:  0: 0 | "addInt0,addInt1,addInt2,addInt3," *ast.BasicLit                  | value   : untyped string = "addInt0,addInt1,addInt2,addInt3," | constant
+001:  5:16 | int                 *ast.Ident                     | type    : int | type
+002:  8:19 | int                 *ast.Ident                     | type    : int | type
+003: 11:15 | func(i, j, k int)   *ast.FuncType                  | type    : func(i int, j int, k int) | type
+004: 11:15 | func(i, j, k int) {
+} *ast.FuncLit                   | value   : func(i int, j int, k int) | value
+005: 11:28 | int                 *ast.Ident                     | type    : int | type
+006: 19: 2 | func(a, b string) string *ast.FuncType                  | type    : func(a string, b string) string | type
+007: 19: 2 | func(a, b string) string {
+	return a + b
+} *ast.FuncLit                   | value   : func(a string, b string) string | value
+008: 19:12 | string              *ast.Ident                     | type    : string | type
+009: 19:20 | string              *ast.Ident                     | type    : string | type
+010: 20:10 | a                   *ast.Ident                     | var     : string | variable
+011: 20:10 | a + b               *ast.BinaryExpr                | value   : string | value
+012: 20:14 | b                   *ast.Ident                     | var     : string | variable
+013: 25: 2 | add                 *ast.Ident                     | value   : func(i int, j int) | value
+014: 25: 2 | add 100, 200        *ast.CallExpr                  | void    : () | no value
+015: 25: 6 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+016: 25:11 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
+017: 26: 2 | add                 *ast.Ident                     | var     : func(i int, j int, k int) | variable
+018: 26: 2 | add 100, 200, 300   *ast.CallExpr                  | void    : () | no value
+019: 26: 6 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+020: 26:11 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
+021: 26:16 | 300                 *ast.BasicLit                  | value   : untyped int = 300 | constant
+022: 27: 2 | add                 *ast.Ident                     | value   : func(a string, b string) string | value
+023: 27: 2 | add("hello", "world") *ast.CallExpr                  | value   : string | value
+024: 27: 6 | "hello"             *ast.BasicLit                  | value   : untyped string = "hello" | constant
+025: 27:15 | "world"             *ast.BasicLit                  | value   : untyped string = "world" | constant
+== defs ==
+000:  0: 0 | Gopo_add            | const main.Gopo_add untyped string
+001:  2: 6 | addInt0             | func main.addInt0()
+002:  5: 6 | addInt1             | func main.addInt1(i int)
+003:  5:14 | i                   | var i int
+004:  8: 6 | addInt2             | func main.addInt2(i int, j int)
+005:  8:14 | i                   | var i int
+006:  8:17 | j                   | var j int
+007: 11: 5 | addInt3             | var main.addInt3 func(i int, j int, k int)
+008: 11:20 | i                   | var i int
+009: 11:23 | j                   | var j int
+010: 11:26 | k                   | var k int
+011: 14: 6 | add                 | func main.add(__gop_overload_args__ interface{_()})
+012: 19: 2 | add__4              | func main.add__4(a string, b string) string
+013: 19: 7 | a                   | var a string
+014: 19:10 | b                   | var b string
+015: 24: 6 | init                | func main.init()
+== uses ==
+000:  5:16 | int                 | type int
+001:  8:19 | int                 | type int
+002: 11:28 | int                 | type int
+003: 15: 2 | addInt0             | func main.addInt0()
+004: 16: 2 | addInt1             | func main.addInt1(i int)
+005: 17: 2 | addInt2             | func main.addInt2(i int, j int)
+006: 18: 2 | addInt3             | var main.addInt3 func(i int, j int, k int)
+007: 19:12 | string              | type string
+008: 19:20 | string              | type string
+009: 20:10 | a                   | var a string
+010: 20:14 | b                   | var b string
+011: 25: 2 | add                 | func main.addInt2(i int, j int)
+012: 26: 2 | add                 | var main.addInt3 func(i int, j int, k int)
+013: 27: 2 | add                 | func main.add__4(a string, b string) string
+== overloads ==
+000: 25: 2 | add                 | func main.add(__gop_overload_args__ interface{_()})
+001: 26: 2 | add                 | func main.add(__gop_overload_args__ interface{_()})
+002: 27: 2 | add                 | func main.add(__gop_overload_args__ interface{_()})`)
+
+	testGopInfo(t, `
+func add = (
+	func(a, b int) int {
+		return a + b
+	}
+	func(a, b string) string {
+		return a + b
+	}
+)
+
+func init() {
+	add 100, 200
+	add "hello", "world"
+}
+`, ``, `== types ==
+000:  3: 2 | func(a, b int) int  *ast.FuncType                  | type    : func(a int, b int) int | type
+001:  3: 2 | func(a, b int) int {
+	return a + b
+} *ast.FuncLit                   | value   : func(a int, b int) int | value
+002:  3:12 | int                 *ast.Ident                     | type    : int | type
+003:  3:17 | int                 *ast.Ident                     | type    : int | type
+004:  4:10 | a                   *ast.Ident                     | var     : int | variable
+005:  4:10 | a + b               *ast.BinaryExpr                | value   : int | value
+006:  4:14 | b                   *ast.Ident                     | var     : int | variable
+007:  6: 2 | func(a, b string) string *ast.FuncType                  | type    : func(a string, b string) string | type
+008:  6: 2 | func(a, b string) string {
+	return a + b
+} *ast.FuncLit                   | value   : func(a string, b string) string | value
+009:  6:12 | string              *ast.Ident                     | type    : string | type
+010:  6:20 | string              *ast.Ident                     | type    : string | type
+011:  7:10 | a                   *ast.Ident                     | var     : string | variable
+012:  7:10 | a + b               *ast.BinaryExpr                | value   : string | value
+013:  7:14 | b                   *ast.Ident                     | var     : string | variable
+014: 12: 2 | add                 *ast.Ident                     | value   : func(a int, b int) int | value
+015: 12: 2 | add 100, 200        *ast.CallExpr                  | value   : int | value
+016: 12: 6 | 100                 *ast.BasicLit                  | value   : untyped int = 100 | constant
+017: 12:11 | 200                 *ast.BasicLit                  | value   : untyped int = 200 | constant
+018: 13: 2 | add                 *ast.Ident                     | value   : func(a string, b string) string | value
+019: 13: 2 | add "hello", "world" *ast.CallExpr                  | value   : string | value
+020: 13: 6 | "hello"             *ast.BasicLit                  | value   : untyped string = "hello" | constant
+021: 13:15 | "world"             *ast.BasicLit                  | value   : untyped string = "world" | constant
+== defs ==
+000:  2: 6 | add                 | func main.add(__gop_overload_args__ interface{_()})
+001:  3: 2 | add__0              | func main.add__0(a int, b int) int
+002:  3: 7 | a                   | var a int
+003:  3:10 | b                   | var b int
+004:  6: 2 | add__1              | func main.add__1(a string, b string) string
+005:  6: 7 | a                   | var a string
+006:  6:10 | b                   | var b string
+007: 11: 6 | init                | func main.init()
+== uses ==
+000:  3:12 | int                 | type int
+001:  3:17 | int                 | type int
+002:  4:10 | a                   | var a int
+003:  4:14 | b                   | var b int
+004:  6:12 | string              | type string
+005:  6:20 | string              | type string
+006:  7:10 | a                   | var a string
+007:  7:14 | b                   | var b string
+008: 12: 2 | add                 | func main.add__0(a int, b int) int
+009: 13: 2 | add                 | func main.add__1(a string, b string) string
+== overloads ==
+000: 12: 2 | add                 | func main.add(__gop_overload_args__ interface{_()})
+001: 13: 2 | add                 | func main.add(__gop_overload_args__ interface{_()})`)
+}
+
+func TestGoxOverloadInfo(t *testing.T) {
+	testSpxInfo(t, "Rect.gox", `
+func addInt(a, b int) int {
+	return a + b
+}
+
+func addString(a, b string) string {
+	return a + b
+}
+
+func add = (
+	addInt
+	func(a, b float64) float64 {
+		return a + b
+	}
+	addString
+)
+`, `== types ==
+000:  0: 0 | ".addInt,,.addString" *ast.BasicLit                  | value   : untyped string = ".addInt,,.addString" | constant
+001:  0: 0 | Rect                *ast.Ident                     | type    : main.Rect | type
+002:  2:18 | int                 *ast.Ident                     | type    : int | type
+003:  2:23 | int                 *ast.Ident                     | type    : int | type
+004:  3: 9 | a                   *ast.Ident                     | var     : int | variable
+005:  3: 9 | a + b               *ast.BinaryExpr                | value   : int | value
+006:  3:13 | b                   *ast.Ident                     | var     : int | variable
+007:  6:21 | string              *ast.Ident                     | type    : string | type
+008:  6:29 | string              *ast.Ident                     | type    : string | type
+009:  7: 9 | a                   *ast.Ident                     | var     : string | variable
+010:  7: 9 | a + b               *ast.BinaryExpr                | value   : string | value
+011:  7:13 | b                   *ast.Ident                     | var     : string | variable
+012: 12:12 | float64             *ast.Ident                     | type    : float64 | type
+013: 12:21 | float64             *ast.Ident                     | type    : float64 | type
+014: 13:10 | a                   *ast.Ident                     | var     : float64 | variable
+015: 13:10 | a + b               *ast.BinaryExpr                | value   : float64 | value
+016: 13:14 | b                   *ast.Ident                     | var     : float64 | variable
+== defs ==
+000:  0: 0 | Gopo_Rect_add       | const main.Gopo_Rect_add untyped string
+001:  0: 0 | this                | var this *main.Rect
+002:  2: 6 | addInt              | func (*main.Rect).addInt(a int, b int) int
+003:  2:13 | a                   | var a int
+004:  2:16 | b                   | var b int
+005:  6: 6 | addString           | func (*main.Rect).addString(a string, b string) string
+006:  6:16 | a                   | var a string
+007:  6:19 | b                   | var b string
+008: 10: 6 | add                 | func (main.Rect).add(__gop_overload_args__ interface{_()})
+009: 12: 2 | add__1              | func (*main.Rect).add__1(a float64, b float64) float64
+010: 12: 7 | a                   | var a float64
+011: 12:10 | b                   | var b float64
+== uses ==
+000:  0: 0 | Rect                | type main.Rect struct{}
+001:  2:18 | int                 | type int
+002:  2:23 | int                 | type int
+003:  3: 9 | a                   | var a int
+004:  3:13 | b                   | var b int
+005:  6:21 | string              | type string
+006:  6:29 | string              | type string
+007:  7: 9 | a                   | var a string
+008:  7:13 | b                   | var b string
+009: 11: 2 | addInt              | func (*main.Rect).addInt(a int, b int) int
+010: 12:12 | float64             | type float64
+011: 12:21 | float64             | type float64
+012: 13:10 | a                   | var a float64
+013: 13:14 | b                   | var b float64
+014: 15: 2 | addString           | func (*main.Rect).addString(a string, b string) string`)
 }