Repository: goplus/gop
Pull request #1201 (2022-05-19 15:17:02)

v1.1.0-beta5


Patch:
diff --git a/README.md b/README.md
index eccf90e0..e637e25d 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,6 @@
 [![Tutorials](https://img.shields.io/badge/tutorial-Go+-blue.svg)](https://tutorial.goplus.org/)
 [![Playground](https://img.shields.io/badge/play-Go+-blue.svg)](https://play.goplus.org/)
 [![VSCode](https://img.shields.io/badge/vscode-Go+-teal.svg)](https://github.com/gopcode/vscode-goplus)
-[![Readme](https://img.shields.io/badge/README-中文-teal.svg)](https://github.com/goplus/gop/blob/main/README_zh.md)
 
 * **For engineering**: working in the simplest language that can be mastered by children.
 * **For STEM education**: studying an engineering language that can be used to work in the future.
diff --git a/VERSION b/VERSION
deleted file mode 100644
index f081620b..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.1.0-beta3
diff --git a/ast/fromgo/gopast.go b/ast/fromgo/gopast.go
new file mode 100644
index 00000000..aa1618e9
--- /dev/null
+++ b/ast/fromgo/gopast.go
@@ -0,0 +1,336 @@
+/*
+ * 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 fromgo
+
+import (
+	"go/ast"
+	"go/token"
+	"log"
+	"reflect"
+
+	gopast "github.com/goplus/gop/ast"
+	goptoken "github.com/goplus/gop/token"
+)
+
+// ----------------------------------------------------------------------------
+
+func gopExpr(val ast.Expr) gopast.Expr {
+	if val == nil {
+		return nil
+	}
+	switch v := val.(type) {
+	case *ast.Ident:
+		return gopIdent(v)
+	case *ast.SelectorExpr:
+		return &gopast.SelectorExpr{
+			X:   gopExpr(v.X),
+			Sel: gopIdent(v.Sel),
+		}
+	case *ast.SliceExpr:
+		return &gopast.SliceExpr{
+			X:      gopExpr(v.X),
+			Lbrack: v.Lbrack,
+			Low:    gopExpr(v.Low),
+			High:   gopExpr(v.High),
+			Max:    gopExpr(v.Max),
+			Slice3: v.Slice3,
+			Rbrack: v.Rbrack,
+		}
+	case *ast.StarExpr:
+		return &gopast.StarExpr{
+			Star: v.Star,
+			X:    gopExpr(v.X),
+		}
+	case *ast.MapType:
+		return &gopast.MapType{
+			Map:   v.Map,
+			Key:   gopType(v.Key),
+			Value: gopType(v.Value),
+		}
+	case *ast.StructType:
+		return &gopast.StructType{
+			Struct: v.Struct,
+			Fields: gopFieldList(v.Fields),
+		}
+	case *ast.FuncType:
+		return gopFuncType(v)
+	case *ast.InterfaceType:
+		return &gopast.InterfaceType{
+			Interface: v.Interface,
+			Methods:   gopFieldList(v.Methods),
+		}
+	case *ast.ArrayType:
+		return &gopast.ArrayType{
+			Lbrack: v.Lbrack,
+			Len:    gopExpr(v.Len),
+			Elt:    gopType(v.Elt),
+		}
+	case *ast.ChanType:
+		return &gopast.ChanType{
+			Begin: v.Begin,
+			Arrow: v.Arrow,
+			Dir:   gopast.ChanDir(v.Dir),
+			Value: gopType(v.Value),
+		}
+	case *ast.BasicLit:
+		return gopBasicLit(v)
+	case *ast.BinaryExpr:
+		return &gopast.BinaryExpr{
+			X:     gopExpr(v.X),
+			OpPos: v.OpPos,
+			Op:    goptoken.Token(v.Op),
+			Y:     gopExpr(v.Y),
+		}
+	case *ast.UnaryExpr:
+		return &gopast.UnaryExpr{
+			OpPos: v.OpPos,
+			Op:    goptoken.Token(v.Op),
+			X:     gopExpr(v.X),
+		}
+	case *ast.CallExpr:
+		return &gopast.CallExpr{
+			Fun:      gopExpr(v.Fun),
+			Lparen:   v.Lparen,
+			Args:     gopExprs(v.Args),
+			Ellipsis: v.Ellipsis,
+			Rparen:   v.Rparen,
+		}
+	case *ast.IndexExpr:
+		return &gopast.IndexExpr{
+			X:      gopExpr(v.X),
+			Lbrack: v.Lbrack,
+			Index:  gopExpr(v.Index),
+			Rbrack: v.Rbrack,
+		}
+	case *ast.ParenExpr:
+		return &gopast.ParenExpr{
+			Lparen: v.Lparen,
+			X:      gopExpr(v.X),
+			Rparen: v.Rparen,
+		}
+	case *ast.CompositeLit:
+		return &gopast.CompositeLit{
+			Type:   gopType(v.Type),
+			Lbrace: v.Lbrace,
+			Elts:   gopExprs(v.Elts),
+			Rbrace: v.Rbrace,
+		}
+	case *ast.FuncLit:
+		return &gopast.FuncLit{
+			Type: gopFuncType(v.Type),
+			Body: &gopast.BlockStmt{}, // skip closure body
+		}
+	case *ast.TypeAssertExpr:
+		return &gopast.TypeAssertExpr{
+			X:      gopExpr(v.X),
+			Lparen: v.Lparen,
+			Type:   gopType(v.Type),
+			Rparen: v.Rparen,
+		}
+	case *ast.KeyValueExpr:
+		return &gopast.KeyValueExpr{
+			Key:   gopExpr(v.Key),
+			Colon: v.Colon,
+			Value: gopExpr(v.Value),
+		}
+	case *ast.Ellipsis:
+		return &gopast.Ellipsis{
+			Ellipsis: v.Ellipsis,
+			Elt:      gopExpr(v.Elt),
+		}
+	}
+	log.Panicln("gopExpr: unknown expr -", reflect.TypeOf(val))
+	return nil
+}
+
+func gopExprs(vals []ast.Expr) []gopast.Expr {
+	n := len(vals)
+	if n == 0 {
+		return nil
+	}
+	ret := make([]gopast.Expr, n)
+	for i, v := range vals {
+		ret[i] = gopExpr(v)
+	}
+	return ret
+}
+
+// ----------------------------------------------------------------------------
+
+func gopFuncType(v *ast.FuncType) *gopast.FuncType {
+	return &gopast.FuncType{
+		Func:    v.Func,
+		Params:  gopFieldList(v.Params),
+		Results: gopFieldList(v.Results),
+	}
+}
+
+func gopType(v ast.Expr) gopast.Expr {
+	return gopExpr(v)
+}
+
+func gopBasicLit(v *ast.BasicLit) *gopast.BasicLit {
+	if v == nil {
+		return nil
+	}
+	return &gopast.BasicLit{
+		ValuePos: v.ValuePos,
+		Kind:     goptoken.Token(v.Kind),
+		Value:    v.Value,
+	}
+}
+
+func gopIdent(v *ast.Ident) *gopast.Ident {
+	if v == nil {
+		return nil
+	}
+	return &gopast.Ident{
+		NamePos: v.NamePos,
+		Name:    v.Name,
+	}
+}
+
+func gopIdents(names []*ast.Ident) []*gopast.Ident {
+	ret := make([]*gopast.Ident, len(names))
+	for i, v := range names {
+		ret[i] = gopIdent(v)
+	}
+	return ret
+}
+
+// ----------------------------------------------------------------------------
+
+func gopField(v *ast.Field) *gopast.Field {
+	return &gopast.Field{
+		Names: gopIdents(v.Names),
+		Type:  gopType(v.Type),
+		Tag:   gopBasicLit(v.Tag),
+	}
+}
+
+func gopFieldList(v *ast.FieldList) *gopast.FieldList {
+	if v == nil {
+		return nil
+	}
+	list := make([]*gopast.Field, len(v.List))
+	for i, item := range v.List {
+		list[i] = gopField(item)
+	}
+	return &gopast.FieldList{Opening: v.Opening, List: list, Closing: v.Closing}
+}
+
+func gopFuncDecl(v *ast.FuncDecl) *gopast.FuncDecl {
+	return &gopast.FuncDecl{
+		Recv: gopFieldList(v.Recv),
+		Name: gopIdent(v.Name),
+		Type: gopFuncType(v.Type),
+		Body: &gopast.BlockStmt{}, // ignore function body
+	}
+}
+
+// ----------------------------------------------------------------------------
+
+func gopImportSpec(spec *ast.ImportSpec) *gopast.ImportSpec {
+	return &gopast.ImportSpec{
+		Name:   gopIdent(spec.Name),
+		Path:   gopBasicLit(spec.Path),
+		EndPos: spec.EndPos,
+	}
+}
+
+func gopTypeSpec(spec *ast.TypeSpec) *gopast.TypeSpec {
+	return &gopast.TypeSpec{
+		Name:   gopIdent(spec.Name),
+		Assign: spec.Assign,
+		Type:   gopType(spec.Type),
+	}
+}
+
+func gopValueSpec(spec *ast.ValueSpec) *gopast.ValueSpec {
+	return &gopast.ValueSpec{
+		Names:  gopIdents(spec.Names),
+		Type:   gopType(spec.Type),
+		Values: gopExprs(spec.Values),
+	}
+}
+
+func gopGenDecl(v *ast.GenDecl) *gopast.GenDecl {
+	specs := make([]gopast.Spec, len(v.Specs))
+	for i, spec := range v.Specs {
+		switch v.Tok {
+		case token.IMPORT:
+			specs[i] = gopImportSpec(spec.(*ast.ImportSpec))
+		case token.TYPE:
+			specs[i] = gopTypeSpec(spec.(*ast.TypeSpec))
+		case token.VAR, token.CONST:
+			specs[i] = gopValueSpec(spec.(*ast.ValueSpec))
+		default:
+			log.Panicln("gopGenDecl: unknown spec -", v.Tok)
+		}
+	}
+	return &gopast.GenDecl{
+		TokPos: v.TokPos,
+		Tok:    goptoken.Token(v.Tok),
+		Lparen: v.Lparen,
+		Specs:  specs,
+		Rparen: v.Rparen,
+	}
+}
+
+// ----------------------------------------------------------------------------
+
+func gopDecl(decl ast.Decl) gopast.Decl {
+	switch v := decl.(type) {
+	case *ast.GenDecl:
+		return gopGenDecl(v)
+	case *ast.FuncDecl:
+		return gopFuncDecl(v)
+	}
+	log.Panicln("gopDecl: unkown decl -", reflect.TypeOf(decl))
+	return nil
+}
+
+func gopDecls(decls []ast.Decl) []gopast.Decl {
+	ret := make([]gopast.Decl, len(decls))
+	for i, decl := range decls {
+		ret[i] = gopDecl(decl)
+	}
+	return ret
+}
+
+// ----------------------------------------------------------------------------
+
+const (
+	KeepFuncBody = 1 << iota
+	KeepCgo
+)
+
+func ASTFile(f *ast.File, mode int) *gopast.File {
+	if (mode & KeepFuncBody) != 0 {
+		log.Panicln("ASTFile: doesn't support keeping func body now")
+	}
+	if (mode & KeepCgo) != 0 {
+		log.Panicln("ASTFile: doesn't support keeping cgo now")
+	}
+	return &gopast.File{
+		Package: f.Package,
+		Name:    gopIdent(f.Name),
+		Decls:   gopDecls(f.Decls),
+	}
+}
+
+// ----------------------------------------------------------------------------
diff --git a/ast/togo/goast.go b/ast/togo/goast.go
index f8ec8060..4dbac88a 100644
--- a/ast/togo/goast.go
+++ b/ast/togo/goast.go
@@ -1,3 +1,19 @@
+/*
+ * 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 togo
 
 import (
@@ -116,7 +132,7 @@ func goExpr(val gopast.Expr) ast.Expr {
 	case *gopast.FuncLit:
 		return &ast.FuncLit{
 			Type: goFuncType(v.Type),
-			Body: nil,
+			Body: &ast.BlockStmt{}, // skip closure body
 		}
 	case *gopast.TypeAssertExpr:
 		return &ast.TypeAssertExpr{
@@ -304,7 +320,7 @@ const (
 
 func ASTFile(f *gopast.File, mode int) *ast.File {
 	if (mode & KeepFuncBody) != 0 {
-		log.Panicln("ASTFile: doesn't support keep func body now")
+		log.Panicln("ASTFile: doesn't support keeping func body now")
 	}
 	return &ast.File{
 		Package: f.Package,
diff --git a/build_install_run.go b/build_install_run.go
new file mode 100644
index 00000000..ea06f000
--- /dev/null
+++ b/build_install_run.go
@@ -0,0 +1,163 @@
+/*
+ * 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 gop
+
+import (
+	"log"
+	"os"
+
+	"github.com/goplus/gop/x/gocmd"
+)
+
+// -----------------------------------------------------------------------------
+
+func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err error) {
+	err = GenGo(dir, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.Install(dir, install)
+}
+
+func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig) (err error) {
+	localDir, err := GenGoPkgPath(workDir, pkgPath, conf, true)
+	if err != nil {
+		return
+	}
+	old := chdir(localDir)
+	defer os.Chdir(old)
+	return gocmd.Install(".", install)
+}
+
+func InstallFiles(files []string, conf *Config, install *gocmd.InstallConfig) (err error) {
+	files, err = GenGoFiles("", files, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.InstallFiles(files, install)
+}
+
+func chdir(dir string) string {
+	old, err := os.Getwd()
+	if err != nil {
+		log.Panicln(err)
+	}
+	err = os.Chdir(dir)
+	if err != nil {
+		log.Panicln(err)
+	}
+	return old
+}
+
+// -----------------------------------------------------------------------------
+
+func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
+	err = GenGo(dir, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.Build(dir, build)
+}
+
+func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) {
+	localDir, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+	if err != nil {
+		return
+	}
+	old := chdirAndMod(localDir)
+	defer restoreDirAndMod(old)
+	return gocmd.Build(".", build)
+}
+
+func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) {
+	files, err = GenGoFiles("", files, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.BuildFiles(files, build)
+}
+
+func chdirAndMod(dir string) string {
+	os.Chmod(dir, 0777)
+	return chdir(dir)
+}
+
+func restoreDirAndMod(old string) {
+	os.Chmod(".", 0555)
+	os.Chdir(old)
+}
+
+// -----------------------------------------------------------------------------
+
+func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
+	err = GenGo(dir, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.RunDir(dir, args, run)
+}
+
+func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig) (err error) {
+	localDir, err := GenGoPkgPath("", pkgPath, conf, true)
+	if err != nil {
+		return
+	}
+	if chDir {
+		old := chdir(localDir)
+		defer os.Chdir(old)
+		localDir = "."
+	}
+	return gocmd.RunDir(localDir, args, run)
+}
+
+func RunFiles(autogen string, files []string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
+	files, err = GenGoFiles(autogen, files, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.RunFiles(files, args, run)
+}
+
+// -----------------------------------------------------------------------------
+
+func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
+	err = GenGo(dir, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.Test(dir, test)
+}
+
+func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) (err error) {
+	localDir, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+	if err != nil {
+		return
+	}
+	old := chdirAndMod(localDir)
+	defer restoreDirAndMod(old)
+	return gocmd.Test(".", test)
+}
+
+func TestFiles(files []string, conf *Config, test *gocmd.TestConfig) (err error) {
+	files, err = GenGoFiles("", files, conf)
+	if err != nil {
+		return
+	}
+	return gocmd.TestFiles(files, test)
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/builtin.go b/cl/builtin.go
index 0a5d5f15..751afc2a 100644
--- a/cl/builtin.go
+++ b/cl/builtin.go
@@ -19,6 +19,7 @@ package cl
 import (
 	"go/token"
 	"go/types"
+	"strings"
 
 	"github.com/goplus/gox"
 )
@@ -68,3 +69,29 @@ func newBuiltinDefault(pkg gox.PkgImporter, conf *gox.Config) *types.Package {
 }
 
 // -----------------------------------------------------------------------------
+
+type gopImporter struct {
+	gopRoot string
+	impFrom types.ImporterFrom
+}
+
+func newGopImporter(gopRoot string, imp types.Importer) types.Importer {
+	if impFrom, ok := imp.(types.ImporterFrom); ok && gopRoot != "" {
+		return &gopImporter{gopRoot: gopRoot, impFrom: impFrom}
+	}
+	return imp
+}
+
+func (p *gopImporter) Import(pkgPath string) (pkg *types.Package, err error) {
+	const (
+		gop = "github.com/goplus/gop"
+	)
+	if strings.HasPrefix(pkgPath, gop) {
+		if suffix := pkgPath[len(gop):]; suffix == "" || suffix[0] == '/' {
+			return p.impFrom.ImportFrom(pkgPath, p.gopRoot, 0)
+		}
+	}
+	return p.impFrom.Import(pkgPath)
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/compile.go b/cl/compile.go
index b74fa9d5..58fee873 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -28,9 +28,11 @@ import (
 	"strings"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/ast/fromgo"
 	"github.com/goplus/gop/token"
-	"github.com/goplus/gop/x/mod/modfile"
 	"github.com/goplus/gox"
+	"github.com/goplus/gox/packages"
+	"github.com/goplus/mod/modfile"
 )
 
 const (
@@ -81,6 +83,9 @@ type Config struct {
 	// TargetDir is the directory in which to generate Go files.
 	TargetDir string
 
+	// GopRoot specifies the Go+ root directory.
+	GopRoot string
+
 	// Fset provides source position information for syntax trees and types.
 	// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
 	Fset *token.FileSet
@@ -148,9 +153,11 @@ type baseLoader struct {
 	start token.Pos
 }
 
-func initLoader(ctx *pkgCtx, syms map[string]loader, start token.Pos, name string, fn func()) {
+func initLoader(ctx *pkgCtx, syms map[string]loader, start token.Pos, name string, fn func(), genCode bool) {
 	if name == "_" {
-		ctx.inits = append(ctx.inits, fn)
+		if genCode {
+			ctx.inits = append(ctx.inits, fn)
+		}
 		return
 	}
 	if old, ok := syms[name]; ok {
@@ -264,7 +271,7 @@ func (bc *blockCtx) findImport(name string) (pr *gox.PkgRef, ok bool) {
 	for k, v := range bc.imports {
 		v.EnsureImported()
 		if v.Types != nil {
-			if v.Types.Name() != k {
+			if v.Types.Name() != k { // if we guess pkgName failed
 				delete(bc.imports, k)
 				bc.imports[v.Types.Name()] = v
 			}
@@ -350,6 +357,12 @@ func (p *pkgCtx) handleRecover(e interface{}) {
 	p.handleErr(err)
 }
 
+const (
+	defaultGoFile  = ""
+	skippingGoFile = "_skip"
+	testingGoFile  = "_test"
+)
+
 // NewPackage creates a Go+ package instance.
 func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package, err error) {
 	workingDir := conf.WorkingDir
@@ -360,34 +373,55 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	if targetDir == "" {
 		targetDir = workingDir
 	}
-	interp := &nodeInterp{fset: conf.Fset, files: pkg.Files, workingDir: workingDir}
+	fset := conf.Fset
+	imp := conf.Importer
+	if imp == nil {
+		imp = packages.NewImporter(fset, workingDir)
+	}
+	files := pkg.Files
+	interp := &nodeInterp{fset: fset, files: files, workingDir: workingDir}
 	ctx := &pkgCtx{syms: make(map[string]loader), nodeInterp: interp}
 	confGox := &gox.Config{
-		Fset:            conf.Fset,
-		Importer:        conf.Importer,
+		Fset:            fset,
+		Importer:        newGopImporter(conf.GopRoot, imp),
 		LoadNamed:       ctx.loadNamed,
 		HandleErr:       ctx.handleErr,
 		NodeInterpreter: interp,
 		NewBuiltin:      newBuiltinDefault,
+		DefaultGoFile:   defaultGoFile,
 	}
 	p = gox.NewPackage(pkgPath, pkg.Name, confGox)
-	for file, gmx := range pkg.Files {
+	for file, gmx := range files {
 		if gmx.IsProj {
 			ctx.gmxSettings = newGmx(p, file, conf)
 			break
 		}
 	}
-	for fpath, f := range pkg.Files {
-		preloadFile(p, ctx, fpath, f, targetDir, conf)
+	for fpath, f := range files {
+		fileLine := !conf.NoFileLine
+		ctx := &blockCtx{
+			pkg: p, pkgCtx: ctx, cb: p.CB(), fset: p.Fset, targetDir: targetDir,
+			fileLine: fileLine, relativePath: conf.RelativePath, isClass: f.IsClass,
+			imports: make(map[string]*gox.PkgRef),
+		}
+		preloadGopFile(p, ctx, fpath, f, conf)
+	}
+	for fpath, gof := range pkg.GoFiles {
+		f := fromgo.ASTFile(gof, 0)
+		ctx := &blockCtx{
+			pkg: p, pkgCtx: ctx, cb: p.CB(), fset: p.Fset, targetDir: targetDir,
+			imports: make(map[string]*gox.PkgRef),
+		}
+		preloadFile(p, ctx, fpath, f, false)
 	}
-	for _, f := range pkg.Files {
+	for _, f := range files {
 		if f.IsProj {
 			loadFile(ctx, f)
 			gmxMainFunc(p, ctx)
 			break
 		}
 	}
-	for _, f := range pkg.Files {
+	for _, f := range files {
 		if !f.IsProj { // only one .gmx file
 			loadFile(ctx, f)
 		}
@@ -402,12 +436,11 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 
 	if !conf.NoAutoGenMain && pkg.Name == "main" {
 		if obj := p.Types.Scope().Lookup("main"); obj == nil {
-			old := p.SetInTestingFile(false)
+			old, _ := p.SetCurFile(defaultGoFile, false)
 			p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End()
-			p.SetInTestingFile(old)
+			p.RestoreCurFile(old)
 		}
 	}
-
 	return
 }
 
@@ -468,15 +501,18 @@ func loadFile(ctx *pkgCtx, f *ast.File) {
 	}
 }
 
-func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targetDir string, conf *Config) {
-	syms := parent.syms
-	fileLine := !conf.NoFileLine
-	testingFile := strings.HasSuffix(file, "_test.gop")
-	ctx := &blockCtx{
-		pkg: p, pkgCtx: parent, cb: p.CB(), fset: p.Fset, targetDir: targetDir,
-		fileLine: fileLine, relativePath: conf.RelativePath, isClass: f.IsClass,
-		imports: make(map[string]*gox.PkgRef),
+func getGoFile(file string, genCode bool) string {
+	if genCode {
+		if strings.HasSuffix(file, "_test.gop") {
+			return testingGoFile
+		}
+		return defaultGoFile
 	}
+	return skippingGoFile
+}
+
+func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, conf *Config) {
+	var parent = ctx.pkgCtx
 	var classType string
 	var baseTypeName string
 	var baseType types.Type
@@ -505,6 +541,7 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 		for i, pkgPath := range parent.pkgPaths {
 			ctx.lookups[i] = p.Import(pkgPath)
 		}
+		syms := parent.syms
 		pos := f.Pos()
 		specs := getFields(ctx, f)
 		ld := getTypeLoader(parent, syms, pos, classType)
@@ -555,7 +592,7 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 		}}}
 	}
 	// check class project no MainEntry and auto added
-	if !conf.NoAutoGenMain && f.IsProj && !f.NoEntrypoint && f.Name.Name == "main" {
+	if f.IsProj && !conf.NoAutoGenMain && !f.NoEntrypoint && f.Name.Name == "main" {
 		entry := getEntrypoint(f, false)
 		var hasEntry bool
 		for _, decl := range f.Decls {
@@ -574,6 +611,14 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 			})
 		}
 	}
+	preloadFile(p, ctx, file, f, true)
+}
+
+func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, genCode bool) {
+	parent := ctx.pkgCtx
+	syms := parent.syms
+	goFile := getGoFile(file, genCode)
+	p.SetCurFile(goFile, true)
 	for _, decl := range f.Decls {
 		switch d := decl.(type) {
 		case *ast.FuncDecl:
@@ -586,42 +631,60 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 				}
 			}
 			if d.Recv == nil {
-				name := d.Name
-				fn := func() {
-					old := p.SetInTestingFile(testingFile)
-					defer p.SetInTestingFile(old)
-					loadFunc(ctx, nil, d)
+				var name = d.Name
+				var fn func()
+				if genCode {
+					fn = func() {
+						old, _ := p.SetCurFile(goFile, true)
+						defer p.RestoreCurFile(old)
+						loadFunc(ctx, nil, d)
+					}
+				} else {
+					fn = func() {
+						declFunc(ctx, nil, d)
+					}
 				}
 				if name.Name == "init" {
-					if debugLoad {
-						log.Println("==> Preload func init")
+					if genCode {
+						if debugLoad {
+							log.Println("==> Preload func init")
+						}
+						parent.inits = append(parent.inits, fn)
 					}
-					parent.inits = append(parent.inits, fn)
 				} else {
 					if debugLoad {
 						log.Println("==> Preload func", name.Name)
 					}
-					initLoader(parent, syms, name.Pos(), name.Name, fn)
+					initLoader(parent, syms, name.Pos(), name.Name, fn, genCode)
 				}
 			} else {
 				if name, ok := getRecvTypeName(parent, d.Recv, true); ok {
 					if debugLoad {
 						log.Printf("==> Preload method %s.%s\n", name, d.Name.Name)
 					}
-					ld := getTypeLoader(parent, syms, token.NoPos, name)
-					ld.methods = append(ld.methods, func() {
-						old := p.SetInTestingFile(testingFile)
-						defer p.SetInTestingFile(old)
-						doInitType(ld)
-						recv := toRecv(ctx, d.Recv)
-						loadFunc(ctx, recv, d)
-					})
+					var ld = getTypeLoader(parent, syms, token.NoPos, name)
+					var fn func()
+					if genCode {
+						fn = func() {
+							old, _ := p.SetCurFile(goFile, true)
+							defer p.RestoreCurFile(old)
+							doInitType(ld)
+							recv := toRecv(ctx, d.Recv)
+							loadFunc(ctx, recv, d)
+						}
+					} else {
+						fn = func() {
+							doInitType(ld)
+							recv := toRecv(ctx, d.Recv)
+							declFunc(ctx, recv, d)
+						}
+					}
+					ld.methods = append(ld.methods, fn)
 				}
 			}
 		case *ast.GenDecl:
 			switch d.Tok {
 			case token.IMPORT:
-				p.SetInTestingFile(testingFile)
 				for _, item := range d.Specs {
 					loadImport(ctx, item.(*ast.ImportSpec))
 				}
@@ -633,30 +696,53 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 						log.Println("==> Preload type", name)
 					}
 					ld := getTypeLoader(parent, syms, t.Name.Pos(), name)
-					ld.typ = func() {
-						old := p.SetInTestingFile(testingFile)
-						defer p.SetInTestingFile(old)
-						if t.Assign != token.NoPos { // alias type
+					if genCode {
+						ld.typ = func() {
+							old, _ := p.SetCurFile(goFile, true)
+							defer p.RestoreCurFile(old)
+							if t.Assign != token.NoPos { // alias type
+								if debugLoad {
+									log.Println("==> Load > AliasType", name)
+								}
+								ctx.pkg.AliasType(name, toType(ctx, t.Type), t.Pos())
+								return
+							}
 							if debugLoad {
-								log.Println("==> Load > AliasType", name)
+								log.Println("==> Load > NewType", name)
+							}
+							decl := ctx.pkg.NewType(name)
+							if t.Doc != nil {
+								decl.SetComments(t.Doc)
+							} else if d.Doc != nil {
+								decl.SetComments(d.Doc)
+							}
+							ld.typInit = func() { // decycle
+								if debugLoad {
+									log.Println("==> Load > InitType", name)
+								}
+								decl.InitType(ctx.pkg, toType(ctx, t.Type))
 							}
-							ctx.pkg.AliasType(name, toType(ctx, t.Type))
-							return
-						}
-						if debugLoad {
-							log.Println("==> Load > NewType", name)
-						}
-						decl := ctx.pkg.NewType(name)
-						if t.Doc != nil {
-							decl.SetComments(t.Doc)
-						} else if d.Doc != nil {
-							decl.SetComments(d.Doc)
 						}
-						ld.typInit = func() { // decycle
+					} else {
+						ld.typ = func() {
+							pkg := ctx.pkg.Types
+							if t.Assign != token.NoPos { // alias type
+								if debugLoad {
+									log.Println("==> Load > AliasType", name)
+								}
+								aliasType(pkg, t.Pos(), name, toType(ctx, t.Type))
+								return
+							}
 							if debugLoad {
-								log.Println("==> Load > InitType", name)
+								log.Println("==> Load > NewType", name)
+							}
+							named := newType(pkg, t.Pos(), name)
+							ld.typInit = func() { // decycle
+								if debugLoad {
+									log.Println("==> Load > InitType", name)
+								}
+								initType(ctx, named, toType(ctx, t.Type))
 							}
-							decl.InitType(ctx.pkg, toType(ctx, t.Type))
 						}
 					}
 				}
@@ -670,8 +756,6 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 					}
 					setNamesLoader(parent, syms, vSpec.Names, func() {
 						if c := cdecl; c != nil {
-							old := p.SetInTestingFile(testingFile)
-							defer p.SetInTestingFile(old)
 							cdecl = nil
 							loadConstSpecs(ctx, c, d.Specs)
 							for _, s := range d.Specs {
@@ -689,9 +773,9 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 					}
 					setNamesLoader(parent, syms, vSpec.Names, func() {
 						if v := vSpec; v != nil { // only init once
-							old := p.SetInTestingFile(testingFile)
-							defer p.SetInTestingFile(old)
 							vSpec = nil
+							old, _ := p.SetCurFile(goFile, true)
+							defer p.RestoreCurFile(old)
 							loadVars(ctx, v, true)
 							removeNames(syms, v.Names)
 						}
@@ -706,6 +790,59 @@ func preloadFile(p *gox.Package, parent *pkgCtx, file string, f *ast.File, targe
 	}
 }
 
+func newType(pkg *types.Package, pos token.Pos, name string) *types.Named {
+	typName := types.NewTypeName(pos, pkg, name, nil)
+	if old := pkg.Scope().Insert(typName); old != nil {
+		log.Panicf("%s redeclared in this block\n\tprevious declaration at %v\n", name, "<TODO>")
+	}
+	return types.NewNamed(typName, nil, nil)
+}
+
+func initType(ctx *blockCtx, named *types.Named, typ types.Type) {
+	if named, ok := typ.(*types.Named); ok {
+		typ = getUnderlying(ctx, named)
+	}
+	named.SetUnderlying(typ)
+}
+
+func aliasType(pkg *types.Package, pos token.Pos, name string, typ types.Type) {
+	o := types.NewTypeName(pos, pkg, name, typ)
+	pkg.Scope().Insert(o)
+}
+
+func declFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl) {
+	name := d.Name.Name
+	if debugLoad {
+		if recv == nil {
+			log.Println("==> Load func", name)
+		} else {
+			log.Printf("==> Load method %v.%s\n", recv.Type(), name)
+		}
+	}
+	if name == "_" {
+		return
+	}
+	pkg := ctx.pkg.Types
+	sig := toFuncType(ctx, d.Type, recv)
+	fn := types.NewFunc(d.Pos(), pkg, name, sig)
+	if recv != nil {
+		typ := recv.Type()
+		switch t := typ.(type) {
+		case *types.Named:
+			t.AddMethod(fn)
+			return
+		case *types.Pointer:
+			if tt, ok := t.Elem().(*types.Named); ok {
+				tt.AddMethod(fn)
+				return
+			}
+		}
+		log.Panicf("invalid receiver type %v (%v is not a defined type)\n", typ, typ)
+	} else {
+		pkg.Scope().Insert(fn)
+	}
+}
+
 func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl) {
 	name := d.Name.Name
 	if debugLoad {
@@ -923,7 +1060,7 @@ func removeNames(syms map[string]loader, names []*ast.Ident) {
 
 func setNamesLoader(ctx *pkgCtx, syms map[string]loader, names []*ast.Ident, load func()) {
 	for _, name := range names {
-		initLoader(ctx, syms, name.Pos(), name.Name, load)
+		initLoader(ctx, syms, name.Pos(), name.Name, load, true)
 	}
 }
 
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 632fadd0..df83facd 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -230,6 +230,9 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 			if chkRedecl(name, field.Type.Pos()) {
 				continue
 			}
+			if t, ok := typ.(*types.Named); ok { // #1196: embedded type should ensure loaded
+				ctx.loadNamed(ctx.pkg, t)
+			}
 			fld := types.NewField(token.NoPos, pkg, name, typ, true)
 			fields = append(fields, fld)
 			tags = append(tags, toFieldTag(field.Tag))
@@ -316,7 +319,11 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type {
 	var embeddeds []types.Type
 	for _, m := range methodsList {
 		if m.Names == nil { // embedded
-			embeddeds = append(embeddeds, toType(ctx, m.Type))
+			typ := toType(ctx, m.Type)
+			if t, ok := typ.(*types.Named); ok { // #1198: embedded type should ensure loaded
+				ctx.loadNamed(ctx.pkg, t)
+			}
+			embeddeds = append(embeddeds, typ)
 			continue
 		}
 		name := m.Names[0].Name
diff --git a/cl/stmt.go b/cl/stmt.go
index f83a339e..5f37927d 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -728,6 +728,9 @@ func compileDeclStmt(ctx *blockCtx, expr *ast.DeclStmt) {
 
 func compileType(ctx *blockCtx, t *ast.TypeSpec) {
 	name := t.Name.Name
+	if name == "_" {
+		return
+	}
 	if t.Assign != token.NoPos { // alias type
 		ctx.cb.AliasType(name, toType(ctx, t.Type))
 	} else {
diff --git a/cmd/gop/main.go b/cmd/gop/main.go
index d8b8fb49..897c0cb0 100644
--- a/cmd/gop/main.go
+++ b/cmd/gop/main.go
@@ -23,9 +23,7 @@ import (
 	"strings"
 
 	"github.com/goplus/gop/cmd/internal/bug"
-	"github.com/goplus/gop/cmd/internal/deps"
 	"github.com/goplus/gop/cmd/internal/gopget"
-	"github.com/goplus/gop/cmd/internal/list"
 	"github.com/qiniu/x/log"
 
 	"github.com/goplus/gop/cmd/internal/base"
@@ -57,12 +55,12 @@ func init() {
 		mod.Cmd,
 		gopfmt.Cmd,
 		gopget.Cmd,
-		list.Cmd,
 		bug.Cmd,
 		clean.Cmd,
 		env.Cmd,
 		test.Cmd,
-		deps.Cmd,
+		// list.Cmd,
+		// deps.Cmd,
 		version.Cmd,
 	}
 }
diff --git a/cmd/gopdecl/goenv.go b/cmd/gopdecl/goenv.go
deleted file mode 100644
index f8429603..00000000
--- a/cmd/gopdecl/goenv.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package main
-
-import (
-	"os"
-	"path/filepath"
-	"runtime"
-)
-
-func initGoModCache() string {
-	val := os.Getenv("GOMODCACHE")
-	if val == "" {
-		val = filepath.Join(runtime.GOROOT(), "pkg/mod")
-		os.Setenv("GOMODCACHE", val)
-	}
-	return val
-}
diff --git a/cmd/gopdecl/gopdecl.go b/cmd/gopdecl/gopdecl.go
index cd5772de..7a82918c 100644
--- a/cmd/gopdecl/gopdecl.go
+++ b/cmd/gopdecl/gopdecl.go
@@ -1,28 +1,43 @@
+/*
+ * 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 main
 
 import (
 	"flag"
 	"fmt"
-	"go/ast"
-	"go/importer"
-	"go/parser"
 	"go/token"
-	"go/types"
-	"io/fs"
 	"log"
 	"os"
 	"path/filepath"
 	"strings"
 	"unicode"
 
-	"github.com/goplus/gop/ast/togo"
-	gopp "github.com/goplus/gop/parser"
-	"golang.org/x/tools/go/gcexportdata"
+	goast "go/ast"
+	goparser "go/parser"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/x/types"
+	"github.com/goplus/gox/packages"
 )
 
 var (
-	compiler = flag.String("c", "source", `compiler to use: source, gc or gccgo`)
-	internal = flag.Bool("i", false, "print internal declarations")
+	verboseFlag = flag.Bool("v", false, "print verbose information")
+	internal    = flag.Bool("i", false, "print internal declarations")
 )
 
 func usage() {
@@ -44,78 +59,67 @@ func isPublic(name string) bool {
 	return false
 }
 
-var (
-	goModCache string
-)
-
 func main() {
 	flag.Parse()
 	if flag.NArg() < 1 {
 		usage()
 		return
 	}
-	goModCache = initGoModCache()
-
-	var fset = token.NewFileSet()
-	var files []*ast.File
-	var imp types.Importer
-	switch *compiler {
-	case "gc":
-		packages := make(map[string]*types.Package)
-		imp = gcexportdata.NewImporter(fset, packages)
-	case "source":
-		imp = importer.ForCompiler(fset, "source", nil)
-	default:
-		log.Panicln("Unsupport compiler:", *compiler)
+	verbose := *verboseFlag
+	if verbose {
+		log.Println("==> Parsing ...")
 	}
 
+	var fset = token.NewFileSet()
+	var in *ast.Package
 	if infile := flag.Arg(0); isDir(infile) {
-		pkgs, first := gopp.ParseDir(fset, infile, func(fi fs.FileInfo) bool {
-			name := fi.Name()
-			if strings.HasPrefix(name, "_") {
-				return false
-			}
-			return !strings.HasSuffix(strings.TrimSuffix(name, filepath.Ext(name)), "_test")
-		}, 0)
+		pkgs, first := parser.ParseDir(fset, infile, nil, 0)
 		check(first)
 		for name, pkg := range pkgs {
 			if !strings.HasSuffix(name, "_test") {
-				for _, f := range pkg.Files {
-					files = append(files, togo.ASTFile(f, 0))
-				}
+				in = pkg
 				break
 			}
 		}
 	} else {
+		in = &ast.Package{
+			Files:   make(map[string]*ast.File),
+			GoFiles: make(map[string]*goast.File),
+		}
 		for i, n := 0, flag.NArg(); i < n; i++ {
 			infile = flag.Arg(i)
 			switch filepath.Ext(infile) {
 			case ".gop":
-				f, err := gopp.ParseFile(fset, infile, nil, 0)
+				f, err := parser.ParseFile(fset, infile, nil, 0)
 				check(err)
-				files = append(files, togo.ASTFile(f, 0))
+				in.Files[infile] = f
 			case ".go":
-				f, err := parser.ParseFile(fset, infile, nil, 0)
+				f, err := goparser.ParseFile(fset, infile, nil, 0)
 				check(err)
-				files = append(files, f)
+				in.GoFiles[infile] = f
 			default:
 				log.Panicln("Unknown support file:", infile)
 			}
 		}
 	}
 
+	if verbose {
+		log.Println("==> Loading ...")
+	}
 	conf := &types.Config{
-		Importer:         imp,
+		Importer:         packages.NewImporter(fset),
 		IgnoreFuncBodies: true,
 		Error: func(err error) {
 			log.Println(err)
 		},
 		DisableUnusedImportCheck: true,
 	}
-
-	pkg, err := conf.Check("", fset, files, nil)
+	pkg, err := types.Load(fset, in, conf)
 	check(err)
 
+	if verbose {
+		log.Println("==> Printing ...")
+	}
 	scope := pkg.Scope()
 	names := scope.Names()
 	for _, name := range names {
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index 011ed809..e40a81ef 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -19,14 +19,17 @@ package build
 
 import (
 	"fmt"
+	"log"
 	"os"
-	"os/exec"
-
-	"github.com/qiniu/x/log"
+	"path/filepath"
+	"reflect"
+	"syscall"
 
+	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
-	"github.com/goplus/gop/x/gopproj"
+	"github.com/goplus/gop/x/gocmd"
+	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 )
@@ -38,8 +41,8 @@ var Cmd = &base.Command{
 }
 
 var (
-	flagVerbose = flag.Bool("v", false, "print verbose information.")
-	flagOutput  = flag.String("o", "a.out", "gop build output file.")
+	flagVerbose = flag.Bool("v", false, "print verbose information")
+	flagOutput  = flag.String("o", "", "gop build output file")
 	flag        = &Cmd.Flag
 )
 
@@ -50,7 +53,7 @@ func init() {
 func runCmd(_ *base.Command, args []string) {
 	err := flag.Parse(args)
 	if err != nil {
-		log.Fatalln("parse input arguments failed:", err)
+		log.Panicln("parse input arguments failed:", err)
 	}
 
 	if *flagVerbose {
@@ -63,35 +66,50 @@ func runCmd(_ *base.Command, args []string) {
 	if len(args) == 0 {
 		args = []string{"."}
 	}
-	gopBuild(args)
-}
 
-func gopBuild(args []string) {
 	proj, args, err := gopprojs.ParseOne(args...)
 	if err != nil {
-		log.Fatalln(err)
+		log.Panicln(err)
+	}
+	if len(args) != 0 {
+		log.Panicln("too many arguments:", args)
 	}
 
-	flags := 0
-	ctx, goProj, err := gopproj.OpenProject(flags, proj)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err)
-		os.Exit(1)
+	gopEnv := gopenv.Get()
+	conf := &gop.Config{Gop: gopEnv}
+	confCmd := &gocmd.BuildConfig{Gop: gopEnv}
+	if *flagOutput != "" {
+		output, err := filepath.Abs(*flagOutput)
+		if err != nil {
+			log.Panicln(err)
+		}
+		confCmd.Flags = []string{"-o", output}
 	}
-	goProj.BuildArgs = []string{"-o", *flagOutput}
-	cmd := ctx.GoCommand("build", goProj)
-	cmd.Stdin = os.Stdin
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	cmd.Env = os.Environ()
-	err = cmd.Run()
-	if err != nil {
-		switch e := err.(type) {
-		case *exec.ExitError:
-			os.Exit(e.ExitCode())
-		default:
-			log.Fatalln(err)
+	build(proj, conf, confCmd)
+}
+
+func build(proj gopprojs.Proj, conf *gop.Config, build *gocmd.BuildConfig) {
+	var obj string
+	var err error
+	switch v := proj.(type) {
+	case *gopprojs.DirProj:
+		obj = v.Dir
+		err = gop.BuildDir(obj, conf, build)
+	case *gopprojs.PkgPathProj:
+		obj = v.Path
+		err = gop.BuildPkgPath("", v.Path, conf, build)
+	case *gopprojs.FilesProj:
+		err = gop.BuildFiles(v.Files, conf, build)
+		if err != nil {
+			log.Panicln(err)
 		}
+	default:
+		log.Panicln("`gop build` doesn't support", reflect.TypeOf(v))
+	}
+	if err == syscall.ENOENT {
+		fmt.Fprintf(os.Stderr, "gop build %v: not found\n", obj)
+	} else if err != nil {
+		log.Panicln(err)
 	}
 }
 
diff --git a/cmd/internal/clean/clean.go b/cmd/internal/clean/clean.go
index e0e896b2..12d448ec 100644
--- a/cmd/internal/clean/clean.go
+++ b/cmd/internal/clean/clean.go
@@ -19,12 +19,11 @@ package clean
 import (
 	"fmt"
 	"io/ioutil"
+	"log"
 	"os"
 	"path/filepath"
 	"strings"
 
-	"github.com/qiniu/x/log"
-
 	"github.com/goplus/gop/cmd/internal/base"
 )
 
diff --git a/cmd/internal/deps/deps.go b/cmd/internal/deps/deps.go
index 1d9249ff..1d5f6a04 100644
--- a/cmd/internal/deps/deps.go
+++ b/cmd/internal/deps/deps.go
@@ -16,6 +16,7 @@
 
 package deps
 
+/*
 import (
 	"fmt"
 	"log"
@@ -67,5 +68,6 @@ func check(err error) {
 		log.Fatalln(err)
 	}
 }
+*/
 
 // -----------------------------------------------------------------------------
diff --git a/cmd/internal/env/env.go b/cmd/internal/env/env.go
index fa7e3ad7..bb77bb58 100644
--- a/cmd/internal/env/env.go
+++ b/cmd/internal/env/env.go
@@ -27,6 +27,8 @@ import (
 
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/env"
+	"github.com/goplus/mod"
+	"github.com/goplus/mod/modcache"
 )
 
 // Cmd - gop env
@@ -69,9 +71,8 @@ func runCmd(_ *base.Command, args []string) {
 	gopEnv["BUILDDATE"] = env.BuildDate()
 	gopEnv["GOPVERSION"] = env.Version()
 	gopEnv["GOPROOT"] = env.GOPROOT()
-	gopEnv["GOPATH"] = env.GOPATH()
-	gopEnv["GOMODCACHE"] = env.GOMODCACHE()
-	gopEnv["GOPMOD"], _ = env.GOPMOD("")
+	gopEnv["GOMODCACHE"] = modcache.GOMODCACHE
+	gopEnv["GOPMOD"], _ = mod.GOPMOD("", 0)
 	gopEnv["HOME"] = env.HOME()
 
 	vars := flag.Args()
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index 3f7f038b..3d85f5c2 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -18,24 +18,23 @@
 package gengo
 
 import (
-	"os"
-
-	"github.com/qiniu/x/log"
+	"log"
+	"reflect"
 
+	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
-	"github.com/goplus/gop/x/gengo"
+	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 )
 
 // gop go
 var Cmd = &base.Command{
-	UsageLine: "gop go [-v -n] [packages]",
+	UsageLine: "gop go [-v] [packages]",
 	Short:     "Convert Go+ packages into Go packages",
 }
 
 var (
-	flagDontRun = flag.Bool("n", false, "print the processing flow but do not run them.")
 	flagVerbose = flag.Bool("v", false, "print verbose information.")
 	flag        = &Cmd.Flag
 )
@@ -47,20 +46,36 @@ func init() {
 func runCmd(cmd *base.Command, args []string) {
 	err := flag.Parse(args)
 	if err != nil {
-		log.Fatalln("parse input arguments failed:", err)
+		log.Panicln("parse input arguments failed:", err)
 	}
 	pattern := flag.Args()
 	if len(pattern) == 0 {
 		pattern = []string{"."}
 	}
 
+	projs, err := gopprojs.ParseAll(pattern...)
+	if err != nil {
+		log.Panicln("gopprojs.ParseAll:", err)
+	}
+
 	if *flagVerbose {
 		gox.SetDebug(gox.DbgFlagAll &^ gox.DbgFlagComments)
 		cl.SetDebug(cl.DbgFlagAll)
 		cl.SetDisableRecover(true)
 	}
-	if !gengo.GenGo(gengo.Config{}, *flagDontRun, ".", pattern...) {
-		os.Exit(1)
+
+	for _, proj := range projs {
+		switch v := proj.(type) {
+		case *gopprojs.DirProj:
+			err = gop.GenGo(v.Dir, nil)
+		case *gopprojs.PkgPathProj:
+			_, err = gop.GenGoPkgPath("", v.Path, nil, true)
+		default:
+			log.Panicln("`gop go` doesn't support", reflect.TypeOf(v))
+		}
+		if err != nil {
+			log.Panicln(err)
+		}
 	}
 }
 
diff --git a/cmd/internal/gopfmt/fmt.go b/cmd/internal/gopfmt/fmt.go
index ed11d826..e721b740 100644
--- a/cmd/internal/gopfmt/fmt.go
+++ b/cmd/internal/gopfmt/fmt.go
@@ -22,12 +22,11 @@ import (
 	"fmt"
 	"io/fs"
 	"io/ioutil"
+	"log"
 	"os"
 	"path/filepath"
 	"strings"
 
-	"github.com/qiniu/x/log"
-
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/format"
 
diff --git a/cmd/internal/gopget/get.go b/cmd/internal/gopget/get.go
index c744f94b..e99e359c 100644
--- a/cmd/internal/gopget/get.go
+++ b/cmd/internal/gopget/get.go
@@ -23,8 +23,9 @@ import (
 	"syscall"
 
 	"github.com/goplus/gop/cmd/internal/base"
-	"github.com/goplus/gop/x/mod/modfetch"
-	"github.com/goplus/gop/x/mod/modload"
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/mod/modfetch"
+	"github.com/goplus/mod/modload"
 )
 
 // -----------------------------------------------------------------------------
@@ -59,14 +60,14 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func get(pkgPath string) {
-	mod, err := modload.Load(".")
+	mod, err := modload.Load(".", 0)
 	hasMod := (err != syscall.ENOENT)
 	if hasMod {
 		check(err)
-		check(mod.UpdateGoMod(true))
+		check(mod.UpdateGoMod(gopenv.Get(), true))
 	}
 	modPath, _ := splitPkgPath(pkgPath)
-	modVer, isClass, err := modfetch.Get(modPath)
+	modVer, isClass, err := modfetch.Get(gopenv.Get(), modPath)
 	check(err)
 	if hasMod {
 		if isClass {
@@ -76,7 +77,7 @@ func get(pkgPath string) {
 		check(mod.AddRequire(modVer.Path, modVer.Version))
 		fmt.Fprintf(os.Stderr, "gop get: added %s %s\n", modVer.Path, modVer.Version)
 		check(mod.Save())
-		check(mod.UpdateGoMod(false))
+		check(mod.UpdateGoMod(gopenv.Get(), false))
 	}
 }
 
diff --git a/cmd/internal/help/help.go b/cmd/internal/help/help.go
index 037d81cd..9d48f3c9 100644
--- a/cmd/internal/help/help.go
+++ b/cmd/internal/help/help.go
@@ -21,14 +21,13 @@ import (
 	"bufio"
 	"fmt"
 	"io"
+	"log"
 	"os"
 	"strings"
 	"text/template"
 	"unicode"
 	"unicode/utf8"
 
-	"github.com/qiniu/x/log"
-
 	"github.com/goplus/gop/cmd/internal/base"
 )
 
diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go
index d23701d5..885d273a 100644
--- a/cmd/internal/install/install.go
+++ b/cmd/internal/install/install.go
@@ -18,13 +18,18 @@
 package install
 
 import (
+	"fmt"
+	"log"
 	"os"
+	"reflect"
+	"syscall"
 
-	"github.com/qiniu/x/log"
-
+	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
-	"github.com/goplus/gop/x/gengo"
+	"github.com/goplus/gop/x/gocmd"
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 )
 
@@ -54,16 +59,48 @@ func runCmd(_ *base.Command, args []string) {
 		pattern = []string{"."}
 	}
 
+	projs, err := gopprojs.ParseAll(pattern...)
+	if err != nil {
+		log.Panicln("gopprojs.ParseAll:", err)
+	}
+
 	if *flagVerbose {
 		gox.SetDebug(gox.DbgFlagAll &^ gox.DbgFlagComments)
 		cl.SetDebug(cl.DbgFlagAll)
 		cl.SetDisableRecover(true)
 	}
 
-	if !gengo.GenGo(gengo.Config{}, false, ".", pattern...) {
-		os.Exit(1)
+	gopEnv := gopenv.Get()
+	conf := &gop.Config{Gop: gopEnv}
+	confCmd := &gocmd.Config{Gop: gopEnv}
+	for _, proj := range projs {
+		install(proj, conf, confCmd)
+	}
+}
+
+func install(proj gopprojs.Proj, conf *gop.Config, install *gocmd.InstallConfig) {
+	var obj string
+	var err error
+	switch v := proj.(type) {
+	case *gopprojs.DirProj:
+		obj = v.Dir
+		err = gop.InstallDir(obj, conf, install)
+	case *gopprojs.PkgPathProj:
+		obj = v.Path
+		err = gop.InstallPkgPath("", v.Path, conf, install)
+	case *gopprojs.FilesProj:
+		err = gop.InstallFiles(v.Files, conf, install)
+		if err != nil {
+			log.Panicln(err)
+		}
+	default:
+		log.Panicln("`gop install` doesn't support", reflect.TypeOf(v))
+	}
+	if err == syscall.ENOENT {
+		fmt.Fprintf(os.Stderr, "gop install %v: not found\n", obj)
+	} else if err != nil {
+		log.Panicln(err)
 	}
-	base.RunGoCmd(".", "install", args...)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/cmd/internal/list/list.go b/cmd/internal/list/list.go
index 11942d1b..a53a0cdc 100644
--- a/cmd/internal/list/list.go
+++ b/cmd/internal/list/list.go
@@ -16,6 +16,7 @@
 
 package list
 
+/*
 import (
 	"fmt"
 	"log"
@@ -64,5 +65,6 @@ func check(err error) {
 		log.Fatalln(err)
 	}
 }
+*/
 
 // -----------------------------------------------------------------------------
diff --git a/cmd/internal/mod/init.go b/cmd/internal/mod/init.go
index 7f2e954c..7cd1b45d 100644
--- a/cmd/internal/mod/init.go
+++ b/cmd/internal/mod/init.go
@@ -24,7 +24,7 @@ import (
 
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/env"
-	"github.com/goplus/gop/x/mod/modload"
+	"github.com/goplus/mod/modload"
 )
 
 // gop mod init
diff --git a/cmd/internal/mod/tidy.go b/cmd/internal/mod/tidy.go
index 6a9f486a..3c82a769 100644
--- a/cmd/internal/mod/tidy.go
+++ b/cmd/internal/mod/tidy.go
@@ -20,7 +20,8 @@ import (
 	"log"
 
 	"github.com/goplus/gop/cmd/internal/base"
-	"github.com/goplus/gop/x/mod/modload"
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/mod/modload"
 )
 
 // gop mod tidy
@@ -34,9 +35,9 @@ func init() {
 }
 
 func runTidy(cmd *base.Command, args []string) {
-	mod, err := modload.Load(".")
+	mod, err := modload.Load(".", 0)
 	check(err)
-	check(mod.Tidy())
+	check(mod.Tidy(gopenv.Get()))
 }
 
 func check(err error) {
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index 02432514..a9518686 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -20,11 +20,14 @@ package run
 import (
 	"fmt"
 	"os"
-	"os/exec"
+	"reflect"
+	"syscall"
 
+	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
-	"github.com/goplus/gop/x/gopproj"
+	"github.com/goplus/gop/x/gocmd"
+	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 	"github.com/qiniu/x/log"
@@ -32,7 +35,7 @@ import (
 
 // gop run
 var Cmd = &base.Command{
-	UsageLine: "gop run [-asm -quiet -debug -nr -gop -prof] package [arguments...]",
+	UsageLine: "gop run [-nc -asm -quiet -debug -prof] package [arguments...]",
 	Short:     "Run a Go+ program",
 }
 
@@ -42,9 +45,7 @@ var (
 	flagVerbose = flag.Bool("v", false, "print verbose information")
 	flagQuiet   = flag.Bool("quiet", false, "don't generate any compiling stage log")
 	flagDebug   = flag.Bool("debug", false, "set log level to debug")
-	flagNorun   = flag.Bool("nr", false, "don't run if no change")
-	flagRTOE    = flag.Bool("rtoe", false, "remove tempfile on error")
-	flagGop     = flag.Bool("gop", false, "parse a .go file as a .gop file")
+	flagNoChdir = flag.Bool("nc", false, "don't change dir (only for `gop run pkgPath`)")
 	flagProf    = flag.Bool("prof", false, "do profile and generate profile report")
 )
 
@@ -60,61 +61,59 @@ func runCmd(cmd *base.Command, args []string) {
 	if flag.NArg() < 1 {
 		cmd.Usage(os.Stderr)
 	}
-	gopRun(flag.Args())
-}
 
-func gopRun(args []string) {
-	proj, args, err := gopprojs.ParseOne(args...)
+	proj, args, err := gopprojs.ParseOne(flag.Args()...)
 	if err != nil {
 		log.Fatalln(err)
 	}
 
 	if *flagQuiet {
 		log.SetOutputLevel(0x7000)
+	} else if *flagVerbose {
+		gox.SetDebug(gox.DbgFlagAll &^ gox.DbgFlagComments)
+		cl.SetDebug(cl.DbgFlagAll)
+		cl.SetDisableRecover(true)
 	} else if *flagDebug {
 		log.SetOutputLevel(log.Ldebug)
 		gox.SetDebug(gox.DbgFlagAll)
 		cl.SetDebug(cl.DbgFlagAll)
-	}
-	if *flagVerbose {
-		gox.SetDebug(gox.DbgFlagAll &^ gox.DbgFlagComments)
-		cl.SetDebug(cl.DbgFlagAll)
-		cl.SetDisableRecover(true)
 	} else if *flagAsm {
 		gox.SetDebug(gox.DbgFlagInstruction)
 	}
+
 	if *flagProf {
 		panic("TODO: profile not impl")
 	}
 
-	flags := 0
-	if *flagGop {
-		flags = gopproj.FlagGoAsGoPlus
-	}
-	ctx, goProj, err := gopproj.OpenProject(flags, proj)
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err)
-		os.Exit(1)
-	}
-	goProj.ExecArgs = args
-	goProj.FlagNRINC = *flagNorun
-	goProj.FlagRTOE = *flagRTOE
-	if goProj.FlagRTOE {
-		goProj.UseDefaultCtx = true
-	}
-	cmd := ctx.GoCommand("run", goProj)
-	cmd.Stdin = os.Stdin
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	cmd.Env = os.Environ()
-	err = cmd.Run()
-	if err != nil {
-		switch e := err.(type) {
-		case *exec.ExitError:
-			os.Exit(e.ExitCode())
-		default:
-			log.Fatalln(err)
+	noChdir := *flagNoChdir
+	gopEnv := gopenv.Get()
+	conf := &gop.Config{Gop: gopEnv}
+	confCmd := &gocmd.Config{Gop: gopEnv}
+	run(proj, args, !noChdir, conf, confCmd)
+}
+
+func run(proj gopprojs.Proj, args []string, chDir bool, conf *gop.Config, run *gocmd.RunConfig) {
+	var obj string
+	var err error
+	switch v := proj.(type) {
+	case *gopprojs.DirProj:
+		obj = v.Dir
+		err = gop.RunDir(obj, args, conf, run)
+	case *gopprojs.PkgPathProj:
+		obj = v.Path
+		err = gop.RunPkgPath(v.Path, args, chDir, conf, run)
+	case *gopprojs.FilesProj:
+		err = gop.RunFiles("", v.Files, args, conf, run)
+		if err != nil {
+			log.Panicln(err)
 		}
+	default:
+		log.Panicln("`gop run` doesn't support", reflect.TypeOf(v))
+	}
+	if err == syscall.ENOENT {
+		fmt.Fprintf(os.Stderr, "gop run %v: not found\n", obj)
+	} else if err != nil {
+		log.Panicln(err)
 	}
 }
 
diff --git a/dummy.go b/dummy.go
deleted file mode 100644
index e9b65410..00000000
--- a/dummy.go
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Copyright (c) 2021 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 gop
diff --git a/env/goenv.go b/env/goenv.go
index 03ea5018..2e4fae97 100644
--- a/env/goenv.go
+++ b/env/goenv.go
@@ -18,8 +18,6 @@ package env
 
 import (
 	"os"
-	"path/filepath"
-	"runtime"
 )
 
 // -----------------------------------------------------------------------------
@@ -29,44 +27,3 @@ func HOME() string {
 }
 
 // -----------------------------------------------------------------------------
-
-func GOPATH() string {
-	s := os.Getenv("GOPATH")
-	if s == "" {
-		return defaultGOPATH()
-	}
-	return s
-}
-
-func defaultGOPATH() string {
-	if home := HOME(); home != "" {
-		def := filepath.Join(home, "go")
-		if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) {
-			// Don't set the default GOPATH to GOROOT,
-			// as that will trigger warnings from the go tool.
-			return ""
-		}
-		return def
-	}
-	return ""
-}
-
-// -----------------------------------------------------------------------------
-
-func GOMODCACHE() string {
-	val := os.Getenv("GOMODCACHE")
-	if val == "" {
-		return gopathJoin("pkg/mod")
-	}
-	return val
-}
-
-func gopathJoin(rel string) string {
-	list := filepath.SplitList(GOPATH())
-	if len(list) == 0 || list[0] == "" {
-		return ""
-	}
-	return filepath.Join(list[0], rel)
-}
-
-// -----------------------------------------------------------------------------
diff --git a/env/mod.go b/env/mod.go
deleted file mode 100644
index a916ed7f..00000000
--- a/env/mod.go
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) 2021 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 env
-
-import (
-	"os"
-	"path/filepath"
-	"strings"
-	"syscall"
-)
-
-// -----------------------------------------------------------------------------
-
-func GOPMOD(dir string) (file string, err error) {
-	if dir == "" {
-		dir = "."
-	}
-	if dir, err = filepath.Abs(dir); err != nil {
-		return
-	}
-	for dir != "" {
-		file = filepath.Join(dir, "gop.mod")
-		if fi, e := os.Lstat(file); e == nil && !fi.IsDir() {
-			return
-		}
-		file = filepath.Join(dir, "go.mod")
-		if fi, e := os.Lstat(file); e == nil && !fi.IsDir() {
-			return
-		}
-		if dir, file = filepath.Split(strings.TrimRight(dir, "/\\")); file == "" {
-			break
-		}
-	}
-	return "", syscall.ENOENT
-}
-
-// -----------------------------------------------------------------------------
diff --git a/gengo.go b/gengo.go
new file mode 100644
index 00000000..6bc7ef22
--- /dev/null
+++ b/gengo.go
@@ -0,0 +1,212 @@
+/*
+ * 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 gop
+
+import (
+	"fmt"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+	"syscall"
+
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/mod/env"
+	"github.com/goplus/mod/gopmod"
+	"github.com/goplus/mod/modcache"
+	"github.com/goplus/mod/modfetch"
+)
+
+const (
+	testingGoFile    = "_test"
+	autoGenFile      = "gop_autogen.go"
+	autoGenTestFile  = "gop_autogen_test.go"
+	autoGen2TestFile = "gop_autogen2_test.go"
+)
+
+// -----------------------------------------------------------------------------
+
+func GenGo(dir string, conf *Config) (err error) {
+	recursively := strings.HasSuffix(dir, "/...")
+	if recursively {
+		dir = dir[:len(dir)-4]
+	}
+	return genGoDir(dir, conf, recursively)
+}
+
+func genGoDir(dir string, conf *Config, recursively bool) (err error) {
+	if recursively {
+		return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+			if err == nil && d.IsDir() {
+				err = genGoIn(path, conf, true)
+				if err != nil {
+					fmt.Fprintln(os.Stderr, err)
+				}
+			}
+			return err
+		})
+	}
+	return genGoIn(dir, conf, false)
+}
+
+func genGoIn(dir string, conf *Config, prompt bool) (err error) {
+	out, test, err := LoadDir(dir, conf)
+	if err != nil {
+		if err == syscall.ENOENT { // no Go+ source files
+			return nil
+		}
+		return
+	}
+
+	if prompt {
+		fmt.Printf("GenGo %v ...\n", dir)
+	}
+
+	os.MkdirAll(dir, 0755)
+	file := filepath.Join(dir, autoGenFile)
+	err = out.WriteFile(file)
+	if err != nil {
+		return
+	}
+
+	err = out.WriteFile(filepath.Join(dir, autoGenTestFile), testingGoFile)
+	if err != nil && err != syscall.ENOENT {
+		return
+	}
+
+	if test != nil {
+		err = test.WriteFile(filepath.Join(dir, autoGen2TestFile), testingGoFile)
+	} else {
+		err = nil
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, err error) {
+	recursively := strings.HasSuffix(pkgPath, "/...")
+	if recursively {
+		pkgPath = pkgPath[:len(pkgPath)-4]
+	}
+
+	gop := gopEnv(conf)
+	mod, err := gopmod.Load(workDir, gop)
+	if err == syscall.ENOENT && allowExtern {
+		remotePkgPathDo(pkgPath, gop, func(dir string) {
+			os.Chmod(dir, 0755)
+			defer os.Chmod(dir, 0555)
+			localDir = dir
+			err = genGoDir(dir, conf, recursively)
+		}, func(e error) {
+			err = e
+		})
+		return
+	} else if err != nil {
+		return
+	}
+
+	pkg, err := mod.Lookup(pkgPath)
+	if err != nil {
+		return
+	}
+	localDir = pkg.Dir
+	if pkg.Type == gopmod.PkgtExtern {
+		os.Chmod(localDir, 0755)
+		defer os.Chmod(localDir, 0555)
+	}
+	err = genGoDir(localDir, conf, recursively)
+	return
+}
+
+func remotePkgPathDo(pkgPath string, gop *env.Gop, doSth func(dir string), onErr func(e error)) {
+	modPath, leftPart := splitPkgPath(pkgPath)
+	modVer, _, err := modfetch.Get(gop, modPath)
+	if err != nil {
+		onErr(err)
+	} else if dir, err := modcache.Path(modVer); err != nil {
+		onErr(err)
+	} else {
+		doSth(filepath.Join(dir, leftPart))
+	}
+}
+
+func splitPkgPath(pkgPath string) (modPath, leftPart string) {
+	if strings.HasPrefix(pkgPath, "github.com/") {
+		parts := strings.SplitN(pkgPath, "/", 4)
+		if len(parts) > 3 {
+			leftPart = parts[3]
+			if pos := strings.IndexByte(leftPart, '@'); pos > 0 {
+				parts[2] += leftPart[pos:]
+				leftPart = leftPart[:pos]
+			}
+			modPath = strings.Join(parts[:3], "/")
+			return
+		}
+	}
+	return pkgPath, "" // TODO:
+}
+
+func gopEnv(conf *Config) *env.Gop {
+	if conf != nil {
+		if gop := conf.Gop; gop != nil {
+			return gop
+		}
+	}
+	return gopenv.Get()
+}
+
+// -----------------------------------------------------------------------------
+
+func GenGoFiles(autogen string, files []string, conf *Config) (result []string, err error) {
+	if autogen == "" {
+		autogen = "gop_autogen.go"
+		if len(files) == 1 {
+			file := files[0]
+			srcDir, fname := filepath.Split(file)
+			if hasMultiFiles(srcDir, ".gop") {
+				autogen = filepath.Join(srcDir, "gop_autogen_"+fname+".go")
+			}
+		}
+	}
+	out, err := LoadFiles(files, conf)
+	if err != nil {
+		return
+	}
+	result = append(result, autogen)
+	err = out.WriteFile(autogen)
+	return
+}
+
+func hasMultiFiles(srcDir string, ext string) bool {
+	var has bool
+	if f, err := os.Open(srcDir); err == nil {
+		defer f.Close()
+		fis, _ := f.ReadDir(-1)
+		for _, fi := range fis {
+			if !fi.IsDir() && filepath.Ext(fi.Name()) == ext {
+				if has {
+					return true
+				}
+				has = true
+			}
+		}
+	}
+	return false
+}
+
+// -----------------------------------------------------------------------------
diff --git a/go.mod b/go.mod
index e5866bfc..992deeef 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,7 @@ module github.com/goplus/gop
 go 1.16
 
 require (
-	github.com/goplus/gox v1.10.8
+	github.com/goplus/gox v1.11.6
+	github.com/goplus/mod v0.9.3
 	github.com/qiniu/x v1.11.5
-	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
-	golang.org/x/tools v0.1.10
 )
diff --git a/go.sum b/go.sum
index 4514d140..ee355176 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,15 @@
-github.com/goplus/gox v1.10.8 h1:Dra25Sxz1BOIzREFqMwfc+yGDA1GoEBzUHMqKaC7EKg=
-github.com/goplus/gox v1.10.8/go.mod h1:gu7fuQF8RmWPZUjd+tEJGuV3m/vOEv0bHrct0x/KatM=
+github.com/goplus/gox v1.11.6 h1:gCJmuuZz0QIW2c5zT+yCItis9npc5Ud/WQydOYU3XXw=
+github.com/goplus/gox v1.11.6/go.mod h1:gu7fuQF8RmWPZUjd+tEJGuV3m/vOEv0bHrct0x/KatM=
+github.com/goplus/mod v0.9.3 h1:kXN2kavdUacqy39Lh/pIdT0zvBTKevb4u96ZbapPbcg=
+github.com/goplus/mod v0.9.3/go.mod h1:NHU13OjeNV3ez1f+R9FLJIlIun0KNSuZb0jnmP3N3o8=
 github.com/qiniu/x v1.11.5 h1:TYr5cl4g2yoHAZeDK4MTjKF6CMoG+IHlCDvvM5qym6U=
 github.com/qiniu/x v1.11.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 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/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
 golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -29,5 +32,4 @@ golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
 golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/load.go b/load.go
new file mode 100644
index 00000000..93bc61fd
--- /dev/null
+++ b/load.go
@@ -0,0 +1,165 @@
+/*
+ * 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 gop
+
+import (
+	"errors"
+	"go/token"
+	"go/types"
+	"io/fs"
+	"strings"
+	"syscall"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/gox"
+	"github.com/goplus/mod/env"
+	"github.com/goplus/mod/gopmod"
+)
+
+type Config struct {
+	Gop      *env.Gop
+	Fset     *token.FileSet
+	Filter   func(fs.FileInfo) bool
+	Importer types.Importer
+
+	UpdateGoMod     bool
+	CheckModChanged bool
+}
+
+// -----------------------------------------------------------------------------
+
+func LoadDir(dir string, conf *Config) (out, test *gox.Package, err error) {
+	if conf == nil {
+		conf = new(Config)
+	}
+	fset := conf.Fset
+	if fset == nil {
+		fset = token.NewFileSet()
+	}
+	gop := conf.Gop
+	if gop == nil {
+		gop = gopenv.Get()
+	}
+
+	mod, err := gopmod.Load(dir, gop)
+	if err != nil && err != syscall.ENOENT {
+		return
+	}
+	if mod != nil {
+		err = mod.RegisterClasses()
+		if err != nil {
+			return
+		}
+		if conf.UpdateGoMod {
+			err = mod.UpdateGoMod(gop, conf.CheckModChanged)
+			if err != nil {
+				return
+			}
+		}
+	} else {
+		mod = new(gopmod.Module)
+	}
+
+	pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{
+		IsClass: mod.IsClass,
+		Filter:  conf.Filter,
+		Mode:    parser.ParseComments,
+	})
+	if err != nil {
+		return
+	}
+
+	var pkgTest *ast.Package
+	var clConf = &cl.Config{
+		WorkingDir:  dir,
+		GopRoot:     gop.Root,
+		Fset:        fset,
+		Importer:    conf.Importer,
+		LookupClass: mod.LookupClass,
+	}
+	for name, pkg := range pkgs {
+		if strings.HasSuffix(name, "_test") {
+			if pkgTest != nil {
+				return nil, nil, errMultiTestPackges
+			}
+			pkgTest = pkg
+			continue
+		}
+		if out != nil {
+			return nil, nil, errMultiPackges
+		}
+		if len(pkg.Files) == 0 { // no Go+ source files
+			break
+		}
+		out, err = cl.NewPackage("", pkg, clConf)
+		if err != nil {
+			return
+		}
+	}
+	if out == nil {
+		return nil, nil, syscall.ENOENT
+	}
+	if pkgTest != nil {
+		test, err = cl.NewPackage("", pkgTest, clConf)
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
+	if conf == nil {
+		conf = new(Config)
+	}
+	fset := conf.Fset
+	if fset == nil {
+		fset = token.NewFileSet()
+	}
+	gop := conf.Gop
+	if gop == nil {
+		gop = gopenv.Get()
+	}
+
+	pkgs, err := parser.ParseFiles(fset, files, parser.ParseComments)
+	if err != nil {
+		return
+	}
+	if len(pkgs) != 1 {
+		return nil, errMultiPackges
+	}
+	for _, pkg := range pkgs {
+		out, err = cl.NewPackage("", pkg, &cl.Config{
+			GopRoot:  gop.Root,
+			Fset:     fset,
+			Importer: conf.Importer,
+		})
+		break
+	}
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+var (
+	errMultiPackges     = errors.New("multiple packages")
+	errMultiTestPackges = errors.New("multiple test packages")
+)
+
+// -----------------------------------------------------------------------------
diff --git a/parser/interface.go b/parser/interface.go
index be5e4a8c..a0fd6bff 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -42,6 +42,8 @@ const (
 	DeclarationErrors
 	// AllErrors - report all errors (not just the first 10 on different lines)
 	AllErrors
+	// ParseGoAsGoPlus - parse Go files by gop/parser
+	ParseGoAsGoPlus
 )
 
 // ParseFile parses the source code of a single Go source file and returns
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 4273085f..5b80f460 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -26,6 +26,9 @@ import (
 	"path/filepath"
 	"strings"
 
+	goast "go/ast"
+	goparser "go/parser"
+
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
 )
@@ -141,13 +144,14 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 		}
 		fname := d.Name()
 		ext := filepath.Ext(fname)
-		var isProj, isClass bool
+		var isProj, isClass, useGoParser bool
 		switch ext {
 		case ".gop":
 		case ".go":
 			if strings.HasPrefix(fname, "gop_autogen") {
 				continue
 			}
+			useGoParser = (conf.Mode & ParseGoAsGoPlus) == 0
 		default:
 			if isProj, isClass = conf.IsClass(ext); !isClass {
 				continue
@@ -155,22 +159,24 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 		}
 		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || conf.Filter(d)) {
 			filename := fs.Join(path, fname)
-			if filedata, err := fs.ReadFile(filename); err == nil {
-				if src, err := ParseFSFile(fset, fs, filename, filedata, conf.Mode); err == nil {
-					src.IsProj, src.IsClass = isProj, isClass
-					name := src.Name.Name
-					pkg, found := pkgs[name]
-					if !found {
-						pkg = &ast.Package{
-							Name:  name,
-							Files: make(map[string]*ast.File),
+			if useGoParser {
+				if filedata, err := fs.ReadFile(filename); err == nil {
+					if src, err := goparser.ParseFile(fset, filename, filedata, goparser.Mode(conf.Mode)); err == nil {
+						pkg := reqPkg(pkgs, src.Name.Name)
+						if pkg.GoFiles == nil {
+							pkg.GoFiles = make(map[string]*goast.File)
 						}
-						pkgs[name] = pkg
+						pkg.GoFiles[filename] = src
+					} else {
+						first = err
 					}
-					pkg.Files[filename] = src
-				} else if first == nil {
+				} else {
 					first = err
 				}
+			} else if src, err := ParseFSFile(fset, fs, filename, nil, conf.Mode); err == nil {
+				src.IsProj, src.IsClass = isProj, isClass
+				pkg := reqPkg(pkgs, src.Name.Name)
+				pkg.Files[filename] = src
 			} else if first == nil {
 				first = err
 			}
@@ -179,6 +185,18 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 	return
 }
 
+func reqPkg(pkgs map[string]*ast.Package, name string) *ast.Package {
+	pkg, found := pkgs[name]
+	if !found {
+		pkg = &ast.Package{
+			Name:  name,
+			Files: make(map[string]*ast.File),
+		}
+		pkgs[name] = pkg
+	}
+	return pkg
+}
+
 func defaultIsClass(ext string) (isProj bool, ok bool) {
 	switch ext {
 	case ".gmx":
diff --git a/x/gengo/gengo.go b/x/gengo/gengo.go
deleted file mode 100644
index def17b5a..00000000
--- a/x/gengo/gengo.go
+++ /dev/null
@@ -1,574 +0,0 @@
-/*
- * Copyright (c) 2021 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 gengo
-
-import (
-	"bytes"
-	"crypto/md5"
-	"encoding/hex"
-	"errors"
-	"fmt"
-	"go/types"
-	"io"
-	"log"
-	"os"
-	"path/filepath"
-	"sort"
-	"strings"
-	"syscall"
-	"time"
-
-	"github.com/goplus/gop/ast"
-	"github.com/goplus/gop/cl"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/token"
-	"github.com/goplus/gop/x/gopmod"
-	"github.com/goplus/gop/x/mod/modfetch"
-	"github.com/goplus/gox"
-	"github.com/goplus/gox/packages"
-)
-
-const (
-	pkgFlagIll = 1 << iota
-	pkgFlagChanged
-)
-
-const (
-	stateNormal = iota
-	stateProcessing
-	stateDone
-	stateOccurErrors = -1
-)
-
-type pkgInfo struct {
-	path    string
-	runner  *Runner
-	imports []string
-	deps    []*pkgInfo
-	hash    string
-	nsource int
-	flags   int
-}
-
-type Runner struct {
-	runners map[string]*Runner
-	mod     *gopmod.Module
-	modRoot string
-	modPath string
-	modTime time.Time
-	pkgs    map[string]*pkgInfo
-	state   int
-}
-
-type Event interface {
-	OnStart(pkgPath string)
-	OnErr(stage string, err error)
-	OnInfo(format string, args ...interface{})
-	OnEnd()
-}
-
-type defaultEvent struct{}
-
-func (p defaultEvent) OnStart(pkgPath string) {
-	fmt.Fprintln(os.Stderr, pkgPath)
-}
-
-func (p defaultEvent) OnInfo(format string, args ...interface{}) {
-	fmt.Fprintf(os.Stderr, format, args...)
-}
-
-func (p defaultEvent) OnErr(stage string, err error) {
-	fmt.Fprintf(os.Stderr, "%s: %v\n", stage, err)
-}
-
-func (p defaultEvent) OnEnd() {
-}
-
-type Config struct {
-	Event
-	Fset   *token.FileSet
-	Loaded map[string]*types.Package
-}
-
-func (p *Runner) GenGoFlow() (pkgPaths []string, err error) {
-	pkgPaths = make([]string, 0, len(p.pkgs))
-	return p.genFlowPkgs(pkgPaths)
-}
-
-func (p *Runner) genFlowPkgs(pkgPaths []string) ([]string, error) {
-	if p.state != stateNormal {
-		if p.state != stateDone {
-			panic("TODO: cycle or error?")
-		}
-		return pkgPaths, nil
-	}
-	p.state = stateProcessing
-	for _, pkg := range p.pkgs {
-		if pkg.flags == pkgFlagChanged {
-			for _, dep := range pkg.deps {
-				if r := dep.runner; r != p {
-					pkgPaths, _ = dep.runner.genFlowPkgs(pkgPaths)
-				}
-			}
-		}
-	}
-	for _, pkg := range p.pkgs {
-		if pkg.flags == pkgFlagChanged {
-			pkgPaths = p.genFlowPkg(pkgPaths, pkg)
-		}
-	}
-	p.state = stateDone
-	return pkgPaths, nil
-}
-
-func (p *Runner) genFlowPkg(pkgPaths []string, pkg *pkgInfo) []string {
-	for _, dep := range pkg.deps {
-		if r := dep.runner; r == p && dep.flags == pkgFlagChanged {
-			pkgPaths = r.genFlowPkg(pkgPaths, dep)
-		}
-	}
-	pkgPaths = append(pkgPaths, pkg.path)
-	pkg.flags = 0
-	return pkgPaths
-}
-
-func (p *Runner) GenGo(conf Config) bool {
-	if conf.Loaded == nil {
-		conf.Loaded = make(map[string]*types.Package)
-	}
-	if conf.Fset == nil {
-		conf.Fset = token.NewFileSet()
-	}
-	if conf.Event == nil {
-		conf.Event = defaultEvent{}
-	}
-	p.genGoPkgs(&conf)
-	return p.state == stateDone
-}
-
-func (p *Runner) genGoPkgs(conf *Config) {
-	if p.state != stateNormal {
-		if p.state != stateDone {
-			panic("TODO: cycle or error?")
-		}
-		return
-	}
-	p.state = stateProcessing
-	imports, changed := make(map[string]none), false
-	for _, pkg := range p.pkgs {
-		if pkg.flags == pkgFlagChanged {
-			changed = true
-			for _, dep := range pkg.deps {
-				if r := dep.runner; r != p {
-					r.genGoPkgs(conf)
-				}
-			}
-			for _, imp := range pkg.imports {
-				imports[imp] = none{}
-			}
-		}
-	}
-	if !changed {
-		p.state = stateDone
-		return
-	}
-	modRoot := p.modRoot
-	inModCache := modfetch.InModCachePath(modRoot)
-	if inModCache {
-		os.Chmod(modRoot, 0755)
-		defer os.Chmod(modRoot, 0555)
-	}
-	imp := packages.NewImporter(conf.Fset)
-	for _, pkg := range p.pkgs {
-		if pkg.flags == pkgFlagChanged {
-			p.genGoPkg(pkg, imp, conf)
-		}
-	}
-	if p.state == stateProcessing {
-		p.state = stateDone
-	}
-}
-
-func getKeys(v map[string]none) []string {
-	keys := make([]string, 0, len(v))
-	for key := range v {
-		keys = append(keys, key)
-	}
-	return keys
-}
-
-const (
-	autoGenFile      = "gop_autogen.go"
-	autoGenTestFile  = "gop_autogen_test.go"
-	autoGen2TestFile = "gop_autogen2_test.go"
-)
-
-func (p *Runner) genGoPkg(pkg *pkgInfo, imp types.Importer, conf *Config) {
-	for _, dep := range pkg.deps {
-		if r := dep.runner; r == p && dep.flags == pkgFlagChanged {
-			r.genGoPkg(dep, imp, conf)
-		}
-	}
-	if err := p.doGenGoPkg(pkg, imp, conf); err != nil {
-		pkg.flags |= pkgFlagIll
-		p.state = stateOccurErrors
-		return
-	}
-	pkg.flags = 0
-}
-
-func (p *Runner) doGenGoPkg(pkgi *pkgInfo, imp types.Importer, conf *Config) (err error) {
-	pkgPath := pkgi.path
-	conf.OnStart(pkgPath)
-	pkgDir := p.modRoot + pkgPath[len(p.modPath):]
-
-	defer func() {
-		if e := recover(); e != nil {
-			switch v := e.(type) {
-			case error:
-				err = v
-			case string:
-				err = errors.New(v)
-			default:
-				panic(e)
-			}
-			conf.OnErr("genGoPkg", err)
-		}
-		conf.OnEnd()
-	}()
-
-	pkgs, err := parser.ParseDirEx(conf.Fset, pkgDir, parser.Config{
-		IsClass: p.mod.IsClass,
-		Mode:    parser.ParseComments,
-	})
-	if err != nil {
-		conf.OnErr("parse", err)
-		return
-	}
-	if len(pkgs) == 0 {
-		panic("TODO: no source in directory")
-	}
-
-	var pkgTest *ast.Package
-	clConf := &cl.Config{
-		WorkingDir:  pkgDir,
-		Fset:        conf.Fset,
-		LookupClass: p.mod.LookupClass,
-		Importer:    imp,
-	}
-	for name, pkg := range pkgs {
-		if strings.HasSuffix(name, "_test") {
-			if pkgTest != nil {
-				panic("TODO: has multi test package?")
-			}
-			pkgTest = pkg
-			continue
-		}
-		out, e := cl.NewPackage("", pkg, clConf)
-		if e != nil {
-			conf.OnErr("compile", e)
-			return e
-		}
-		err = saveGoFile(pkgDir, out, pkgi)
-		if err != nil {
-			conf.OnErr("compile", err)
-			return
-		}
-	}
-	if pkgTest != nil {
-		out, e := cl.NewPackage("", pkgTest, clConf)
-		if e != nil {
-			conf.OnErr("compile", e)
-			return e
-		}
-		err = gox.WriteFile(filepath.Join(pkgDir, autoGen2TestFile), out, true)
-		if err != nil {
-			conf.OnErr("compile", err)
-		}
-	}
-	return
-}
-
-func saveGoFile(dir string, pkg *gox.Package, pkgi *pkgInfo) error {
-	os.MkdirAll(dir, 0755)
-	err := func() (err error) {
-		file := filepath.Join(dir, autoGenFile)
-		f, err := os.Create(file)
-		if err != nil {
-			return
-		}
-		err = syscall.EFAULT
-		defer func() {
-			f.Close()
-			if err != nil {
-				os.Remove(file)
-			}
-		}()
-		err = pkgi.writeCinfo(f)
-		if err != nil {
-			return
-		}
-		return gox.WriteTo(f, pkg, false)
-	}()
-	if err != nil {
-		return err
-	}
-	if pkg.HasTestingFile() {
-		return gox.WriteFile(filepath.Join(dir, autoGenTestFile), pkg, true)
-	}
-	return nil
-}
-
-// -----------------------------------------------------------------------------
-
-type none = struct{}
-
-func modTime(file string, errs *ErrorList) (mt time.Time) {
-	fi, err := os.Stat(file)
-	if err != nil {
-		*errs = append(*errs, err)
-		return
-	}
-	return fi.ModTime()
-}
-
-func New(mod *gopmod.Module, pattern ...string) (p *Runner, err error) {
-	pkgPaths := make(map[string]struct{})
-	err = mod.List(pkgPaths, pattern...)
-	if err != nil {
-		return
-	}
-	modFile := mod.Modfile()
-	modRoot := filepath.Dir(modFile)
-
-	var errs ErrorList
-	p = &Runner{
-		mod:     mod,
-		modRoot: modRoot,
-		modPath: mod.Path(),
-		modTime: modTime(modFile, &errs),
-		pkgs:    make(map[string]*pkgInfo),
-	}
-	p.runners = map[string]*Runner{modRoot: p}
-	for pkgPath := range pkgPaths {
-		p.genDeps(pkgPath, pkgPath, &errs)
-	}
-	if len(errs) > 0 {
-		err = errs
-	}
-	return
-}
-
-func (p *Runner) genDeps(pkgPath, pkgPathBase string, errs *ErrorList) (pkg *pkgInfo) {
-	switch p.mod.PkgType(pkgPath) {
-	case gopmod.PkgtStandard:
-		return nil
-	case gopmod.PkgtLocal:
-		pkgPath = filepath.ToSlash(filepath.Join(pkgPathBase, pkgPath))
-	case gopmod.PkgtModule:
-	case gopmod.PkgtExtern:
-		return p.genExternDeps(pkgPath, errs)
-	default:
-		panic("TODO: invalid pkgPath")
-	}
-	if v, ok := p.pkgs[pkgPath]; ok {
-		return v
-	}
-	pkg = &pkgInfo{
-		path:   pkgPath,
-		runner: p,
-	}
-	p.pkgs[pkgPath] = pkg
-	dir := filepath.Join(p.modRoot, pkgPath[len(p.modPath):])
-
-	ci, err := p.mod.ChangeInfo(dir)
-	if err != nil {
-		*errs, pkg.flags = append(*errs, err), pkgFlagIll
-		return
-	}
-	if ci.GopFileNum == 0 {
-		return
-	}
-
-	var buf bytes.Buffer
-	imports, err := p.getImports(dir)
-	if err != nil {
-		errs.addError("import "+pkgPath, err)
-		pkg.flags = pkgFlagIll
-		return
-	}
-	imps := getSortedKeys(imports)
-	for _, imp := range imps {
-		if t := p.genDeps(imp, pkgPath, errs); t != nil {
-			buf.WriteString(t.runner.modRoot)
-			pkg.flags |= t.flags
-			if t.flags == pkgFlagChanged {
-				pkg.deps = append(pkg.deps, t)
-			}
-		} else {
-			buf.WriteString(imp)
-		}
-		buf.WriteByte('\n')
-	}
-	pkg.imports = imps
-	pkg.hash = hashOf(buf.Bytes())
-	pkg.nsource = ci.SourceNum
-	if p.sourceChanged(dir, ci.ModTime, pkg) {
-		pkg.flags |= pkgFlagChanged
-	}
-	return
-}
-
-func (p *Runner) getImports(dir string) (ret map[string]struct{}, err error) {
-	pkgs, err := p.mod.DirImports(dir)
-	if err != nil {
-		return
-	}
-	exists := false
-	for name, imps := range pkgs {
-		if !strings.HasSuffix(name, "_test") {
-			if exists {
-				return nil, errors.New("multiple packages in a directory")
-			}
-			ret, exists = imps, true
-		}
-	}
-	if exists {
-		return
-	}
-	return nil, syscall.ENOENT
-}
-
-func getSortedKeys(v map[string]none) []string {
-	keys := make([]string, 0, len(v))
-	for key := range v {
-		keys = append(keys, key)
-	}
-	sort.Strings(keys)
-	return keys
-}
-
-func hashOf(b []byte) string {
-	h := md5.Sum(b)
-	return hex.EncodeToString(h[:])
-}
-
-func (p *Runner) genExternDeps(pkgPath string, errs *ErrorList) *pkgInfo {
-	_, modVer, ok := p.mod.LookupExternPkg(pkgPath)
-	if !ok {
-		log.Panicln("externPkg not found:", pkgPath)
-	}
-	modRoot, err := modfetch.ModCachePath(modVer)
-	if err != nil {
-		*errs = append(*errs, err)
-		return &pkgInfo{path: pkgPath, flags: pkgFlagIll}
-	}
-	exr, ok := p.runners[modRoot]
-	if !ok {
-		mod, err := gopmod.LoadMod(modVer)
-		if err != nil {
-			*errs = append(*errs, err)
-			return &pkgInfo{path: pkgPath, flags: pkgFlagIll}
-		}
-		exr = &Runner{
-			mod:     mod,
-			modRoot: modRoot,
-			modPath: mod.Path(),
-			modTime: modTime(mod.Modfile(), errs),
-			pkgs:    make(map[string]*pkgInfo),
-			runners: p.runners,
-		}
-		p.runners[modRoot] = exr
-	}
-	return exr.genDeps(pkgPath, pkgPath, errs)
-}
-
-func (p *Runner) sourceChanged(dir string, modt time.Time, pkg *pkgInfo) bool {
-	f, err := os.Open(dir + "/gop_autogen.go")
-	if err != nil {
-		return true
-	}
-	fi, err := f.Stat()
-	if err != nil || modt.After(fi.ModTime()) {
-		return true
-	}
-	//cinfo $SourceNum $DepsHash
-	var sourceNum int
-	var hash string
-	if _, err = fmt.Fscanf(f, "//cinfo %d %s\n", &sourceNum, &hash); err != nil {
-		return true
-	}
-	return pkg.nsource != sourceNum || pkg.hash != hash
-}
-
-func (p pkgInfo) writeCinfo(w io.Writer) error {
-	_, err := fmt.Fprintf(w, "//cinfo %d %s\n", p.nsource, p.hash)
-	return err
-}
-
-// -----------------------------------------------------------------------------
-
-type ErrorList []error
-
-func (p *ErrorList) addError(stage string, err error) {
-	*p = append(*p, fmt.Errorf("%s: %v", stage, err))
-}
-
-func (e ErrorList) Error() string {
-	errStrs := make([]string, len(e))
-	for i, err := range e {
-		errStrs[i] = err.Error()
-	}
-	return strings.Join(errStrs, "\n")
-}
-
-// -----------------------------------------------------------------------------
-
-func GenGo(conf Config, dontRun bool, dir string, pattern ...string) bool {
-	if conf.Event == nil {
-		conf.Event = defaultEvent{}
-	}
-	mod, err := gopmod.Load(dir)
-	if err != nil {
-		conf.OnErr("loadMod", err)
-		return false
-	}
-	err = mod.RegisterClasses()
-	if err != nil {
-		conf.OnErr("registerClass", err)
-		return false
-	}
-	p, err := New(mod, pattern...)
-	if err != nil {
-		conf.OnErr("genDeps", err)
-		return false
-	}
-	if dontRun {
-		pkgPaths, err := p.GenGoFlow()
-		if err != nil {
-			conf.OnErr("genGo", err)
-			return false
-		}
-		for _, pkgPath := range pkgPaths {
-			fmt.Fprintln(os.Stderr, pkgPath)
-		}
-		return true
-	}
-	return p.GenGo(conf)
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gocmd/build_install.go b/x/gocmd/build_install.go
new file mode 100644
index 00000000..7faf157a
--- /dev/null
+++ b/x/gocmd/build_install.go
@@ -0,0 +1,55 @@
+/*
+ * 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 gocmd
+
+// -----------------------------------------------------------------------------
+
+type InstallConfig = Config
+
+func Install(arg string, conf *InstallConfig) (err error) {
+	return doWithArgs("install", conf, arg)
+}
+
+func InstallFiles(files []string, conf *InstallConfig) (err error) {
+	return doWithArgs("install", conf, files...)
+}
+
+// -----------------------------------------------------------------------------
+
+type BuildConfig = Config
+
+func Build(arg string, conf *BuildConfig) (err error) {
+	return doWithArgs("build", conf, arg)
+}
+
+func BuildFiles(files []string, conf *BuildConfig) (err error) {
+	return doWithArgs("build", conf, files...)
+}
+
+// -----------------------------------------------------------------------------
+
+type TestConfig = Config
+
+func Test(arg string, conf *TestConfig) (err error) {
+	return doWithArgs("test", conf, arg)
+}
+
+func TestFiles(files []string, conf *TestConfig) (err error) {
+	return doWithArgs("test", conf, files...)
+}
+
+// -----------------------------------------------------------------------------
diff --git a/x/gocmd/gocmd.go b/x/gocmd/gocmd.go
new file mode 100644
index 00000000..ffb89466
--- /dev/null
+++ b/x/gocmd/gocmd.go
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2021 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 gocmd
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/mod/env"
+)
+
+type GopEnv = env.Gop
+
+type Config struct {
+	Gop   *GopEnv
+	Flags []string
+	Run   func(cmd *exec.Cmd) error
+}
+
+// -----------------------------------------------------------------------------
+
+func doWithArgs(op string, conf *Config, args ...string) (err error) {
+	if conf == nil {
+		conf = new(Config)
+	}
+	exargs := make([]string, 1, 16)
+	exargs[0] = op
+	exargs = appendLdflags(exargs, conf.Gop)
+	exargs = append(exargs, conf.Flags...)
+	exargs = append(exargs, args...)
+	cmd := exec.Command("go", exargs...)
+	run := conf.Run
+	if run == nil {
+		run = runCmd
+	}
+	return run(cmd)
+}
+
+func runCmd(cmd *exec.Cmd) (err error) {
+	cmd.Stdin = os.Stdin
+	cmd.Stderr = os.Stderr
+	cmd.Stdout = os.Stdout
+	return cmd.Run()
+}
+
+// -----------------------------------------------------------------------------
+
+const (
+	ldFlagVersion   = "-X \"github.com/goplus/gop/env.buildVersion=%s\""
+	ldFlagBuildDate = "-X \"github.com/goplus/gop/env.buildDate=%s\""
+	ldFlagGopRoot   = "-X \"github.com/goplus/gop/env.defaultGopRoot=%s\""
+)
+
+const (
+	ldFlagAll = ldFlagVersion + " " + ldFlagBuildDate + " " + ldFlagGopRoot
+)
+
+func loadFlags(env *GopEnv) string {
+	return fmt.Sprintf(ldFlagAll, env.Version, env.BuildDate, env.Root)
+}
+
+func appendLdflags(exargs []string, env *GopEnv) []string {
+	if env == nil {
+		env = gopenv.Get()
+	}
+	return append(exargs, "-ldflags", loadFlags(env))
+}
+
+// -----------------------------------------------------------------------------
diff --git a/x/gopproj/gofile.go b/x/gocmd/run.go
similarity index 50%
rename from x/gopproj/gofile.go
rename to x/gocmd/run.go
index d65fcc58..89fc3908 100644
--- a/x/gopproj/gofile.go
+++ b/x/gocmd/run.go
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ * 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.
@@ -14,51 +14,44 @@
  * limitations under the License.
  */
 
-package gopproj
+package gocmd
 
 import (
-	"crypto/sha1"
 	"os"
 	"path/filepath"
+	"strings"
 )
 
 // -----------------------------------------------------------------------------
 
-type goFile struct {
-	file string
-}
-
-func openFromGoFile(file string) (proj *Project, err error) {
-	proj = &Project{
-		Source:        &goFile{file: file},
-		AutoGenFile:   file,
-		FriendlyFname: filepath.Base(file),
-	}
-	return
-}
+type RunConfig = Config
 
-func (p *goFile) Fingerp() (ret *Fingerp, err error) { // source code fingerprint
-	file, err := filepath.Abs(p.file)
+func RunDir(dir string, args []string, conf *RunConfig) (err error) {
+	fis, err := os.ReadDir(dir)
 	if err != nil {
 		return
 	}
-	fi, err := os.Stat(file)
-	if err != nil {
-		return
+	var files []string
+	for _, fi := range fis {
+		if !fi.IsDir() {
+			if fname := fi.Name(); filterRunFname(fname) {
+				files = append(files, filepath.Join(dir, fname))
+			}
+		}
 	}
-	hash := sha1.Sum([]byte(file))
-	return &Fingerp{Hash: hash, ModTime: fi.ModTime()}, nil
+	return RunFiles(files, args, conf)
 }
 
-func (p *goFile) GenGo(outFile, modFile string) error {
-	if p.file != outFile {
-		code, err := os.ReadFile(p.file)
-		if err != nil {
-			return err
-		}
-		return os.WriteFile(outFile, code, 0644)
-	}
-	return nil
+func filterRunFname(fname string) bool {
+	return strings.HasSuffix(fname, ".go") &&
+		!(strings.HasSuffix(fname, "_test.go") || strings.HasPrefix(fname, "_"))
+}
+
+// -----------------------------------------------------------------------------
+
+func RunFiles(files []string, args []string, conf *RunConfig) (err error) {
+	args = append(files, args...)
+	return doWithArgs("run", conf, args...)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/x/mod/modfile/print.go b/x/gopenv/env.go
similarity index 66%
rename from x/mod/modfile/print.go
rename to x/gopenv/env.go
index 4cd364d7..d4ffb348 100644
--- a/x/mod/modfile/print.go
+++ b/x/gopenv/env.go
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ * 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.
@@ -14,11 +14,17 @@
  * limitations under the License.
  */
 
-package modfile
+package gopenv
 
-import "golang.org/x/mod/modfile"
+import (
+	"github.com/goplus/gop/env"
+	modenv "github.com/goplus/mod/env"
+)
 
-// Format returns a gop.mod file as a byte slice, formatted in standard style.
-func Format(f *FileSyntax) []byte {
-	return modfile.Format(f)
+func Get() *modenv.Gop {
+	return &modenv.Gop{
+		Version:   env.Version(),
+		BuildDate: env.BuildDate(),
+		Root:      env.GOPROOT(),
+	}
 }
diff --git a/x/gopmod/classfile.go b/x/gopmod/classfile.go
deleted file mode 100644
index f3aecfb4..00000000
--- a/x/gopmod/classfile.go
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopmod
-
-import (
-	"errors"
-	"syscall"
-
-	"github.com/goplus/gop/x/mod/modfetch"
-	"github.com/goplus/gop/x/mod/modfile"
-	"github.com/goplus/gop/x/mod/modload"
-	"golang.org/x/mod/module"
-)
-
-type Class = modfile.Classfile
-
-var (
-	ClassSpx = &Class{
-		ProjExt:  ".gmx",
-		WorkExt:  ".spx",
-		PkgPaths: []string{"github.com/goplus/spx", "math"},
-	}
-)
-
-var (
-	ErrNotClassFileMod = errors.New("not a classfile module")
-)
-
-// -----------------------------------------------------------------------------
-
-func (p *Module) IsClass(ext string) (isProj bool, ok bool) {
-	c, ok := p.classes[ext]
-	if ok {
-		isProj = (ext == c.ProjExt)
-	}
-	return
-}
-
-func (p *Module) LookupClass(ext string) (c *Class, ok bool) {
-	c, ok = p.classes[ext]
-	return
-}
-
-func (p *Module) RegisterClasses(registerClass ...func(c *Class)) (err error) {
-	var regcls func(c *Class)
-	if registerClass != nil {
-		regcls = registerClass[0]
-	}
-	p.registerClass(ClassSpx, regcls)
-	if c := p.Classfile; c != nil {
-		p.registerClass(c, regcls)
-	}
-	for _, r := range p.Register {
-		if err = p.registerMod(r.ClassfileMod, regcls); err != nil {
-			return
-		}
-	}
-	return
-}
-
-func (p *Module) registerMod(modPath string, regcls func(c *Class)) (err error) {
-	mod, ok := p.LookupDepMod(modPath)
-	if !ok {
-		return syscall.ENOENT
-	}
-	err = p.registerClassFrom(mod, regcls)
-	if err != syscall.ENOENT {
-		return
-	}
-	mod, _, err = modfetch.Get(mod.String())
-	if err != nil {
-		return
-	}
-	return p.registerClassFrom(mod, regcls)
-}
-
-func (p *Module) registerClassFrom(modVer module.Version, regcls func(c *Class)) (err error) {
-	dir, err := modfetch.ModCachePath(modVer)
-	if err != nil {
-		return
-	}
-	mod, err := modload.Load(dir)
-	if err != nil {
-		return
-	}
-	c := mod.Classfile
-	if c == nil {
-		return ErrNotClassFileMod
-	}
-	p.registerClass(c, regcls)
-	return
-}
-
-func (p *Module) registerClass(c *Class, regcls func(c *Class)) {
-	p.classes[c.ProjExt] = c
-	if c.WorkExt != "" {
-		p.classes[c.WorkExt] = c
-	}
-	if regcls != nil {
-		regcls(c)
-	}
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopmod/godeps.go b/x/gopmod/godeps.go
deleted file mode 100644
index e6569aff..00000000
--- a/x/gopmod/godeps.go
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopmod
-
-import (
-	"path"
-	"strconv"
-	"strings"
-
-	"go/ast"
-	"go/parser"
-	"go/token"
-)
-
-// -----------------------------------------------------------------------------
-
-type none = struct{}
-
-func (p *Module) parseGoImport(errs *ErrorList, getImports getImportsFunc, gopfile string) {
-	f, err := parser.ParseFile(p.fset, gopfile, nil, parser.ImportsOnly)
-	if err != nil {
-		*errs = append(*errs, err)
-		return
-	}
-	imports := getImports(f.Name.Name)
-	for _, imp := range f.Imports {
-		p.importGo(imports, imp)
-	}
-}
-
-func (p *Module) importGo(imports map[string]none, spec *ast.ImportSpec) {
-	pkgPath := p.canonicalGo(goToString(spec.Path))
-	imports[pkgPath] = none{}
-}
-
-func (p *Module) canonicalGo(pkgPath string) string {
-	if strings.HasPrefix(pkgPath, ".") {
-		return path.Join(p.Path(), pkgPath)
-	}
-	return pkgPath
-}
-
-func goToString(l *ast.BasicLit) string {
-	if l.Kind == token.STRING {
-		s, err := strconv.Unquote(l.Value)
-		if err == nil {
-			return s
-		}
-	}
-	panic("TODO: toString - convert ast.BasicLit to string failed")
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopmod/gopdeps.go b/x/gopmod/gopdeps.go
deleted file mode 100644
index 8cc63f50..00000000
--- a/x/gopmod/gopdeps.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopmod
-
-import (
-	"path"
-	"strconv"
-	"strings"
-
-	"github.com/goplus/gop/ast"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/token"
-)
-
-// -----------------------------------------------------------------------------
-
-func (p *Module) parseGopImport(errs *ErrorList, getImports getImportsFunc, gopfile string) PkgImports {
-	f, err := parser.ParseFile(p.fset, gopfile, nil, parser.ImportsOnly)
-	if err != nil {
-		*errs = append(*errs, err)
-		return nil
-	}
-	imports := getImports(f.Name.Name)
-	for _, imp := range f.Imports {
-		p.importGop(imports, imp)
-	}
-	return imports
-}
-
-func (p *Module) importGop(imports map[string]none, spec *ast.ImportSpec) {
-	pkgPath := p.canonicalGop(gopToString(spec.Path))
-	imports[pkgPath] = none{}
-}
-
-func (p *Module) canonicalGop(pkgPath string) string {
-	if strings.HasPrefix(pkgPath, "gop/") {
-		return "github.com/goplus/" + pkgPath
-	} else if strings.HasPrefix(pkgPath, ".") {
-		return path.Join(p.Path(), pkgPath)
-	}
-	return pkgPath
-}
-
-func gopToString(l *ast.BasicLit) string {
-	if l.Kind == token.STRING {
-		s, err := strconv.Unquote(l.Value)
-		if err == nil {
-			return s
-		}
-	}
-	panic("TODO: toString - convert ast.BasicLit to string failed")
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopmod/module.go b/x/gopmod/module.go
deleted file mode 100644
index 49e068f9..00000000
--- a/x/gopmod/module.go
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopmod
-
-import (
-	"fmt"
-	"io/fs"
-	"os"
-	"path"
-	"path/filepath"
-	"sort"
-	"strings"
-	"syscall"
-	"time"
-
-	"github.com/goplus/gop/token"
-	"github.com/goplus/gop/x/mod/modfetch"
-	"github.com/goplus/gop/x/mod/modload"
-	"golang.org/x/mod/module"
-)
-
-func Imports(dir string) (ret []string, err error) {
-	dir, recursive := parsePattern(dir)
-	mod, err := Load(dir)
-	if err != nil {
-		return
-	}
-	err = mod.RegisterClasses()
-	if err != nil {
-		return
-	}
-	imports := make(map[string]none)
-	err = mod.Imports(imports, dir, recursive)
-	if err != nil {
-		return
-	}
-	return getSortedKeys(imports), nil
-}
-
-func List(pattern ...string) (pkgPaths []string, err error) {
-	mod, err := Load(".")
-	if err != nil {
-		return
-	}
-	err = mod.RegisterClasses()
-	if err != nil {
-		return
-	}
-	ret := make(map[string]none)
-	err = mod.List(ret, pattern...)
-	if err != nil {
-		return
-	}
-	return getSortedKeys(ret), nil
-}
-
-func getSortedKeys(v map[string]none) []string {
-	keys := make([]string, 0, len(v))
-	for key := range v {
-		keys = append(keys, key)
-	}
-	sort.Strings(keys)
-	return keys
-}
-
-// -----------------------------------------------------------------------------
-
-type depmodInfo struct {
-	path string
-	real module.Version
-}
-
-type Module struct {
-	modload.Module
-	classes map[string]*Class
-	depmods []depmodInfo
-	fset    *token.FileSet
-}
-
-// PkgType specifies a package type.
-type PkgType int
-
-const (
-	PkgtStandard PkgType = iota // a standard Go/Go+ package
-	PkgtModule                  // a package in this module (in standard form)
-	PkgtLocal                   // a package in this module (in relative path form)
-	PkgtExtern                  // an extarnal package
-	PkgtInvalid  = -1           // an invalid package
-)
-
-// PkgType returns the package type of specified package.
-func (p *Module) PkgType(pkgPath string) PkgType {
-	if pkgPath == "" {
-		return PkgtInvalid
-	}
-	if isPkgInMod(pkgPath, p.Path()) {
-		return PkgtModule
-	}
-	if pkgPath[0] == '.' {
-		return PkgtLocal
-	}
-	pos := strings.Index(pkgPath, "/")
-	if pos > 0 {
-		pkgPath = pkgPath[:pos]
-	}
-	if strings.Contains(pkgPath, ".") {
-		return PkgtExtern
-	}
-	return PkgtStandard
-}
-
-func isPkgInMod(pkgPath, modPath string) bool {
-	if strings.HasPrefix(pkgPath, modPath) {
-		suffix := pkgPath[len(modPath):]
-		return suffix == "" || suffix[0] == '/'
-	}
-	return false
-}
-
-// LookupExternPkg lookups a external package from depended modules.
-// If modVer.Path is replace to be a local path, it will be canonical to an absolute path.
-func (p *Module) LookupExternPkg(pkgPath string) (modPath string, modVer module.Version, ok bool) {
-	for _, m := range p.depmods {
-		if isPkgInMod(pkgPath, m.path) {
-			modPath, modVer, ok = m.path, m.real, true
-			break
-		}
-	}
-	return
-}
-
-// LookupDepMod lookups a depended module.
-// If modVer.Path is replace to be a local path, it will be canonical to an absolute path.
-func (p *Module) LookupDepMod(modPath string) (modVer module.Version, ok bool) {
-	for _, m := range p.depmods {
-		if m.path == modPath {
-			modVer, ok = m.real, true
-			break
-		}
-	}
-	return
-}
-
-func (p *Module) IsGopMod() bool {
-	const gopPkgPath = "github.com/goplus/gop"
-	_, file := filepath.Split(p.Modfile())
-	if file == "gop.mod" {
-		return true
-	}
-	if _, ok := p.LookupDepMod(gopPkgPath); ok {
-		return true
-	}
-	return p.Path() == gopPkgPath
-}
-
-func getDepMods(mod modload.Module) []depmodInfo {
-	depmods := mod.DepMods()
-	ret := make([]depmodInfo, 0, len(depmods))
-	for path, m := range depmods {
-		ret = append(ret, depmodInfo{path: path, real: m})
-	}
-	sort.Slice(ret, func(i, j int) bool {
-		return ret[i].path > ret[j].path
-	})
-	return ret
-}
-
-func New(mod modload.Module) *Module {
-	classes := make(map[string]*Class)
-	depmods := getDepMods(mod)
-	fset := token.NewFileSet()
-	return &Module{classes: classes, depmods: depmods, Module: mod, fset: fset}
-}
-
-func Load(dir string) (*Module, error) {
-	mod, err := modload.Load(dir)
-	if err != nil {
-		return nil, err
-	}
-	return New(mod), nil
-}
-
-func LoadMod(mod module.Version) (p *Module, err error) {
-	p, err = loadModFrom(mod)
-	if err != syscall.ENOENT {
-		return
-	}
-	mod, _, err = modfetch.Get(mod.String())
-	if err != nil {
-		return
-	}
-	return loadModFrom(mod)
-}
-
-func loadModFrom(mod module.Version) (p *Module, err error) {
-	dir, err := modfetch.ModCachePath(mod)
-	if err != nil {
-		return
-	}
-	return Load(dir)
-}
-
-func (p *Module) List(ret map[string]struct{}, pattern ...string) (err error) {
-	modRoot, modPath := p.Root(), p.Path()
-	for _, pat := range pattern {
-		if err = p.listPkgs(ret, pat, modRoot, modPath); err != nil {
-			return
-		}
-	}
-	return
-}
-
-func (p *Module) listPkgs(ret map[string]none, pat, modRoot, modPath string) error {
-	pat, recursive := parsePattern(pat)
-	if isLocal(pat) {
-		patAbs, err1 := filepath.Abs(pat)
-		patRel, err2 := filepath.Rel(modRoot, patAbs)
-		if err1 != nil || err2 != nil || strings.HasPrefix(patRel, "..") {
-			return fmt.Errorf("directory `%s` outside available modules", pat)
-		}
-		pkgPathBase := path.Join(modPath, filepath.ToSlash(patRel))
-		return p.doListLocalPkgs(ret, pkgPathBase, pat, recursive)
-	}
-	return p.doListPkgs(ret, pat, recursive)
-}
-
-func parsePattern(pat string) (string, bool) {
-	const multi = "/..."
-	recursive := strings.HasSuffix(pat, multi)
-	if recursive {
-		pat = pat[:len(pat)-len(multi)]
-		if pat == "" {
-			pat = "/"
-		}
-	}
-	return pat, recursive
-}
-
-func isLocal(ns string) bool {
-	if len(ns) > 0 {
-		switch c := ns[0]; c {
-		case '/', '\\', '.':
-			return true
-		default:
-			return len(ns) >= 2 && ns[1] == ':' && ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z')
-		}
-	}
-	return false
-}
-
-func (p *Module) doListLocalPkgs(ret map[string]none, pkgPathBase, pat string, recursive bool) (err error) {
-	fis, err := os.ReadDir(pat)
-	if err != nil {
-		return
-	}
-	noSouceFile := true
-	for _, fi := range fis {
-		name := fi.Name()
-		if strings.HasPrefix(name, "_") { // skip this file/directory
-			continue
-		}
-		if fi.IsDir() {
-			if recursive {
-				err = p.doListLocalPkgs(ret, pkgPathBase+"/"+name, pat+"/"+name, true)
-				if err != nil {
-					return
-				}
-			}
-		} else if noSouceFile {
-			ext := path.Ext(name)
-			switch ext {
-			case ".gop", ".go":
-				noSouceFile = false
-			default:
-				if _, ok := p.classes[ext]; ok {
-					noSouceFile = false
-				}
-			}
-		}
-	}
-	if !noSouceFile {
-		ret[pkgPathBase] = none{}
-	}
-	return
-}
-
-func (p *Module) doListPkgs(ret map[string]none, pkgPath string, recursive bool) error {
-	switch p.PkgType(pkgPath) {
-	case PkgtStandard:
-		if !recursive {
-			ret[pkgPath] = none{}
-			return nil
-		}
-	case PkgtModule:
-		relDir := p.Root() + pkgPath[len(p.Path()):]
-		return p.doListLocalPkgs(ret, pkgPath, relDir, recursive)
-	}
-	modPath, modVer, ok := p.LookupExternPkg(pkgPath)
-	if !ok {
-		panic("TODO: external package not found")
-	}
-	mod, err := LoadMod(modVer)
-	if err != nil {
-		return err
-	}
-	relDir := mod.Root() + pkgPath[len(modPath):]
-	return mod.doListLocalPkgs(ret, pkgPath, relDir, recursive)
-}
-
-func (p *Module) Imports(imports map[string]struct{}, dir string, recursive bool) (err error) {
-	getImports := func(name string) PkgImports {
-		return imports
-	}
-	err = p.doImports(getImports, dir, false)
-	return
-}
-
-type PkgImports map[string]struct{}
-
-func (p *Module) DirImports(dir string) (pkgs map[string]PkgImports, err error) {
-	pkgs = make(map[string]PkgImports)
-	getImports := func(name string) PkgImports {
-		imps, ok := pkgs[name]
-		if !ok {
-			imps = make(PkgImports)
-			if p.IsGopMod() {
-				importGopBuiltins(imps)
-			}
-			pkgs[name] = imps
-		}
-		return imps
-	}
-	err = p.doImports(getImports, dir, false)
-	return
-}
-
-func importGopBuiltins(imports PkgImports) {
-	imports["strconv"] = none{}
-	imports["strings"] = none{}
-	imports["github.com/goplus/gop/builtin"] = none{}
-}
-
-type getImportsFunc func(name string) PkgImports
-
-func (p *Module) doImports(getImports getImportsFunc, dir string, recursive bool) (err error) {
-	list, err := os.ReadDir(dir)
-	if err != nil {
-		return
-	}
-	var errs ErrorList
-	for _, d := range list {
-		fname := d.Name()
-		if strings.HasPrefix(fname, "_") { // skip this file/directory
-			continue
-		}
-		if d.IsDir() {
-			if recursive {
-				if err = p.doImports(getImports, filepath.Join(dir, fname), true); err != nil {
-					return
-				}
-			}
-			continue
-		}
-		ext := filepath.Ext(fname)
-		switch ext {
-		case ".gop":
-			p.parseGopImport(&errs, getImports, filepath.Join(dir, fname))
-		case ".go":
-			if !strings.HasPrefix(fname, "gop_autogen") {
-				p.parseGoImport(&errs, getImports, filepath.Join(dir, fname))
-			}
-		default:
-			if c, ok := p.classes[ext]; ok {
-				imports := p.parseGopImport(&errs, getImports, filepath.Join(dir, fname))
-				if imports != nil {
-					for _, pkgPath := range c.PkgPaths {
-						imports[pkgPath] = none{}
-					}
-				}
-			}
-		}
-	}
-	if len(errs) > 0 {
-		err = errs
-	}
-	return
-}
-
-type ChangeInfo struct {
-	ModTime    time.Time
-	GopFileNum int
-	SourceNum  int
-}
-
-func (p *Module) ChangeInfo(dir string) (ci ChangeInfo, err error) {
-	list, err := os.ReadDir(dir)
-	if err != nil {
-		return
-	}
-	dir += "/"
-	var fi fs.FileInfo
-	for _, d := range list {
-		fname := d.Name()
-		if strings.HasPrefix(fname, "_") || d.IsDir() {
-			continue
-		}
-		ext := filepath.Ext(fname)
-		switch ext {
-		case ".gop":
-			ci.GopFileNum++
-		case ".go":
-			if strings.HasPrefix(fname, "gop_autogen") {
-				continue
-			}
-		default:
-			if _, ok := p.classes[ext]; !ok {
-				continue
-			}
-			ci.GopFileNum++
-		}
-		ci.SourceNum++
-		fi, err = os.Stat(dir + fname)
-		if err != nil {
-			return
-		}
-		t := fi.ModTime()
-		if t.After(ci.ModTime) {
-			ci.ModTime = t
-		}
-	}
-	return
-}
-
-// -----------------------------------------------------------------------------
-
-type ErrorList []error
-
-func (e ErrorList) Error() string {
-	errStrs := make([]string, len(e))
-	for i, err := range e {
-		errStrs[i] = err.Error()
-	}
-	return strings.Join(errStrs, "\n")
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopproj/gocmd.go b/x/gopproj/gocmd.go
deleted file mode 100644
index b73c8c8a..00000000
--- a/x/gopproj/gocmd.go
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopproj
-
-import (
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-
-	"github.com/goplus/gop/env"
-)
-
-// -----------------------------------------------------------------------------
-
-const (
-	ldFlagVersion   = "-X \"github.com/goplus/gop/env.buildVersion=%s\""
-	ldFlagBuildDate = "-X \"github.com/goplus/gop/env.buildDate=%s\""
-	ldFlagGopRoot   = "-X \"github.com/goplus/gop/env.defaultGopRoot=%s\""
-)
-
-const (
-	ldFlagAll = ldFlagVersion + " " + ldFlagBuildDate + " " + ldFlagGopRoot
-)
-
-var (
-	ldFlags = fmt.Sprintf(ldFlagAll, env.Version(), env.BuildDate(), GOPROOT)
-)
-
-var (
-	GOPROOT = env.GOPROOT()
-)
-
-func LoadFlags() string {
-	return ldFlags
-}
-
-// -----------------------------------------------------------------------------
-
-type GoCmd struct {
-	*exec.Cmd
-	after func(error) error
-}
-
-func (p GoCmd) IsValid() bool {
-	return p.Cmd != nil
-}
-
-func (p GoCmd) Run() error {
-	err := p.Cmd.Run()
-	if p.after != nil {
-		return p.after(err)
-	}
-	return err
-}
-
-func goCommand(dir, op string, t *goTarget) (ret GoCmd) {
-	proj := t.proj
-	exargs := make([]string, 1, len(proj.BuildArgs)+len(proj.ExecArgs)+6)
-	exargs[0] = op                             // 1
-	exargs = append(exargs, proj.BuildArgs...) // len(proj.BuildArgs)
-	exargs = appendLdflags(exargs, op)         // 2
-	if op == "run" && t.defctx {               // 2
-		afterDir, goFile, outFile := dir, t.goFile, t.outFile
-		dir, _ = filepath.Split(goFile)
-		exargs[0] = "build"
-		exargs = append(exargs, "-o", outFile, goFile)
-		ret.after = func(e error) error {
-			if e == nil {
-				e = runCommand(afterDir, outFile, proj.ExecArgs...)
-				os.Remove(outFile)
-			}
-			if e != nil && t.proj.FlagRTOE { // remove tempfile on error
-				os.Remove(goFile)
-			}
-			return e
-		}
-	} else {
-		exargs = append(exargs, t.goFile)         // 1
-		exargs = append(exargs, proj.ExecArgs...) // len(proj.ExecArgs)
-	}
-	ret.Cmd = exec.Command("go", exargs...)
-	ret.Cmd.Dir = dir
-	return
-}
-
-func appendLdflags(exargs []string, op string) []string {
-	for _, v := range opsWithLdflags {
-		if op == v {
-			return append(exargs, "-ldflags", LoadFlags())
-		}
-	}
-	return exargs
-}
-
-var (
-	opsWithLdflags = []string{"run", "install", "build", "test"}
-)
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopproj/gopfiles.go b/x/gopproj/gopfiles.go
deleted file mode 100644
index dcbccde4..00000000
--- a/x/gopproj/gopfiles.go
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopproj
-
-import (
-	"bytes"
-	"crypto/sha1"
-	"log"
-	"os"
-	"path/filepath"
-	"time"
-
-	"github.com/goplus/gop/cl"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/token"
-	"github.com/goplus/gox"
-	"github.com/goplus/gox/packages"
-)
-
-// -----------------------------------------------------------------------------
-
-type gopFiles struct {
-	files  []string
-	defctx bool
-}
-
-func (p *Context) openFromGopFiles(files []string) (proj *Project, err error) {
-	proj = &Project{
-		Source: &gopFiles{files: files, defctx: p.defctx},
-	}
-	if len(files) == 1 {
-		file := files[0]
-		srcDir, fname := filepath.Split(file)
-		var autogen string
-		if hasMultiFiles(srcDir, ".gop") {
-			autogen = "gop_autogen_" + fname + ".go"
-		} else {
-			autogen = "gop_autogen.go"
-		}
-		proj.FriendlyFname = fname
-		proj.AutoGenFile = srcDir + autogen
-	}
-	return
-}
-
-func hasMultiFiles(srcDir string, ext string) bool {
-	var has bool
-	if f, err := os.Open(srcDir); err == nil {
-		defer f.Close()
-		fis, _ := f.ReadDir(-1)
-		for _, fi := range fis {
-			if !fi.IsDir() && filepath.Ext(fi.Name()) == ext {
-				if has {
-					return true
-				}
-				has = true
-			}
-		}
-	}
-	return false
-}
-
-func (p *gopFiles) Fingerp() (*Fingerp, error) { // source code fingerprint
-	var buf bytes.Buffer
-	var lastModTime time.Time
-	for _, file := range p.files {
-		absfile, err := filepath.Abs(file)
-		if err != nil {
-			return nil, err
-		}
-		if buf.Len() >= 0 {
-			buf.WriteByte('\n')
-		}
-		buf.WriteString(absfile)
-		fi, err := os.Stat(absfile)
-		if err != nil {
-			return nil, err
-		}
-		modTime := fi.ModTime()
-		if modTime.After(lastModTime) {
-			lastModTime = modTime
-		}
-	}
-	hash := sha1.Sum(buf.Bytes())
-	return &Fingerp{Hash: hash, ModTime: lastModTime}, nil
-}
-
-const (
-	parserMode = parser.ParseComments
-)
-
-func (p *gopFiles) GenGo(outFile, modFile string) error {
-	fset := token.NewFileSet()
-	pkgs, err := parser.ParseFiles(fset, p.files, parserMode)
-	if err != nil {
-		return err
-	}
-	if len(pkgs) != 1 {
-		log.Panicln("TODO: mutli packages -", len(pkgs))
-	}
-	mainPkg, ok := pkgs["main"]
-	if !ok {
-		panic("TODO: main package not found")
-	}
-
-	srcDir, _ := filepath.Split(outFile)
-	modDir, _ := filepath.Split(modFile)
-	conf := &cl.Config{WorkingDir: modDir, TargetDir: srcDir, Fset: fset}
-	if p.defctx {
-		wd, _ := os.Getwd()
-		os.Chdir(modDir)
-		defer func() {
-			os.Chdir(wd)
-		}()
-	}
-	conf.Importer = packages.NewImporter(fset)
-	out, err := cl.NewPackage("", mainPkg, conf)
-	if err != nil {
-		return err
-	}
-	return gox.WriteFile(outFile, out, false)
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopproj/gopproj.go b/x/gopproj/gopproj.go
deleted file mode 100644
index 23e10eb5..00000000
--- a/x/gopproj/gopproj.go
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopproj
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"github.com/goplus/gop/x/gengo"
-	"github.com/goplus/gop/x/gopprojs"
-	"github.com/goplus/gop/x/mod/modfetch"
-)
-
-// -----------------------------------------------------------------------------
-
-type handleEvent struct {
-	lastErr error
-}
-
-func (p *handleEvent) OnStart(pkgPath string) {
-	fmt.Fprintln(os.Stderr, pkgPath)
-}
-
-func (p *handleEvent) OnInfo(format string, args ...interface{}) {
-	fmt.Fprintf(os.Stderr, format, args...)
-}
-
-func (p *handleEvent) OnErr(stage string, err error) {
-	lastErr := fmt.Errorf("%s: %w", stage, err)
-	if p.lastErr != nil {
-		fmt.Fprintln(os.Stderr, p.lastErr)
-	}
-	p.lastErr = lastErr
-}
-
-func (p *handleEvent) OnEnd() {
-}
-
-// -----------------------------------------------------------------------------
-
-const (
-	FlagGoAsGoPlus = 1 << iota
-)
-
-func (p *Context) OpenFiles(flags int, args ...string) (proj *Project, err error) {
-	if len(args) != 1 {
-		return p.openFromGopFiles(args)
-	}
-	src := args[0]
-	if (flags&FlagGoAsGoPlus) == 0 && filepath.Ext(src) == ".go" {
-		return openFromGoFile(src)
-	}
-	return p.openFromGopFiles(args)
-}
-
-func OpenProject(flags int, src gopprojs.Proj) (ctx *Context, proj *Project, err error) {
-	switch v := src.(type) {
-	case *gopprojs.FilesProj:
-		ctx = New(".")
-		proj, err = ctx.OpenFiles(flags, v.Files...)
-		return
-	case *gopprojs.DirProj:
-		os.Chdir(v.Dir)
-		return OpenDir(flags, v.Dir)
-	case *gopprojs.PkgPathProj:
-		os.Chdir(modfetch.GOMODCACHE)
-		return OpenPkgPath(flags, v.Path)
-	}
-	panic("OpenProject: unexpected source")
-}
-
-func OpenDir(flags int, dir string) (ctx *Context, proj *Project, err error) {
-	ev := new(handleEvent)
-	if !gengo.GenGo(gengo.Config{Event: ev}, false, dir, dir) {
-		return nil, nil, ev.lastErr
-	}
-	ctx = New(dir)
-	proj, err = ctx.OpenFiles(0, filepath.Join(dir, "gop_autogen.go"))
-	return
-}
-
-func OpenPkgPath(flags int, pkgPath string) (ctx *Context, proj *Project, err error) {
-	modPath, leftPart := splitPkgPath(pkgPath)
-	modVer, _, err := modfetch.Get(modPath)
-	if err != nil {
-		return
-	}
-	dir, err := modfetch.ModCachePath(modVer)
-	if err != nil {
-		return
-	}
-	return OpenDir(flags, dir+leftPart)
-}
-
-func splitPkgPath(pkgPath string) (modPath, leftPart string) {
-	return pkgPath, ""
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/gopproj/projctx.go b/x/gopproj/projctx.go
deleted file mode 100644
index dc74da86..00000000
--- a/x/gopproj/projctx.go
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (c) 2021 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 gopproj
-
-import (
-	"bytes"
-	"encoding/base64"
-	"fmt"
-	"log"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"time"
-
-	"github.com/goplus/gop/env"
-)
-
-// -----------------------------------------------------------------------------
-
-type Fingerp struct {
-	Hash    [20]byte
-	ModTime time.Time
-}
-
-type Source interface {
-	Fingerp() (*Fingerp, error) // source code fingerprint
-	GenGo(outFile, modFile string) error
-}
-
-type Project struct {
-	Source
-	AutoGenFile   string // autogen file of output
-	FriendlyFname string // friendly fname of source
-	BuildArgs     []string
-	ExecArgs      []string
-	UseDefaultCtx bool
-	ForceToGen    bool
-	FlagNRINC     bool // do not run if not changed
-	FlagRTOE      bool // remove tempfile on error
-}
-
-type Context struct {
-	modfile string
-	dir     string
-	defctx  bool
-}
-
-func New(dir string) *Context {
-	modfile, err := env.GOPMOD(dir)
-	if err != nil {
-		return NewDefault(dir)
-	}
-	return &Context{modfile: modfile, dir: dir}
-}
-
-func NewDefault(dir string) *Context {
-	modfile := env.HOME() + "/.gop/run/go.mod"
-	if _, err := os.Stat(modfile); os.IsNotExist(err) {
-		genDefaultGopMod(modfile)
-	}
-	return &Context{modfile: modfile, dir: dir, defctx: true}
-}
-
-func (p *Context) GoCommand(op string, src *Project) GoCmd {
-	if src.UseDefaultCtx {
-		p = NewDefault(p.dir)
-	}
-	fp, err := src.Fingerp()
-	if err != nil {
-		log.Panicln(err)
-	}
-	out := p.out(src, fp.Hash[:])
-	if src.ForceToGen || fileIsDirty(fp.ModTime, out.goFile) {
-		if p.defctx {
-			dir, _ := filepath.Split(out.goFile)
-			os.Mkdir(dir, 0755)
-		}
-		if err := src.GenGo(out.goFile, p.modfile); err != nil {
-			log.Panicln(err)
-		}
-	} else if src.FlagNRINC { // do not run if not changed
-		return GoCmd{}
-	}
-	return goCommand(p.dir, op, &out)
-}
-
-func fileIsDirty(srcMod time.Time, destFile string) bool {
-	fiDest, err := os.Stat(destFile)
-	if err != nil {
-		return true
-	}
-	return srcMod.After(fiDest.ModTime())
-}
-
-type goTarget struct {
-	goFile  string
-	outFile string
-	proj    *Project
-	defctx  bool
-}
-
-func (p *Context) out(src *Project, hash []byte) (ret goTarget) {
-	fname := src.FriendlyFname
-	if !strings.HasSuffix(fname, ".go") {
-		fname += ".go"
-	}
-	dir, _ := filepath.Split(p.modfile)
-	ret.outFile = dir + "g" + base64.RawURLEncoding.EncodeToString(hash)
-	ret.proj = src
-	ret.defctx = p.defctx
-	if ret.defctx || src.AutoGenFile == "" {
-		ret.goFile = ret.outFile + fname
-	} else {
-		ret.goFile = src.AutoGenFile
-	}
-	if inWindows {
-		ret.outFile += ".exe"
-	}
-	return
-}
-
-const (
-	inWindows = (runtime.GOOS == "windows")
-)
-
-// -----------------------------------------------------------------------------
-
-const (
-	dummyGoFile = `package dummy
-
-import (
-	_ "github.com/goplus/gop"
-)
-`
-	gomodFormat = `module goplus.org/userapp
-
-go 1.16
-
-require (
-	github.com/goplus/gop %s
-)
-
-replace (
-	github.com/goplus/gop => %s
-)
-`
-)
-
-func genGomodFile(modfile string) {
-	var buf bytes.Buffer
-	var err error
-	var gopRoot = GOPROOT
-	if inWindows {
-		if gopRoot, err = filepath.Rel(filepath.Dir(modfile), gopRoot); err != nil {
-			log.Panicln(err)
-		}
-		gopRoot = filepath.ToSlash(gopRoot)
-	}
-	fmt.Fprintf(&buf, gomodFormat, env.Version(), gopRoot)
-	err = os.WriteFile(modfile, buf.Bytes(), 0644)
-	if err != nil {
-		log.Panicln(err)
-	}
-}
-
-func genDummyProject(dir string) {
-	err := os.WriteFile(dir+"/dummy.go", []byte(dummyGoFile), 0644)
-	if err != nil {
-		log.Panicln(err)
-	}
-}
-
-func execCommand(dir, command string, args ...string) {
-	err := runCommand(dir, command, args...)
-	if err != nil {
-		switch e := err.(type) {
-		case *exec.ExitError:
-			os.Exit(e.ExitCode())
-		default:
-			log.Fatalln(err)
-		}
-	}
-}
-
-func runCommand(dir, command string, args ...string) error {
-	cmd := exec.Command(command, args...)
-	cmd.Stdin = os.Stdin
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	cmd.Env = os.Environ()
-	cmd.Dir = dir
-	return cmd.Run()
-}
-
-func genDefaultGopMod(modfile string) {
-	dir, _ := filepath.Split(modfile)
-	dummy := dir + "dummy"
-	tmodfile := dummy + "/go.mod"
-	os.MkdirAll(dummy, 0755)
-	genGomodFile(tmodfile)
-	genDummyProject(dummy)
-	execCommand(dummy, "go", "mod", "tidy")
-	os.Rename(tmodfile, modfile)
-	os.Rename(toGoSum(tmodfile), toGoSum(modfile))
-}
-
-func toGoSum(modfile string) string {
-	return modfile[:len(modfile)-3] + "sum"
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/mod/modfetch/cache.go b/x/mod/modfetch/cache.go
deleted file mode 100644
index 4999c036..00000000
--- a/x/mod/modfetch/cache.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2021 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 modfetch
-
-import (
-	"errors"
-	"path/filepath"
-	"strings"
-
-	"github.com/goplus/gop/env"
-	"golang.org/x/mod/module"
-)
-
-// -----------------------------------------------------------------------------
-
-var (
-	GOMODCACHE = env.GOMODCACHE()
-)
-
-var (
-	ErrNoNeedToDownload = errors.New("no need to download")
-)
-
-func DownloadCachePath(mod module.Version) (string, error) {
-	if mod.Version == "" {
-		return mod.Path, ErrNoNeedToDownload
-	}
-	encPath, err := module.EscapePath(mod.Path)
-	if err != nil {
-		return "", err
-	}
-	return filepath.Join(GOMODCACHE, "cache/download", encPath, "@v", mod.Version+".zip"), nil
-}
-
-func ModCachePath(mod module.Version) (string, error) {
-	if mod.Version == "" {
-		return mod.Path, nil
-	}
-	encPath, err := module.EscapePath(mod.Path)
-	if err != nil {
-		return "", err
-	}
-	return filepath.Join(GOMODCACHE, encPath+"@"+mod.Version), nil
-}
-
-func InModCachePath(path string) bool {
-	if strings.HasPrefix(path, GOMODCACHE) {
-		name := path[len(GOMODCACHE):]
-		return name == "" || name[0] == '/' || name[0] == '\\'
-	}
-	return false
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/mod/modfetch/fetch.go b/x/mod/modfetch/fetch.go
deleted file mode 100644
index 964da12b..00000000
--- a/x/mod/modfetch/fetch.go
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (c) 2021 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 modfetch
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-	"syscall"
-
-	"github.com/goplus/gop/x/mod/modload"
-	"golang.org/x/mod/module"
-	"golang.org/x/mod/semver"
-)
-
-// -----------------------------------------------------------------------------
-
-type (
-	ExecCmdError = modload.ExecCmdError
-)
-
-func Get(modPath string, noCache ...bool) (mod module.Version, isClass bool, err error) {
-	if noCache == nil || !noCache[0] {
-		mod, isClass, err = getFromCache(modPath)
-		if err != syscall.ENOENT {
-			return
-		}
-	}
-	var stdout, stderr bytes.Buffer
-	cmd := exec.Command("go", "get", modPath)
-	cmd.Stdout = &stdout
-	cmd.Stderr = &stderr
-	err = cmd.Run()
-	if err != nil {
-		err = &ExecCmdError{Err: err, Stderr: stderr.Bytes()}
-		return
-	}
-	if stderr.Len() > 0 {
-		return getResult(stderr.String())
-	}
-	return getFromCache(modPath)
-}
-
-func getResult(data string) (mod module.Version, isClass bool, err error) {
-	// go: downloading github.com/xushiwei/foogop v0.1.0
-	const downloading = "go: downloading "
-	if strings.HasPrefix(data, downloading) {
-		if pos := strings.IndexByte(data, '\n'); pos > 0 {
-			fmt.Fprintln(os.Stderr, "gop:", data[4:pos])
-		}
-		return tryConvGoMod(data[len(downloading):], &data)
-	}
-	// go get: added github.com/xushiwei/foogop v0.1.0
-	const added = "go get: added "
-	if strings.HasPrefix(data, added) {
-		return tryConvGoMod(data[len(added):], &data)
-	}
-	return
-}
-
-func tryConvGoMod(data string, next *string) (mod module.Version, isClass bool, err error) {
-	err = syscall.ENOENT
-	if pos := strings.IndexByte(data, '\n'); pos > 0 {
-		line := data[:pos]
-		*next = data[pos+1:]
-		if pos = strings.IndexByte(line, ' '); pos > 0 {
-			mod.Path, mod.Version = line[:pos], line[pos+1:]
-			if dir, e := ModCachePath(mod); e == nil {
-				isClass, err = convGoMod(dir)
-			}
-		}
-	}
-	return
-}
-
-func convGoMod(dir string) (isClass bool, err error) {
-	mod, err := modload.Load(dir)
-	if err != nil {
-		return
-	}
-	os.Chmod(dir, 0755)
-	defer os.Chmod(dir, 0555)
-	return mod.Classfile != nil, mod.UpdateGoMod(true)
-}
-
-// -----------------------------------------------------------------------------
-
-func getFromCache(modPath string) (modVer module.Version, isClass bool, err error) {
-	modRoot, modVer, err := lookupFromCache(modPath)
-	if err != nil {
-		return
-	}
-	isClass, err = convGoMod(modRoot)
-	return
-}
-
-func lookupFromCache(modPath string) (modRoot string, mod module.Version, err error) {
-	mod.Path = modPath
-	pos := strings.IndexByte(modPath, '@')
-	if pos > 0 {
-		mod.Path, mod.Version = modPath[:pos], modPath[pos+1:]
-	}
-	encPath, err := module.EscapePath(mod.Path)
-	if err != nil {
-		return
-	}
-	modRoot = filepath.Join(GOMODCACHE, encPath+"@"+mod.Version)
-	if pos > 0 { // has version
-		fi, e := os.Stat(modRoot)
-		if e != nil || !fi.IsDir() {
-			err = syscall.ENOENT
-		}
-		return
-	}
-	dir, fname := filepath.Split(modRoot)
-	fis, err := os.ReadDir(dir)
-	if err != nil {
-		err = errors.Unwrap(err)
-		return
-	}
-	err = syscall.ENOENT
-	for _, fi := range fis {
-		if fi.IsDir() {
-			if name := fi.Name(); strings.HasPrefix(name, fname) {
-				ver := name[len(fname):]
-				if semver.Compare(mod.Version, ver) < 0 {
-					modRoot, mod.Version, err = dir+name, ver, nil
-				}
-			}
-		}
-	}
-	return
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/mod/modfile/read.go b/x/mod/modfile/read.go
deleted file mode 100644
index 78f4b7b4..00000000
--- a/x/mod/modfile/read.go
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2021 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 modfile
-
-import (
-	"golang.org/x/mod/modfile"
-)
-
-// A Position describes an arbitrary source position in a file, including the
-// file, line, column, and byte offset.
-type Position = modfile.Position
-
-// An Expr represents an input element.
-type Expr = modfile.Expr
-
-// A Comment represents a single // comment.
-type Comment = modfile.Comment
-
-// Comments collects the comments associated with an expression.
-type Comments = modfile.Comments
-
-// A FileSyntax represents an entire gop.mod file.
-type FileSyntax = modfile.FileSyntax
-
-// A CommentBlock represents a top-level block of comments separate
-// from any rule.
-type CommentBlock = modfile.CommentBlock
-
-// A Line is a single line of tokens.
-type Line = modfile.Line
-
-// A LineBlock is a factored block of lines, like
-//
-//	require (
-//		"x"
-//		"y"
-//	)
-//
-type LineBlock = modfile.LineBlock
-
-// An LParen represents the beginning of a parenthesized line block.
-// It is a place to store suffix comments.
-type LParen = modfile.LParen
-
-// An RParen represents the end of a parenthesized line block.
-// It is a place to store whole-line (before) comments.
-type RParen = modfile.RParen
-
-// ModulePath returns the module path from the gopmod file text.
-// If it cannot find a module path, it returns an empty string.
-// It is tolerant of unrelated problems in the gop.mod file.
-func ModulePath(mod []byte) string {
-	return modfile.ModulePath(mod)
-}
diff --git a/x/mod/modfile/rule.go b/x/mod/modfile/rule.go
deleted file mode 100644
index ff1c2def..00000000
--- a/x/mod/modfile/rule.go
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * Copyright (c) 2021 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 modfile
-
-import (
-	"errors"
-	"fmt"
-	"path/filepath"
-	"strconv"
-	"strings"
-
-	"golang.org/x/mod/modfile"
-	"golang.org/x/mod/module"
-)
-
-// A File is the parsed, interpreted form of a gop.mod file.
-type File struct {
-	modfile.File
-	Gop       *Gop
-	Classfile *Classfile
-	Register  []*Register
-}
-
-// A Module is the module statement.
-type Module = modfile.Module
-
-// A Go is the go statement.
-type Go = modfile.Go
-
-// A Require is a single require statement.
-type Require = modfile.Require
-
-// An Exclude is a single exclude statement.
-type Exclude = modfile.Exclude
-
-// A Replace is a single replace statement.
-type Replace = modfile.Replace
-
-// A Retract is a single retract statement.
-type Retract = modfile.Retract
-
-// A Gop is the gop statement.
-type Gop = modfile.Go
-
-// A Classfile is the classfile statement.
-type Classfile struct {
-	ProjExt  string   // ".gmx"
-	WorkExt  string   // ".spx"
-	PkgPaths []string // package paths of classfile
-	Syntax   *Line
-}
-
-// A Register is the register statement.
-type Register struct {
-	ClassfileMod string // module path of classfile
-	Syntax       *Line
-}
-
-// A VersionInterval represents a range of versions with upper and lower bounds.
-// Intervals are closed: both bounds are included. When Low is equal to High,
-// the interval may refer to a single version ('v1.2.3') or an interval
-// ('[v1.2.3, v1.2.3]'); both have the same representation.
-type VersionInterval = modfile.VersionInterval
-
-type VersionFixer = modfile.VersionFixer
-
-// Parse parses and returns a gop.mod file.
-//
-// file is the name of the file, used in positions and errors.
-//
-// data is the content of the file.
-//
-// fix is an optional function that canonicalizes module versions.
-// If fix is nil, all module versions must be canonical (module.CanonicalVersion
-// must return the same string).
-func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
-	return parseToFile(file, data, fix, true)
-}
-
-// ParseLax is like Parse but ignores unknown statements.
-// It is used when parsing gop.mod files other than the main module,
-// under the theory that most statement types we add in the future will
-// only apply in the main module, like exclude and replace,
-// and so we get better gradual deployments if old go commands
-// simply ignore those statements when found in gop.mod files
-// in dependencies.
-func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
-	return parseToFile(file, data, fix, false)
-}
-
-func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) {
-	f, err := modfile.ParseLax(file, data, fix)
-	if err != nil {
-		return
-	}
-	parsed = &File{File: *f}
-
-	var errs ErrorList
-	var fs = f.Syntax
-	for _, x := range fs.Stmt {
-		switch x := x.(type) {
-		case *Line:
-			parsed.parseVerb(&errs, x.Token[0], x, x.Token[1:], strict)
-		case *LineBlock:
-			verb := x.Token[0]
-			for _, line := range x.Line {
-				parsed.parseVerb(&errs, verb, line, line.Token, strict)
-			}
-		}
-	}
-	if len(errs) > 0 {
-		return nil, errs
-	}
-	return
-}
-
-func (f *File) parseVerb(errs *ErrorList, verb string, line *Line, args []string, strict bool) {
-	wrapModPathError := func(modPath string, err error) {
-		*errs = append(*errs, Error{
-			Filename: f.Syntax.Name,
-			Pos:      line.Start,
-			ModPath:  modPath,
-			Verb:     verb,
-			Err:      err,
-		})
-	}
-	wrapError := func(err error) {
-		*errs = append(*errs, Error{
-			Filename: f.Syntax.Name,
-			Pos:      line.Start,
-			Err:      err,
-		})
-	}
-	errorf := func(format string, args ...interface{}) {
-		wrapError(fmt.Errorf(format, args...))
-	}
-	switch verb {
-	case "require", "exclude", "module", "go", "retract":
-	case "replace":
-		arrow := 2
-		if len(args) >= 2 && args[1] == "=>" {
-			arrow = 1
-		}
-		if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
-			errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
-			return
-		}
-		s, err := parseString(&args[0])
-		if err != nil {
-			errorf("invalid quoted string: %v", err)
-			return
-		}
-		pathMajor, err := modulePathMajor(s)
-		if err != nil {
-			wrapModPathError(s, err)
-			return
-		}
-		var v string
-		if arrow == 2 {
-			v, err = parseVersion(verb, s, &args[1])
-			if err != nil {
-				wrapError(err)
-				return
-			}
-			if err := module.CheckPathMajor(v, pathMajor); err != nil {
-				wrapModPathError(s, err)
-				return
-			}
-		}
-		ns, err := parseString(&args[arrow+1])
-		if err != nil {
-			errorf("invalid quoted string: %v", err)
-			return
-		}
-		nv := ""
-		if len(args) == arrow+2 {
-			if !IsDirectoryPath(ns) {
-				errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
-				return
-			}
-			if filepath.Separator == '/' && strings.Contains(ns, `\`) {
-				errorf("replacement directory appears to be Windows path (on a non-windows system)")
-				return
-			}
-		}
-		if len(args) == arrow+3 {
-			nv, err = parseVersion(verb, ns, &args[arrow+2])
-			if err != nil {
-				wrapError(err)
-				return
-			}
-			if IsDirectoryPath(ns) {
-				errorf("replacement module directory path %q cannot have version", ns)
-				return
-			}
-		}
-		f.Replace = append(f.Replace, &Replace{
-			Old:    module.Version{Path: s, Version: v},
-			New:    module.Version{Path: ns, Version: nv},
-			Syntax: line,
-		})
-	case "gop":
-		if f.Gop != nil {
-			errorf("repeated go statement")
-			return
-		}
-		if len(args) != 1 {
-			errorf("go directive expects exactly one argument")
-			return
-		} else if !modfile.GoVersionRE.MatchString(args[0]) {
-			errorf("invalid gop version '%s': must match format 1.23", args[0])
-			return
-		}
-		f.Gop = &Gop{Syntax: line}
-		f.Gop.Version = args[0]
-	case "register":
-		if len(args) != 1 {
-			errorf("register directive expects exactly one argument")
-			return
-		}
-		s, err := parseString(&args[0])
-		if err != nil {
-			errorf("invalid quoted string: %v", err)
-			return
-		}
-		err = module.CheckPath(s)
-		if err != nil {
-			wrapError(err)
-			return
-		}
-		f.Register = append(f.Register, &Register{
-			ClassfileMod: s,
-			Syntax:       line,
-		})
-	case "classfile":
-		if f.Classfile != nil {
-			errorf("repeated classfile statement")
-			return
-		}
-		if len(args) < 3 {
-			errorf("usage: classfile projExt workExt [classFilePkgPath ...]")
-			return
-		}
-		projExt, err := parseExt(&args[0])
-		if err != nil {
-			wrapError(err)
-			return
-		}
-		workExt, err := parseExt(&args[1])
-		if err != nil {
-			wrapError(err)
-			return
-		}
-		pkgPaths, err := parseStrings(args[2:])
-		if err != nil {
-			errorf("invalid quoted string: %v", err)
-			return
-		}
-		f.Classfile = &Classfile{
-			ProjExt: projExt, WorkExt: workExt, PkgPaths: pkgPaths, Syntax: line,
-		}
-	default:
-		if strict {
-			errorf("unknown directive: %s", verb)
-		}
-	}
-}
-
-func parseVersion(verb string, path string, s *string) (string, error) {
-	t, err := parseString(s)
-	if err != nil {
-		return "", &Error{
-			Verb:    verb,
-			ModPath: path,
-			Err: &module.InvalidVersionError{
-				Version: *s,
-				Err:     err,
-			},
-		}
-	}
-	cv := module.CanonicalVersion(t)
-	if cv == "" {
-		return "", &Error{
-			Verb:    verb,
-			ModPath: path,
-			Err: &module.InvalidVersionError{
-				Version: t,
-				Err:     errors.New("must be of the form v1.2.3"),
-			},
-		}
-	}
-	t = cv
-	*s = t
-	return *s, nil
-}
-
-func modulePathMajor(path string) (string, error) {
-	_, major, ok := module.SplitPathVersion(path)
-	if !ok {
-		return "", fmt.Errorf("invalid module path")
-	}
-	return major, nil
-}
-
-// IsDirectoryPath reports whether the given path should be interpreted
-// as a directory path. Just like on the go command line, relative paths
-// and rooted paths are directory paths; the rest are module paths.
-func IsDirectoryPath(ns string) bool {
-	return modfile.IsDirectoryPath(ns)
-}
-
-// MustQuote reports whether s must be quoted in order to appear as
-// a single token in a gop.mod line.
-func MustQuote(s string) bool {
-	return modfile.MustQuote(s)
-}
-
-// AutoQuote returns s or, if quoting is required for s to appear in a gop.mod,
-// the quotation of s.
-func AutoQuote(s string) string {
-	return modfile.AutoQuote(s)
-}
-
-func parseString(s *string) (string, error) {
-	t := *s
-	if strings.HasPrefix(t, `"`) {
-		var err error
-		if t, err = strconv.Unquote(t); err != nil {
-			return "", err
-		}
-	} else if strings.ContainsAny(t, "\"'`") {
-		// Other quotes are reserved both for possible future expansion
-		// and to avoid confusion. For example if someone types 'x'
-		// we want that to be a syntax error and not a literal x in literal quotation marks.
-		return "", fmt.Errorf("unquoted string cannot contain quote")
-	}
-	*s = AutoQuote(t)
-	return t, nil
-}
-
-func parseStrings(args []string) (arr []string, err error) {
-	arr = make([]string, len(args))
-	for i := range args {
-		if arr[i], err = parseString(&args[i]); err != nil {
-			return
-		}
-	}
-	return
-}
-
-func parseExt(s *string) (t string, err error) {
-	t, err = parseString(s)
-	if err != nil {
-		goto failed
-	}
-	if len(t) > 1 && t[0] == '.' || t == "" {
-		return
-	}
-	err = errors.New("invalid ext format")
-failed:
-	return "", &InvalidExtError{
-		Ext: *s,
-		Err: err,
-	}
-}
-
-type InvalidExtError struct {
-	Ext string
-	Err error
-}
-
-func (e *InvalidExtError) Error() string {
-	return fmt.Sprintf("ext %s invalid: %s", e.Ext, e.Err)
-}
-
-func (e *InvalidExtError) Unwrap() error { return e.Err }
-
-type ErrorList = modfile.ErrorList
-type Error = modfile.Error
-
-// -----------------------------------------------------------------------------
-
-const (
-	directiveInvalid = iota
-	directiveModule
-	directiveGo
-	directiveGop
-	directiveClassfile
-)
-
-const (
-	directiveLineBlock = 0x80 + iota
-	directiveRegister
-	directiveRequire
-	directiveExclude
-	directiveReplace
-	directiveRetract
-)
-
-var directiveWeights = map[string]int{
-	"module":    directiveModule,
-	"go":        directiveGo,
-	"gop":       directiveGop,
-	"classfile": directiveClassfile,
-	"register":  directiveRegister,
-	"require":   directiveRequire,
-	"exclude":   directiveExclude,
-	"replace":   directiveReplace,
-	"retract":   directiveRetract,
-}
-
-func getWeight(e Expr) int {
-	if line, ok := e.(*Line); ok {
-		return directiveWeights[line.Token[0]]
-	}
-	if w, ok := directiveWeights[e.(*LineBlock).Token[0]]; ok {
-		return w
-	}
-	return directiveLineBlock
-}
-
-func updateLine(line *Line, tokens ...string) {
-	if line.InBlock {
-		tokens = tokens[1:]
-	}
-	line.Token = tokens
-}
-
-func addLine(x *FileSyntax, tokens ...string) *Line {
-	new := &Line{Token: tokens}
-	w := directiveWeights[tokens[0]]
-	for i, e := range x.Stmt {
-		w2 := getWeight(e)
-		if w <= w2 {
-			x.Stmt = append(x.Stmt, nil)
-			copy(x.Stmt[i+1:], x.Stmt[i:])
-			x.Stmt[i] = new
-			return new
-		}
-	}
-	x.Stmt = append(x.Stmt, new)
-	return new
-}
-
-func (f *File) AddGopStmt(version string) error {
-	if !modfile.GoVersionRE.MatchString(version) {
-		return fmt.Errorf("invalid language version string %q", version)
-	}
-	if f.Gop == nil {
-		f.Gop = &Gop{
-			Version: version,
-			Syntax:  addLine(f.Syntax, "gop", version),
-		}
-	} else {
-		f.Gop.Version = version
-		updateLine(f.Gop.Syntax, "gop", version)
-	}
-	return nil
-}
-
-func (f *File) AddRegister(modPath string) {
-	for _, r := range f.Register {
-		if r.ClassfileMod == modPath {
-			return
-		}
-	}
-	f.AddNewRegister(modPath)
-}
-
-func (f *File) AddNewRegister(modPath string) {
-	line := addLine(f.Syntax, "register", AutoQuote(modPath))
-	r := &Register{
-		ClassfileMod: modPath,
-		Syntax:       line,
-	}
-	f.Register = append(f.Register, r)
-}
-
-// -----------------------------------------------------------------------------
-
-// markRemoved modifies line so that it (and its end-of-line comment, if any)
-// will be dropped by (*FileSyntax).Cleanup.
-func markRemoved(line *Line) {
-	line.Token = nil
-	line.Comments.Suffix = nil
-}
-
-func (f *File) DropAllRequire() {
-	for _, r := range f.Require {
-		markRemoved(r.Syntax)
-	}
-	f.Require = nil
-}
-
-func (f *File) DropAllReplace() {
-	for _, r := range f.Replace {
-		markRemoved(r.Syntax)
-	}
-	f.Replace = nil
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/mod/modload/module.go b/x/mod/modload/module.go
deleted file mode 100644
index a207497b..00000000
--- a/x/mod/modload/module.go
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright (c) 2021 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 modload
-
-import (
-	"bytes"
-	"errors"
-	"fmt"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-
-	gomodfile "golang.org/x/mod/modfile"
-	"golang.org/x/mod/module"
-
-	"github.com/goplus/gop/env"
-	"github.com/goplus/gop/x/mod/modfile"
-)
-
-var (
-	ErrNoModDecl = errors.New("no module declaration in gop.mod (or go.mod)")
-	ErrNoModRoot = errors.New("gop.mod or go.mod file not found in current directory or any parent directory")
-)
-
-type Module struct {
-	*modfile.File
-}
-
-// Modfile returns absolute path of the module file (gop.mod or go.mod).
-func (p Module) Modfile() string {
-	return p.Syntax.Name
-}
-
-// Root returns absolute root path of this module.
-func (p Module) Root() string {
-	return filepath.Dir(p.Syntax.Name)
-}
-
-// Path returns the module path.
-func (p Module) Path() string {
-	if mod := p.Module; mod != nil {
-		return mod.Mod.Path
-	}
-	return ""
-}
-
-// DepMods returns all depended modules.
-// If a depended module path is replace to be a local path, it will be canonical to an absolute path.
-func (p Module) DepMods() map[string]module.Version {
-	vers := make(map[string]module.Version)
-	for _, r := range p.Require {
-		if r.Mod.Path != "" {
-			vers[r.Mod.Path] = r.Mod
-		}
-	}
-	for _, r := range p.Replace {
-		if r.Old.Path != "" {
-			real := r.New
-			if real.Version == "" {
-				if strings.HasPrefix(real.Path, ".") {
-					dir, _ := filepath.Split(p.Modfile())
-					real.Path = dir + real.Path
-				}
-				if a, err := filepath.Abs(real.Path); err == nil {
-					real.Path = a
-				}
-			}
-			vers[r.Old.Path] = real
-		}
-	}
-	return vers
-}
-
-// Create creates a new module in `dir`.
-// You should call `Save` manually to save this module.
-func Create(dir string, modPath, goVer, gopVer string) (p Module, err error) {
-	gopmod, err := filepath.Abs(filepath.Join(dir, "gop.mod"))
-	if err != nil {
-		return
-	}
-	if _, err := os.Stat(gopmod); err == nil {
-		return Module{}, fmt.Errorf("gop: %s already exists", gopmod)
-	}
-	mod := new(modfile.File)
-	mod.AddModuleStmt(modPath)
-	mod.AddGoStmt(goVer)
-	mod.AddGopStmt(gopVer)
-	mod.Syntax.Name = gopmod
-	return Module{File: mod}, nil
-}
-
-// fixVersion returns a modfile.VersionFixer implemented using the Query function.
-//
-// It resolves commit hashes and branch names to versions,
-// canonicalizes versions that appeared in early vgo drafts,
-// and does nothing for versions that already appear to be canonical.
-//
-// The VersionFixer sets 'fixed' if it ever returns a non-canonical version.
-func fixVersion(fixed *bool) modfile.VersionFixer {
-	return func(path, vers string) (resolved string, err error) {
-		// do nothing
-		return vers, nil
-	}
-}
-
-// Load loads a module from `dir`.
-func Load(dir string) (p Module, err error) {
-	gopmod, err := env.GOPMOD(dir)
-	if err != nil {
-		return
-	}
-
-	data, err := os.ReadFile(gopmod)
-	if err != nil {
-		return
-	}
-
-	var fixed bool
-	f, err := modfile.Parse(gopmod, data, fixVersion(&fixed))
-	if err != nil {
-		// Errors returned by modfile.Parse begin with file:line.
-		return
-	}
-	if f.Module == nil {
-		// No module declaration. Must add module path.
-		return Module{}, ErrNoModDecl
-	}
-	return Module{File: f}, nil
-}
-
-func loadGoMod(gomod string) (f *gomodfile.File, err error) {
-	data, err := os.ReadFile(gomod)
-	if err != nil {
-		return
-	}
-	var fixed bool
-	return gomodfile.Parse(gomod, data, fixVersion(&fixed))
-}
-
-// -----------------------------------------------------------------------------
-
-// Save saves all changes of this module.
-func (p Module) Save() error {
-	modfile := p.Modfile()
-	data, err := p.Format()
-	if err == nil {
-		err = os.WriteFile(modfile, data, 0644)
-	}
-	return err
-}
-
-const (
-	gopMod = "github.com/goplus/gop"
-)
-
-// Tidy adds missing and removes unused modules.
-// Note: please call GenGo before Tidy.
-func (p Module) Tidy() (err error) {
-	gopmod := p.Modfile()
-	dir, file := filepath.Split(gopmod)
-	gomod := dir + "go.mod"
-	isGopMod := file != "go.mod"
-	if isGopMod {
-		if err = p.saveGoMod(gomod); err != nil {
-			return
-		}
-	}
-	if err = tidy(dir); err != nil {
-		return
-	}
-	if isGopMod {
-		gof, loadErr := loadGoMod(gomod)
-		if loadErr != nil {
-			return loadErr
-		}
-		p.DropAllRequire()
-		p.DropAllReplace()
-		for _, r := range gof.Require {
-			switch r.Mod.Path {
-			case gopMod, "":
-				continue
-			}
-			p.AddRequire(r.Mod.Path, r.Mod.Version)
-		}
-		for _, r := range gof.Replace {
-			switch r.Old.Path {
-			case gopMod, "":
-				continue
-			}
-			p.AddReplace(r.Old.Path, r.Old.Version, r.New.Path, r.New.Version)
-		}
-		err = p.Save()
-		if err == nil {
-			err = p.UpdateGoMod(false)
-		}
-	}
-	return
-}
-
-func tidy(modRoot string) (err error) {
-	var stdout, stderr bytes.Buffer
-	cmd := exec.Command("go", "mod", "tidy")
-	cmd.Stdout = &stdout
-	cmd.Stderr = &stderr
-	cmd.Dir = modRoot
-	err = cmd.Run()
-	if err != nil {
-		err = &ExecCmdError{Err: err, Stderr: stderr.Bytes()}
-	}
-	msg := strings.ReplaceAll(stderr.String(), "go: ", "gop: ")
-	os.Stdout.WriteString(msg)
-	return
-}
-
-// UpdateGoMod updates the go.mod file.
-func (p Module) UpdateGoMod(checkChanged bool) error {
-	gopmod := p.Modfile()
-	dir, file := filepath.Split(gopmod)
-	if file == "go.mod" {
-		return nil
-	}
-	gomod := dir + "go.mod"
-	if checkChanged && notChanged(gomod, gopmod) {
-		return nil
-	}
-	return p.saveGoMod(gomod)
-}
-
-func (p Module) saveGoMod(gomod string) error {
-	gof := p.convToGoMod()
-	data, err := gof.Format()
-	if err == nil {
-		err = os.WriteFile(gomod, data, 0644)
-	}
-	return err
-}
-
-func (p Module) convToGoMod() *gomodfile.File {
-	copy := p.File.File
-	copy.Syntax = cloneGoFileSyntax(copy.Syntax)
-	addRequireIfNotExist(&copy, gopMod, env.Version())
-	addReplaceIfNotExist(&copy, gopMod, "", env.GOPROOT(), "")
-	return &copy
-}
-
-func addRequireIfNotExist(f *gomodfile.File, path, vers string) {
-	for _, r := range f.Require {
-		if r.Mod.Path == path {
-			return
-		}
-	}
-	f.AddNewRequire(path, vers, false)
-}
-
-func addReplaceIfNotExist(f *gomodfile.File, oldPath, oldVers, newPath, newVers string) {
-	for _, r := range f.Replace {
-		if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
-			return
-		}
-	}
-	f.AddReplace(oldPath, oldVers, newPath, newVers)
-}
-
-func notChanged(target, src string) bool {
-	fiTarget, err := os.Stat(target)
-	if err != nil {
-		return false
-	}
-	fiSrc, err := os.Stat(src)
-	if err != nil {
-		return false
-	}
-	return fiTarget.ModTime().After(fiSrc.ModTime())
-}
-
-// -----------------------------------------------------------------------------
-
-func cloneGoFileSyntax(syn *modfile.FileSyntax) *modfile.FileSyntax {
-	stmt := make([]modfile.Expr, 0, len(syn.Stmt))
-	for _, e := range syn.Stmt {
-		if isGopExpr(e) {
-			continue
-		}
-		stmt = append(stmt, cloneExpr(e))
-	}
-	return &modfile.FileSyntax{
-		Name:     syn.Name,
-		Comments: syn.Comments,
-		Stmt:     stmt,
-	}
-}
-
-func cloneExpr(e modfile.Expr) modfile.Expr {
-	if v, ok := e.(*modfile.LineBlock); ok {
-		copy := *v
-		return &copy
-	}
-	return e
-}
-
-func isGopExpr(e modfile.Expr) bool {
-	switch verb := getVerb(e); verb {
-	case "gop", "register", "classfile":
-		return true
-	}
-	return false
-}
-
-func getVerb(e modfile.Expr) string {
-	if line, ok := e.(*modfile.Line); ok {
-		return line.Token[0]
-	}
-	return e.(*modfile.LineBlock).Token[0]
-}
-
-// -----------------------------------------------------------------------------
-
-type ExecCmdError struct {
-	Err    error
-	Stderr []byte
-}
-
-func (p *ExecCmdError) Error() string {
-	if e := p.Stderr; e != nil {
-		return string(e)
-	}
-	return p.Err.Error()
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/types/load.go b/x/types/load.go
new file mode 100644
index 00000000..b62c9540
--- /dev/null
+++ b/x/types/load.go
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+// This package is experimental. Don't use it in production.
+package types
+
+import (
+	"go/token"
+	"go/types"
+	"strings"
+
+	goast "go/ast"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/ast/togo"
+)
+
+// A Config specifies the configuration for type checking.
+// The zero value for Config is a ready-to-use default configuration.
+type Config = types.Config
+
+// A Package describes a Go/Go+ package.
+type Package = types.Package
+
+// Load loads a package and returns the resulting package object and
+// the first error if any.
+//
+// The package is marked as complete if no errors occurred, otherwise it is
+// incomplete. See Config.Error for controlling behavior in the presence of
+// errors.
+func Load(fset *token.FileSet, in *ast.Package, conf *Config) (pkg *Package, err error) {
+	n := len(in.Files) + len(in.GoFiles)
+	files := make([]*goast.File, 0, n)
+	for filename, f := range in.Files {
+		if !strings.HasSuffix(filename, "_test.gop") {
+			files = append(files, togo.ASTFile(f, 0))
+		}
+	}
+	for filename, f := range in.GoFiles {
+		if !strings.HasSuffix(filename, "_test.go") {
+			files = append(files, f)
+		}
+	}
+	return conf.Check(in.Name, fset, files, nil)
+}


Test patch:
diff --git a/ast/fromgo/gopast_test.go b/ast/fromgo/gopast_test.go
new file mode 100644
index 00000000..5f748acf
--- /dev/null
+++ b/ast/fromgo/gopast_test.go
@@ -0,0 +1,127 @@
+/*
+ * 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 fromgo
+
+import (
+	"bytes"
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"testing"
+
+	"github.com/goplus/gop/format"
+)
+
+func testAST(t *testing.T, from, to string) {
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "foo.go", from, 0)
+	if err != nil {
+		t.Fatal("parser.ParseFile:", err)
+	}
+	gopf := ASTFile(f, 0)
+	var b bytes.Buffer
+	err = format.Node(&b, fset, gopf)
+	if err != nil {
+		t.Fatal("format.Node:", err)
+	}
+	result := b.String()
+	if to != result {
+		t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, to)
+	}
+}
+
+func test(t *testing.T, src string) {
+	testAST(t, src, src)
+}
+
+func testPanic(t *testing.T, panicMsg string, doPanic func()) {
+	t.Run(panicMsg, func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil {
+				t.Fatal("testPanic: no error?")
+			} else if msg := e.(string); msg != panicMsg {
+				t.Fatalf("\nResult:\n%s\nExpected Panic:\n%s\n", msg, panicMsg)
+			}
+		}()
+		doPanic()
+	})
+}
+
+func TestErrASTFile(t *testing.T) {
+	testPanic(t, "ASTFile: doesn't support keeping cgo now\n", func() {
+		ASTFile(nil, KeepCgo)
+	})
+	testPanic(t, "ASTFile: doesn't support keeping func body now\n", func() {
+		ASTFile(nil, KeepFuncBody)
+	})
+}
+
+func TestErrDecl(t *testing.T) {
+	testPanic(t, "gopDecl: unkown decl - <nil>\n", func() {
+		gopDecl(nil)
+	})
+	testPanic(t, "gopGenDecl: unknown spec - ILLEGAL\n", func() {
+		gopGenDecl(&ast.GenDecl{
+			Specs: []ast.Spec{nil},
+		})
+	})
+}
+
+func TestErrExpr(t *testing.T) {
+	testPanic(t, "gopExpr: unknown expr - *ast.BadExpr\n", func() {
+		gopExpr(&ast.BadExpr{})
+	})
+}
+
+func TestBasic(t *testing.T) {
+	test(t, `package main
+
+import "fmt"
+
+type a struct {
+	v   map[int]chan int
+	arr *[2]func()
+	i   interface{}
+}
+
+var b = &a{
+	arr: &[2]func(){
+		nil,
+		func() {},
+	},
+}
+
+const c = (10 + 20) * 2
+
+var d = b.arr[1]
+
+var e = b.arr[:1]
+
+var f = a.i.(func() (int))()
+
+func foo(v ...interface{}) {}
+`)
+}
+
+func TestMethod(t *testing.T) {
+	test(t, `package main
+
+type foo int
+
+func (a foo) Str() (string) {}
+`)
+}
diff --git a/ast/togo/goast_test.go b/ast/togo/goast_test.go
new file mode 100644
index 00000000..ff95c59a
--- /dev/null
+++ b/ast/togo/goast_test.go
@@ -0,0 +1,115 @@
+/*
+ * 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 togo
+
+import (
+	"bytes"
+	"go/format"
+	"go/token"
+	"testing"
+
+	gopast "github.com/goplus/gop/ast"
+	"github.com/goplus/gop/parser"
+)
+
+func testAST(t *testing.T, from, to string) {
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "foo.go", from, 0)
+	if err != nil {
+		t.Fatal("parser.ParseFile:", err)
+	}
+	gopf := ASTFile(f, 0)
+	var b bytes.Buffer
+	err = format.Node(&b, fset, gopf)
+	if err != nil {
+		t.Fatal("format.Node:", err)
+	}
+	result := b.String()
+	if to != result {
+		t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, to)
+	}
+}
+
+func test(t *testing.T, src string) {
+	testAST(t, src, src)
+}
+
+func testPanic(t *testing.T, panicMsg string, doPanic func()) {
+	t.Run(panicMsg, func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil {
+				t.Fatal("testPanic: no error?")
+			} else if msg := e.(string); msg != panicMsg {
+				t.Fatalf("\nResult:\n%s\nExpected Panic:\n%s\n", msg, panicMsg)
+			}
+		}()
+		doPanic()
+	})
+}
+
+func TestErrASTFile(t *testing.T) {
+	testPanic(t, "ASTFile: doesn't support keeping func body now\n", func() {
+		ASTFile(nil, KeepFuncBody)
+	})
+}
+
+func TestErrDecl(t *testing.T) {
+	testPanic(t, "goDecl: unkown decl - <nil>\n", func() {
+		goDecl(nil)
+	})
+	testPanic(t, "goGenDecl: unknown spec - ILLEGAL\n", func() {
+		goGenDecl(&gopast.GenDecl{
+			Specs: []gopast.Spec{nil},
+		})
+	})
+}
+
+func TestErrExpr(t *testing.T) {
+	testPanic(t, "goExpr: unknown expr - *ast.BadExpr\n", func() {
+		goExpr(&gopast.BadExpr{})
+	})
+}
+
+func TestBasic(t *testing.T) {
+	test(t, `package main
+
+import "fmt"
+
+type a struct {
+	v   map[int]chan int
+	arr *[2]func()
+	i   interface{}
+}
+
+var b = &a{
+	arr: &[2]func(){
+		nil,
+		func() {},
+	},
+}
+
+const c = (10 + 20) * 2
+
+var d = b.arr[1]
+
+var e = b.arr[:1]
+
+var f = a.i.(func() (int))()
+
+func foo(v ...interface{}) {}
+`)
+}
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 1af6b22e..dd9367e4 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -22,9 +22,9 @@ import (
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
-	"github.com/goplus/gop/x/gopmod"
 	"github.com/goplus/gox"
 	"github.com/goplus/gox/packages"
+	"github.com/goplus/mod/gopmod"
 )
 
 var (
@@ -129,4 +129,53 @@ func lookupClass(ext string) (c *gopmod.Class, ok bool) {
 	return
 }
 
+func TestImporter(t *testing.T) {
+	if newGopImporter("", nil) != nil {
+		t.Fatal("TestImporter failed")
+	}
+}
+
+func TestGetGoFile(t *testing.T) {
+	if f := getGoFile("a_test.gop", true); f != testingGoFile {
+		t.Fatal("TestGetGoFile:", f)
+	}
+	if f := getGoFile("a_test.gop", false); f != skippingGoFile {
+		t.Fatal("TestGetGoFile:", f)
+	}
+}
+
+func TestErrNewType(t *testing.T) {
+	testPanic(t, `bar redeclared in this block
+	previous declaration at <TODO>
+`, func() {
+		pkg := types.NewPackage("", "foo")
+		newType(pkg, token.NoPos, "bar")
+		newType(pkg, token.NoPos, "bar")
+	})
+}
+
+func TestErrDeclFunc(t *testing.T) {
+	testPanic(t, "invalid receiver type **byte (**byte is not a defined type)\n", func() {
+		pkg := gox.NewPackage("", "foo", goxConf)
+		recv := pkg.NewParam(token.NoPos, "p", types.NewPointer(types.NewPointer(gox.TyByte)))
+		declFunc(&blockCtx{pkg: pkg}, recv, &ast.FuncDecl{
+			Name: &ast.Ident{Name: "m"},
+			Type: &ast.FuncType{Params: &ast.FieldList{}},
+		})
+	})
+}
+
+func testPanic(t *testing.T, panicMsg string, doPanic func()) {
+	t.Run(panicMsg, func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil {
+				t.Fatal("testPanic: no error?")
+			} else if msg := e.(string); msg != panicMsg {
+				t.Fatalf("\nResult:\n%s\nExpected Panic:\n%s\n", msg, panicMsg)
+			}
+		}()
+		doPanic()
+	})
+}
+
 // -----------------------------------------------------------------------------
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index f0dbfc04..53435033 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -26,8 +26,7 @@ import (
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/scanner"
-	"github.com/goplus/gop/x/gopmod"
-	"github.com/goplus/gox"
+	"github.com/goplus/mod/gopmod"
 )
 
 func newTwoFileFS(dir string, fname, data string, fname2 string, data2 string) *parsertest.MemFS {
@@ -90,7 +89,7 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode,
 			t.Fatal("NewPackage:", err)
 		}
 		var b bytes.Buffer
-		err = gox.WriteTo(&b, pkg, false)
+		err = pkg.WriteTo(&b)
 		if err != nil {
 			t.Fatal("gox.WriteTo failed:", err)
 		}
@@ -404,6 +403,7 @@ func (this *Kai) onMsg(msg string) {
 
 func TestSpxMainEntry(t *testing.T) {
 	conf := *gblConf
+	conf.Importer = nil
 	conf.NoAutoGenMain = false
 
 	gopSpxTestExConf(t, "Nocode", &conf, `
diff --git a/cl/compile_test.go b/cl/compile_test.go
index cadc4fe8..86c3ab75 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -46,6 +46,7 @@ func init() {
 		Fset:          gblFset,
 		Importer:      imp,
 		LookupClass:   lookupClass,
+		GopRoot:       "..",
 		NoFileLine:    true,
 		NoAutoGenMain: true,
 	}
@@ -62,10 +63,19 @@ func gopClTest(t *testing.T, gopcode, expected string) {
 }
 
 func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) {
+	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", gopcode)
+	gopClTestFS(t, conf, fs, pkgname, expected)
+}
+
+func gopMixedClTest(t *testing.T, pkgname, gocode, gopcode, expected string) {
+	fs := parsertest.NewTwoFilesFS("/foo", "a.go", gocode, "b.gop", gopcode)
+	gopClTestFS(t, gblConf, fs, pkgname, expected)
+}
+
+func gopClTestFS(t *testing.T, conf *cl.Config, fs parser.FileSystem, pkgname, expected string) {
 	cl.SetDisableRecover(true)
 	defer cl.SetDisableRecover(false)
 
-	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", gopcode)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{Mode: parser.ParseComments})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
@@ -77,7 +87,7 @@ func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected strin
 		t.Fatal("NewPackage:", err)
 	}
 	var b bytes.Buffer
-	err = gox.WriteTo(&b, pkg, false)
+	err = pkg.WriteTo(&b)
 	if err != nil {
 		t.Fatal("gox.WriteTo failed:", err)
 	}
@@ -87,6 +97,222 @@ func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected strin
 	}
 }
 
+func TestMixedGo(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+import "strconv"
+
+const n = 10
+
+func f(v int) string {
+	return strconv.Itoa(v)
+}
+
+type foo struct {
+	v int
+}
+
+func (a foo) _() {
+}
+
+func (a foo) Str() string {
+	return f(a.v)
+}
+
+func (a *foo) Bar() int {
+	return 0
+}
+
+type foo2 = foo
+type foo3 foo2
+`, `
+var a [n]int
+var b string = f(n)
+var c foo2
+var d int = c.v
+var e = foo3{}
+var x string = c.str
+`, `package main
+
+var a [10]int
+var b string = f(n)
+var c foo
+var d int = c.v
+var e = foo3{}
+var x string = c.Str()
+`)
+}
+
+func TestUnderscoreRedeclared_Issue1197(t *testing.T) {
+	gopClTest(t, `
+func() (_ [2]int) { type _ int; return }()
+`, `package main
+
+func main() {
+	func() (_ [2]int) {
+		return
+	}()
+}
+`)
+}
+
+func TestInterfaceBugNilUnderlying_Issue1198(t *testing.T) {
+	gopClTest(t, `
+import "runtime"
+
+type Outer interface{ Inner }
+
+type impl struct{}
+
+func New() Outer { return &impl{} }
+
+type Inner interface {
+	DoStuff() error
+}
+
+func (a *impl) DoStuff() error {
+	return nil
+}
+
+func main() {
+	var outer Outer = New()
+}
+`, `package main
+
+type Inner interface {
+	DoStuff() error
+}
+type Outer interface {
+	Inner
+}
+type impl struct {
+}
+
+func (a *impl) DoStuff() error {
+	return nil
+}
+func New() Outer {
+	return &impl{}
+}
+func main() {
+	var outer Outer = New()
+}
+`)
+}
+
+func TestInterfaceBugNilUnderlying_Issue1196(t *testing.T) {
+	gopClTest(t, `
+func main() {
+	i := I(A{})
+
+	b := make(chan I, 1)
+	b <- B{}
+
+	var ok bool
+	i, ok = <-b
+}
+
+type I interface{ M() int }
+
+type T int
+
+func (T) M() int { return 0 }
+
+type A struct{ T }
+type B struct{ T }
+`, `package main
+
+func main() {
+	i := I(A{})
+	b := make(chan I, 1)
+	b <- B{}
+	var ok bool
+	i, ok = <-b
+}
+
+type T int
+
+func (T) M() int {
+	return 0
+}
+
+type A struct {
+	T
+}
+type I interface {
+	M() int
+}
+type B struct {
+	T
+}
+`)
+}
+
+func TestMyIntInc_Issue1195(t *testing.T) {
+	gopClTest(t, `
+type MyInt int
+var c MyInt
+c++
+`, `package main
+
+type MyInt int
+
+var c MyInt
+
+func main() {
+	c++
+}
+`)
+}
+
+func TestAutoPropMixedName_Issue1194(t *testing.T) {
+	gopClTest(t, `
+type Point struct {
+	Min, Max int
+}
+
+type Obj struct {
+	bbox Point
+}
+
+func (o *Obj) Bbox() Point {
+	return o.bbox
+}
+
+func (o *Obj) Points() [2]int{
+	return [2]int{o.bbox.Min, o.bbox.Max}
+}
+`, `package main
+
+type Point struct {
+	Min int
+	Max int
+}
+type Obj struct {
+	bbox Point
+}
+
+func (o *Obj) Bbox() Point {
+	return o.bbox
+}
+func (o *Obj) Points() [2]int {
+	return [2]int{o.bbox.Min, o.bbox.Max}
+}
+`)
+}
+
+func TestShiftUntypedInt_Issue1193(t *testing.T) {
+	gopClTest(t, `
+func GetValue(shift uint) uint {
+	return 1 << shift
+}`, `package main
+
+func GetValue(shift uint) uint {
+	return 1 << shift
+}
+`)
+}
+
 func TestInitFunc(t *testing.T) {
 	gopClTest(t, `
 
diff --git a/cl/run_test.go b/cl/run_test.go
index 4687d724..e05b025d 100644
--- a/cl/run_test.go
+++ b/cl/run_test.go
@@ -15,7 +15,6 @@ import (
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/scanner"
-	"github.com/goplus/gox"
 )
 
 var (
@@ -95,7 +94,7 @@ func genGo(t *testing.T, conf *cl.Config, gopcode string) []byte {
 		t.Fatal("NewPackage:", err)
 	}
 	var b bytes.Buffer
-	err = gox.WriteTo(&b, pkg, false)
+	err = pkg.WriteTo(&b)
 	if err != nil {
 		t.Fatal("gox.WriteTo failed:", err)
 	}
diff --git a/cmd/goptestgo/goptestgo.go b/cmd/goptestgo/goptestgo.go
index d25e5ee1..43f426e4 100644
--- a/cmd/goptestgo/goptestgo.go
+++ b/cmd/goptestgo/goptestgo.go
@@ -18,32 +18,50 @@ package main
 
 import (
 	"bytes"
+	"crypto/sha1"
+	"encoding/base64"
 	"go/build"
 	"io/ioutil"
 	"log"
 	"os"
 	"os/exec"
 	"path/filepath"
+	"time"
+
+	"github.com/goplus/gop"
+	"github.com/goplus/gop/x/gocmd"
+	"github.com/goplus/gop/x/gopenv"
 )
 
-// RunGopCmd executes `gop` command tools.
-func RunGopCmd(dir string, op string, args ...string) {
-	opwargs := make([]string, len(args)+1)
-	opwargs[0] = op
-	copy(opwargs[1:], args)
-	cmd := exec.Command("gop", opwargs...)
-	cmd.Dir = dir
-	cmd.Stdin = os.Stdin
-	cmd.Stdout = os.Stdout
-	cmd.Stderr = os.Stderr
-	cmd.Env = os.Environ()
-	err := cmd.Run()
+func fileIsDirty(srcMod time.Time, destFile string) bool {
+	fiDest, err := os.Stat(destFile)
+	if err != nil {
+		return true
+	}
+	return srcMod.After(fiDest.ModTime())
+}
+
+func runGoFile(dir, file, fname string) {
+	gopEnv := gopenv.Get()
+	conf := &gop.Config{Gop: gopEnv}
+	confCmd := &gocmd.BuildConfig{Gop: gopEnv}
+	fi, err := os.Stat(file)
 	if err != nil {
-		switch e := err.(type) {
-		case *exec.ExitError:
-			os.Exit(e.ExitCode())
-		default:
-			log.Fatalln("RunGopCmd failed:", err)
+		log.Panicln(err)
+	}
+	absFile, _ := filepath.Abs(file)
+	hash := sha1.Sum([]byte(absFile))
+	outFile := dir + "g" + base64.RawURLEncoding.EncodeToString(hash[:]) + fname
+	if fileIsDirty(fi.ModTime(), outFile) {
+		err = gop.RunFiles(outFile, []string{file}, nil, conf, confCmd)
+		if err != nil {
+			os.Remove(outFile)
+			switch e := err.(type) {
+			case *exec.ExitError:
+				os.Exit(e.ExitCode())
+			default:
+				log.Fatalln("runGoFile:", err)
+			}
 		}
 	}
 }
@@ -64,6 +82,8 @@ var (
 		"issue18149.go":     {},
 		"issue22662.go":     {},
 		"issue27201.go":     {},
+		"issue46903.go":     {},
+		"issue50190.go":     {}, // interesting, should be fixed
 		"nilptr_aix.go":     {},
 		"inline_literal.go": {},
 		"returntype.go":     {}, // not a problem
@@ -72,6 +92,9 @@ var (
 )
 
 func gopTestRunGo(dir string) {
+	home, _ := os.UserHomeDir()
+	targetDir := home + "/.gop/run/"
+	os.MkdirAll(targetDir, 0777)
 	filepath.Walk(dir, func(file string, fi os.FileInfo, err error) error {
 		name := fi.Name()
 		if err != nil || fi.IsDir() {
@@ -94,8 +117,8 @@ func gopTestRunGo(dir string) {
 		if !bytes.HasPrefix(data, goRunPrefix) {
 			return nil
 		}
-		log.Println("==> gop run -gop -v", file)
-		RunGopCmd("", "run", "-nr", "-rtoe", "-gop", file)
+		log.Println("==> gop run -v", file)
+		runGoFile(targetDir, file, name)
 		return nil
 	})
 }
diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go
index 024cfddd..f174ca18 100644
--- a/cmd/internal/test/test.go
+++ b/cmd/internal/test/test.go
@@ -18,13 +18,18 @@
 package test
 
 import (
+	"fmt"
+	"log"
 	"os"
+	"reflect"
+	"syscall"
 
-	"github.com/qiniu/x/log"
-
+	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
-	"github.com/goplus/gop/x/gengo"
+	"github.com/goplus/gop/x/gocmd"
+	"github.com/goplus/gop/x/gopenv"
+	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 )
 
@@ -54,16 +59,48 @@ func runCmd(_ *base.Command, args []string) {
 		pattern = []string{"."}
 	}
 
+	projs, err := gopprojs.ParseAll(pattern...)
+	if err != nil {
+		log.Panicln("gopprojs.ParseAll:", err)
+	}
+
 	if *flagVerbose {
 		gox.SetDebug(gox.DbgFlagAll &^ gox.DbgFlagComments)
 		cl.SetDebug(cl.DbgFlagAll)
 		cl.SetDisableRecover(true)
 	}
 
-	if !gengo.GenGo(gengo.Config{}, false, ".", pattern...) {
-		os.Exit(1)
+	gopEnv := gopenv.Get()
+	conf := &gop.Config{Gop: gopEnv}
+	confCmd := &gocmd.Config{Gop: gopEnv}
+	for _, proj := range projs {
+		test(proj, conf, confCmd)
+	}
+}
+
+func test(proj gopprojs.Proj, conf *gop.Config, test *gocmd.TestConfig) {
+	var obj string
+	var err error
+	switch v := proj.(type) {
+	case *gopprojs.DirProj:
+		obj = v.Dir
+		err = gop.TestDir(obj, conf, test)
+	case *gopprojs.PkgPathProj:
+		obj = v.Path
+		err = gop.TestPkgPath("", v.Path, conf, test)
+	case *gopprojs.FilesProj:
+		err = gop.TestFiles(v.Files, conf, test)
+		if err != nil {
+			log.Panicln(err)
+		}
+	default:
+		log.Panicln("`gop test` doesn't support", reflect.TypeOf(v))
+	}
+	if err == syscall.ENOENT {
+		fmt.Fprintf(os.Stderr, "gop test %v: not found\n", obj)
+	} else if err != nil {
+		log.Panicln(err)
 	}
-	base.RunGoCmd(".", "test", args...)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/env/mod_test.go b/env/mod_test.go
deleted file mode 100644
index a6ffbd67..00000000
--- a/env/mod_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (c) 2021 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 env
-
-import (
-	"os"
-	"path/filepath"
-	"runtime"
-	"strings"
-	"testing"
-)
-
-const (
-	sep = string(os.PathSeparator)
-)
-
-func TestGopMod(t *testing.T) {
-	file, err := GOPMOD("")
-	if err != nil {
-		t.Fatal("GOPMOD failed:", err)
-	}
-	if !strings.HasSuffix(file, "gop"+sep+"go.mod") {
-		t.Fatal("GOPMOD failed:", file)
-	}
-	file, err = GOPMOD("./modtest")
-	if err != nil {
-		t.Fatal("GOPMOD modtest failed:", err)
-	}
-	if !strings.HasSuffix(filepath.ToSlash(file), "env/modtest/gop.mod") {
-		t.Fatal("GOPMOD modtest failed:", file)
-	}
-}
-
-func TestGOPATH(t *testing.T) {
-	os.Setenv("GOPATH", "")
-	gopath := GOPATH()
-	home := HOME()
-	expect := filepath.Join(home, "go")
-	if expect == filepath.Clean(runtime.GOROOT()) {
-		if gopath != "" {
-			t.Fatal("TestGOPATH failed:", gopath)
-		}
-	} else if gopath != expect {
-		t.Fatal("TestGOPATH failed:", gopath)
-	}
-	const ugopath = "/abc/work"
-	os.Setenv("GOPATH", ugopath)
-	gopath = GOPATH()
-	if gopath != ugopath {
-		t.Fatal("TestGOPATH failed:", gopath)
-	}
-
-	os.Setenv("GOPATH", "")
-	os.Setenv(envHOME, "")
-	defer os.Setenv(envHOME, home)
-	if gopath := GOPATH(); gopath != "" {
-		t.Fatal("TestGOPATH failed:", gopath)
-	}
-}
-
-func TestGOMODCACHE(t *testing.T) {
-	gopath := GOPATH()
-	os.Setenv("GOMODCACHE", "")
-	modcache := GOMODCACHE()
-	if filepath.Join(gopath, "pkg/mod") != modcache {
-		t.Fatal("TestGOMODCACHE failed:", modcache)
-	}
-	const umodcache = "/abc/mod"
-	os.Setenv("GOMODCACHE", umodcache)
-	modcache = GOMODCACHE()
-	if modcache != umodcache {
-		t.Fatal("TestGOMODCACHE (umodcache) failed:", modcache)
-	}
-	/* TODO:
-	os.Setenv("GOMODCACHE", "")
-	os.Setenv("GOPATH", "")
-	home := HOME()
-	defer os.Setenv(envHOME, home)
-	os.Setenv(envHOME, strings.TrimRight(runtime.GOROOT(), "go"))
-	modcache = GOMODCACHE()
-	if modcache != "" {
-		t.Fatal("TestGOMODCACHE (GOROOT) failed:", modcache)
-	}
-	*/
-}
diff --git a/env/path_test.go b/env/path_test.go
index 38162da6..a38dc654 100644
--- a/env/path_test.go
+++ b/env/path_test.go
@@ -21,10 +21,12 @@ import (
 	"os"
 	"path/filepath"
 	"testing"
+
+	"github.com/goplus/mod"
 )
 
 func findGoModFile(dir string) (modfile string, noCacheFile bool, err error) {
-	modfile, err = GOPMOD(dir)
+	modfile, err = mod.GOPMOD(dir, 0)
 	if err != nil {
 		gopRoot, err := findGopRoot()
 		if err == nil {
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 4875d4e8..003053a5 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -23,13 +23,13 @@ import (
 	"path"
 	"reflect"
 	"strings"
+	"syscall"
 	"testing"
 
-	"github.com/qiniu/x/log"
-
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/scanner"
 	"github.com/goplus/gop/token"
+	"github.com/qiniu/x/log"
 )
 
 // -----------------------------------------------------------------------------
@@ -89,7 +89,7 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
 	}
 	log.Println("Parsing", pkgDir)
 	fset := token.NewFileSet()
-	pkgs, err := ParseDir(fset, pkgDir, nil, (Trace|ParseComments)&^exclude)
+	pkgs, err := ParseDir(fset, pkgDir, nil, (Trace|ParseComments|ParseGoAsGoPlus)&^exclude)
 	if err != nil || len(pkgs) != 1 {
 		if errs, ok := err.(scanner.ErrorList); ok {
 			for _, e := range errs {
@@ -110,7 +110,7 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
 
 func TestParseGo(t *testing.T) {
 	fset := token.NewFileSet()
-	pkgs, err := ParseDirEx(fset, "./_testdata/functype", Config{Mode: Trace})
+	pkgs, err := ParseDirEx(fset, "./_testdata/functype", Config{Mode: Trace | ParseGoAsGoPlus})
 	if err != nil {
 		t.Fatal("TestParseGo: ParseDir failed -", err)
 	}
@@ -121,7 +121,7 @@ func TestParseGo(t *testing.T) {
 
 func TestParseGoFiles(t *testing.T) {
 	fset := token.NewFileSet()
-	pkgs, err := ParseFiles(fset, []string{"./_testdata/functype/functype.go"}, Trace)
+	pkgs, err := ParseFiles(fset, []string{"./_testdata/functype/functype.go"}, Trace|ParseGoAsGoPlus)
 	if err != nil {
 		t.Fatal("TestParseGoFiles: ParseFiles failed -", err)
 	}
@@ -130,6 +130,51 @@ func TestParseGoFiles(t *testing.T) {
 	}
 }
 
+func TestGopAutoGen(t *testing.T) {
+	fset := token.NewFileSet()
+	fs := parsertest.NewSingleFileFS("/foo", "gop_autogen.go", ``)
+	pkgs, err := ParseFSDir(fset, fs, "/foo", Config{})
+	if err != nil {
+		t.Fatal("ParseFSDir:", err)
+	}
+	if len(pkgs) != 0 {
+		t.Fatal("TestGopAutoGen:", len(pkgs))
+	}
+}
+
+func TestGoFile(t *testing.T) {
+	fset := token.NewFileSet()
+	fs := parsertest.NewSingleFileFS("/foo", "test.go", `package foo`)
+	pkgs, err := ParseFSDir(fset, fs, "/foo", Config{})
+	if err != nil {
+		t.Fatal("ParseFSDir:", err)
+	}
+	if len(pkgs) != 1 || pkgs["foo"].GoFiles["/foo/test.go"] == nil {
+		t.Fatal("TestGoFile:", len(pkgs))
+	}
+}
+
+func TestErrParse(t *testing.T) {
+	fset := token.NewFileSet()
+	fs := parsertest.NewSingleFileFS("/foo", "test.go", `package foo bar`)
+	_, err := ParseFSDir(fset, fs, "/foo", Config{})
+	if err == nil {
+		t.Fatal("ParseFSDir test.go: no error?")
+	}
+
+	fs = parsertest.NewSingleFileFS("/foo", "test.gop", `package foo bar`)
+	_, err = ParseFSDir(fset, fs, "/foo", Config{})
+	if err == nil {
+		t.Fatal("ParseFSDir test.gop: no error?")
+	}
+
+	fs = parsertest.NewMemFS(map[string][]string{"/foo": {"test.go"}}, map[string]string{})
+	_, err = ParseFSDir(fset, fs, "/foo", Config{})
+	if err != syscall.ENOENT {
+		t.Fatal("ParseFSDir:", err)
+	}
+}
+
 func TestFromTestdata(t *testing.T) {
 	sel := ""
 	dir, err := os.Getwd()
diff --git a/parser/parsertest/memfs.go b/parser/parsertest/memfs.go
index dbe7e402..09e7b9ef 100644
--- a/parser/parsertest/memfs.go
+++ b/parser/parsertest/memfs.go
@@ -109,4 +109,14 @@ func NewSingleFileFS(dir string, fname string, data string) *MemFS {
 	})
 }
 
+// NewTwoFilesFS creates a file system that contains two files.
+func NewTwoFilesFS(dir string, fname1, data1, fname2, data2 string) *MemFS {
+	return NewMemFS(map[string][]string{
+		dir: {fname1, fname2},
+	}, map[string]string{
+		path.Join(dir, fname1): data1,
+		path.Join(dir, fname2): data2,
+	})
+}
+
 // -----------------------------------------------------------------------------
diff --git a/testdata/mixgo/a.go b/testdata/mixgo/a.go
new file mode 100644
index 00000000..46c3612e
--- /dev/null
+++ b/testdata/mixgo/a.go
@@ -0,0 +1,8 @@
+package main
+
+import "fmt"
+
+func p(a interface{}) {
+	sayMix()
+	fmt.Println("Hello,", a)
+}
diff --git a/testdata/mixgo/b.gop b/testdata/mixgo/b.gop
new file mode 100644
index 00000000..26f88bb3
--- /dev/null
+++ b/testdata/mixgo/b.gop
@@ -0,0 +1,5 @@
+func sayMix() {
+	println "Mix Go and Go+"
+}
+
+p "world"
diff --git a/x/mod/modfile/gop_test.go b/x/mod/modfile/gop_test.go
deleted file mode 100644
index 12dc5269..00000000
--- a/x/mod/modfile/gop_test.go
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (c) 2021 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 modfile
-
-import (
-	"path/filepath"
-	"testing"
-
-	"golang.org/x/mod/modfile"
-)
-
-func TestUpdateLine(t *testing.T) {
-	line := &Line{InBlock: true}
-	updateLine(line, "foo", "bar")
-	if len(line.Token) != 1 && line.Token[0] != "bar" {
-		t.Fatal("updateLine failed:", line.Token)
-	}
-}
-
-func TestGetWeight(t *testing.T) {
-	if getWeight(&modfile.LineBlock{Token: []string{"require"}}) != directiveRequire {
-		t.Fatal("getWeight require failed")
-	}
-	if getWeight(&modfile.LineBlock{Token: []string{"unknown"}}) != directiveLineBlock {
-		t.Fatal("getWeight unknown failed")
-	}
-}
-
-// -----------------------------------------------------------------------------
-
-const gopmodSpx = `
-module spx
-
-go 1.17
-gop 1.1
-
-classfile .gmx .spx github.com/goplus/spx math
-
-require (
-	github.com/ajstarks/svgo v0.0.0-20210927141636-6d70534b1098
-)
-`
-
-func TestGoModCompat(t *testing.T) {
-	const (
-		gopmod = gopmodSpx
-	)
-	f, err := modfile.ParseLax("go.mod", []byte(gopmod), nil)
-	if err != nil || len(f.Syntax.Stmt) != 5 {
-		t.Fatal("modfile.ParseLax failed:", f, err)
-	}
-
-	gop := f.Syntax.Stmt[2].(*modfile.Line)
-	if len(gop.Token) != 2 || gop.Token[0] != "gop" || gop.Token[1] != "1.1" {
-		t.Fatal("modfile.ParseLax gop:", gop)
-	}
-
-	require := f.Syntax.Stmt[4].(*modfile.LineBlock)
-	if len(require.Token) != 1 || require.Token[0] != "require" {
-		t.Fatal("modfile.ParseLax require:", require)
-	}
-	if len(require.Line) != 1 {
-		t.Fatal("modfile.ParseLax require.Line:", require.Line)
-	}
-}
-
-// -----------------------------------------------------------------------------
-
-func TestParse1(t *testing.T) {
-	const (
-		gopmod = gopmodSpx
-	)
-	f, err := ParseLax("github.com/goplus/gop/gop.mod", []byte(gopmod), nil)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	if f.Gop.Version != "1.1" {
-		t.Errorf("gop version expected be 1.1, but %s got", f.Gop.Version)
-	}
-
-	if f.Classfile.ProjExt != ".gmx" {
-		t.Errorf("classfile exts expected be .gmx, but %s got", f.Classfile.ProjExt)
-	}
-	if f.Classfile.WorkExt != ".spx" {
-		t.Errorf("classfile exts expected be .spx, but %s got", f.Classfile.WorkExt)
-	}
-
-	if len(f.Classfile.PkgPaths) != 2 {
-		t.Errorf("classfile pkgpaths length expected be 2, but %d got", len(f.Classfile.PkgPaths))
-	}
-
-	if f.Classfile.PkgPaths[0] != "github.com/goplus/spx" {
-		t.Errorf("classfile path expected be github.com/goplus/spx, but %s got", f.Classfile.PkgPaths[0])
-	}
-	if f.Classfile.PkgPaths[1] != "math" {
-		t.Errorf("classfile path expected be math, but %s got", f.Classfile.PkgPaths[1])
-	}
-}
-
-const gopmodUserProj = `
-module moduleUserProj
-
-go 1.17
-gop 1.1
-
-register github.com/goplus/spx
-
-require (
-    github.com/goplus/spx v1.0
-)
-
-replace (
-	github.com/goplus/spx v1.0 => github.com/xushiwei/spx v1.2
-	github.com/goplus/gop => /Users/xushiwei/work/gop
-)
-`
-
-func TestParse2(t *testing.T) {
-	const (
-		gopmod = gopmodUserProj
-	)
-	f, err := Parse("github.com/goplus/gop/gop.mod", []byte(gopmod), nil)
-	if err != nil || len(f.Register) != 1 {
-		t.Fatal("Parse:", f, err)
-		return
-	}
-	if f.Register[0].ClassfileMod != "github.com/goplus/spx" {
-		t.Fatal("Parse => Register:", f.Register)
-	}
-	if len(f.Replace) != 2 {
-		t.Fatal("Parse => Replace:", f.Replace)
-	}
-	f.DropAllRequire()
-	if f.Require != nil {
-		t.Fatal("DropAllRequire failed")
-	}
-	f.AddRegister("github.com/goplus/spx")
-	if len(f.Register) != 1 {
-		t.Fatal("AddRegister not exist?")
-	}
-	f.AddRegister("github.com/xushiwei/foogop")
-	if len(f.Register) != 2 {
-		t.Fatal("AddRegister failed")
-	}
-	f.AddReplace("github.com/goplus/spx", "v1.0", "/Users/xushiwei/work/spx", "")
-	f.DropAllReplace()
-	if f.Replace != nil {
-		t.Fatal("DropAllReplace failed")
-	}
-}
-
-func TestParseErr(t *testing.T) {
-	doTestParseErr(t, `gop.mod:2: replace github.com/goplus/gop/v2: version "v1.2.0" invalid: should be v2, not v1`, `
-replace github.com/goplus/gop/v2 v1.2 => ../
-`)
-	if filepath.Separator == '/' {
-		doTestParseErr(t, `gop.mod:2:3: replacement directory appears to be Windows path (on a non-windows system)`, `
-		replace github.com/goplus/gop v1.2 => ..\
-		`)
-	}
-	doTestParseErr(t, `gop.mod:2: replacement module directory path "../" cannot have version`, `
-replace github.com/goplus/gop v1.2 => ../ v1.3
-`)
-	doTestParseErr(t, `gop.mod:2: replacement module without version must be directory path (rooted or starting with ./ or ../)`, `
-replace github.com/goplus/gop v1.2 => abc.com
-`)
-	doTestParseErr(t, `gop.mod:2: invalid quoted string: unquoted string cannot contain quote`, `
-replace github.com/goplus/gop v1.2 => /"
-`)
-	doTestParseErr(t, `gop.mod:2: replace github.com/goplus/gop: version "v1.2\"" invalid: unquoted string cannot contain quote`, `
-replace github.com/goplus/gop v1.2" => /
-`)
-	doTestParseErr(t, `gop.mod:2: invalid quoted string: unquoted string cannot contain quote`, `
-replace gopkg.in/" v1.2 => /
-`)
-	doTestParseErr(t, `gop.mod:2: replace gopkg.in/?: invalid module path`, `
-replace gopkg.in/? v1.2 => /
-`)
-	doTestParseErr(t, `gop.mod:2: replace /: version "?" invalid: must be of the form v1.2.3`, `
-replace github.com/goplus/gop => / ?
-`)
-	doTestParseErr(t, `gop.mod:2: replace github.com/goplus/gop: version "?" invalid: must be of the form v1.2.3`, `
-replace github.com/goplus/gop ? => /
-`)
-	doTestParseErr(t, `gop.mod:2: usage: replace module/path [v1.2.3] => other/module v1.4
-	 or replace module/path [v1.2.3] => ../local/directory`, `
-replace ?
-`)
-	doTestParseErr(t, `gop.mod:3: repeated go statement`, `
-gop 1.1
-gop 1.2
-`)
-	doTestParseErr(t, `gop.mod:2: go directive expects exactly one argument`, `
-gop 1.1 1.2
-`)
-	doTestParseErr(t, `gop.mod:2: invalid gop version '1.x': must match format 1.23`, `
-gop 1.x
-`)
-	doTestParseErr(t, `gop.mod:2: register directive expects exactly one argument`, `
-register 1 2 3
-`)
-	doTestParseErr(t, `gop.mod:2: invalid quoted string: invalid syntax`, `
-register "\?"
-`)
-	doTestParseErr(t, `gop.mod:2: malformed module path "-": leading dash`, `
-register -
-`)
-	doTestParseErr(t, `gop.mod:3: repeated classfile statement`, `
-classfile .gmx .spx github.com/goplus/spx math
-classfile .gmx .spx github.com/goplus/spx math
-`)
-	doTestParseErr(t, `gop.mod:2: usage: classfile projExt workExt [classFilePkgPath ...]`, `
-classfile .gmx .spx
-`)
-	doTestParseErr(t, `gop.mod:2: ext . invalid: invalid ext format`, `
-classfile .gmx . math
-`)
-	doTestParseErr(t, `gop.mod:2: ext "\?" invalid: invalid syntax`, `
-classfile "\?" .spx math
-`)
-	doTestParseErr(t, `gop.mod:2: invalid quoted string: invalid syntax`, `
-classfile .123 .spx "\?"
-`)
-	doTestParseErr(t, `gop.mod:2: unknown directive: unknown`, `
-unknown .spx
-`)
-	doTestParseErr(t, `gop.mod:2: invalid go version '1.x': must match format 1.23`, `
-go 1.x
-`)
-}
-
-func doTestParseErr(t *testing.T, errMsg string, gopmod string) {
-	t.Run(errMsg, func(t *testing.T) {
-		_, err := Parse("gop.mod", []byte(gopmod), nil)
-		if err == nil {
-			t.Fatal("Parse: no error?")
-			return
-		}
-		if err.Error() != errMsg {
-			t.Error("Parse got:", err, "\nExpected:", errMsg)
-		}
-	})
-}
-
-// -----------------------------------------------------------------------------
diff --git a/x/mod/modfile/read_test.go b/x/mod/modfile/read_test.go
deleted file mode 100644
index f4254e10..00000000
--- a/x/mod/modfile/read_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2021 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 modfile
-
-import (
-	"testing"
-)
-
-var modulePathTests = []struct {
-	input    []byte
-	expected string
-}{
-	{input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
-	{input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
-	{input: []byte("module  \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"},
-	{input: []byte("module  github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"},
-	{input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"},
-	{input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"},
-	{input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"},
-	{input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"},
-	{input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"},
-	{input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"},
-	{input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""},
-	{input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"},
-	{input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"},
-	{input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"},
-	{input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"},
-	{input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"},
-	{input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
-	{input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
-	{input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
-	{input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""},
-	{input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""},
-	{input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""},
-	{input: []byte("module  \nmodule a/b/c "), expected: "a/b/c"},
-	{input: []byte("module \"   \""), expected: "   "},
-	{input: []byte("module   "), expected: ""},
-	{input: []byte("module \"  a/b/c  \""), expected: "  a/b/c  "},
-	{input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"},
-}
-
-func TestModulePath(t *testing.T) {
-	for _, test := range modulePathTests {
-		t.Run(string(test.input), func(t *testing.T) {
-			result := ModulePath(test.input)
-			if result != test.expected {
-				t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected)
-			}
-		})
-	}
-}
diff --git a/x/mod/modfile/rule_test.go b/x/mod/modfile/rule_test.go
deleted file mode 100644
index 26ad0aa0..00000000
--- a/x/mod/modfile/rule_test.go
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (c) 2021 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 modfile
-
-import (
-	"bytes"
-	"syscall"
-	"testing"
-)
-
-// -----------------------------------------------------------------------------
-
-var addParseExtTests = []struct {
-	desc    string
-	ext     string
-	want    string
-	wantErr string
-}{
-	{
-		"spx ok",
-		".spx",
-		".spx",
-		"",
-	},
-	{
-		"no ext",
-		"",
-		"",
-		"",
-	},
-	{
-		"not a ext",
-		"gmx",
-		"",
-		"ext gmx invalid: invalid ext format",
-	},
-}
-
-func TestParseExt(t *testing.T) {
-	if (&InvalidExtError{Err: syscall.EINVAL}).Unwrap() != syscall.EINVAL {
-		t.Fatal("InvalidExtError.Unwrap failed")
-	}
-	for _, tt := range addParseExtTests {
-		t.Run(tt.desc, func(t *testing.T) {
-			ext, err := parseExt(&tt.ext)
-			if err != nil {
-				if err.Error() != tt.wantErr {
-					t.Fatalf("wanterr: %s, but got: %s", tt.wantErr, err)
-				}
-			}
-			if ext != tt.want {
-				t.Fatalf("want: %s, but got: %s", tt.want, ext)
-			}
-		})
-	}
-}
-
-func TestIsDirectoryPath(t *testing.T) {
-	if !IsDirectoryPath("./...") {
-		t.Fatal("IsDirectoryPath failed")
-	}
-}
-
-func TestFormat(t *testing.T) {
-	if b := Format(&FileSyntax{}); len(b) != 0 {
-		t.Fatal("Format failed:", b)
-	}
-}
-
-func TestMustQuote(t *testing.T) {
-	if !MustQuote("") {
-		t.Fatal("MustQuote failed")
-	}
-}
-
-// -----------------------------------------------------------------------------
-
-var addGopTests = []struct {
-	desc    string
-	in      string
-	version string
-	out     string
-}{
-	{
-		`module_only`,
-		`module m
-		`,
-		`1.14`,
-		`module m
-		gop 1.14
-		`,
-	},
-	{
-		`module_before_require`,
-		`module m
-		require x.y/a v1.2.3
-		`,
-		`1.14`,
-		`module m
-		gop 1.14
-		require x.y/a v1.2.3
-		`,
-	},
-	{
-		`require_before_module`,
-		`require x.y/a v1.2.3
-		module example.com/inverted
-		`,
-		`1.14`,
-		`gop 1.14
-		require x.y/a v1.2.3
-		module example.com/inverted
-		`,
-	},
-	{
-		`require_only`,
-		`require x.y/a v1.2.3
-		`,
-		`1.14`,
-		`gop 1.14
-		require x.y/a v1.2.3
-		`,
-	},
-	{
-		`replace gop`,
-		`require x.y/a v1.2.3
-		gop 1.10
-		`,
-		`1.14`,
-		`require x.y/a v1.2.3
-		gop 1.14
-		`,
-	},
-}
-
-func TestAddGop(t *testing.T) {
-	for _, tt := range addGopTests {
-		t.Run(tt.desc, func(t *testing.T) {
-			testEdit(t, tt.in, tt.out, true, func(f *File) error {
-				return f.AddGopStmt(tt.version)
-			})
-		})
-	}
-}
-
-func TestAddGopErr(t *testing.T) {
-	if new(File).AddGopStmt("1.x") == nil {
-		t.Fatal("AddGoStmt failed")
-	}
-}
-
-func testEdit(t *testing.T, in, want string, strict bool, transform func(f *File) error) *File {
-	t.Helper()
-	parse := Parse
-	if !strict {
-		parse = ParseLax
-	}
-	f, err := parse("in", []byte(in), nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	g, err := parse("out", []byte(want), nil)
-	if err != nil {
-		t.Fatal(err)
-	}
-	golden, err := g.Format()
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if err := transform(f); err != nil {
-		t.Fatal(err)
-	}
-	out, err := f.Format()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !bytes.Equal(out, golden) {
-		t.Errorf("have:\n%s\nwant:\n%s", out, golden)
-	}
-
-	return f
-}
-
-// -----------------------------------------------------------------------------