Repository: goplus/gop
Pull request #1655 (2024-01-22 04:04:47)

Pkg publish
* rename winget package name to goplus to supports `winget install goplus`, also supports `winget install goplus.gop`
* rename deb/rpm/snapcraft package name to gop
* publish deb/rpm to fury.io
* update installation docs


Patch:
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 91acd95c..2e61bcb0 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -50,3 +50,13 @@ jobs:
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }}
+
+    - name: Upload deb/rpm to Fury.io
+      run: |
+        for file in dist/*.{deb,rpm}
+        do
+          echo "Uploading $file to Fury.io"
+          curl -sS -F package=@$file https://$FURY_TOKEN@push.fury.io/goplus/
+        done
+      env:
+        FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 908dddb4..36519f89 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -68,12 +68,25 @@ changelog:
       - "^test:"
 
 winget:
-  - name: gop
+  - name: goplus
     homepage: "https://goplus.org/"
     publisher: goplus
     publisher_url: https://github.com/goplus/gop
     publisher_support_url: "https://github.com/goplus/gop/issues/new"
     package_identifier: goplus.gop
+    path: "manifests/g/goplus/gop/{{.Version}}"
+    tags:
+      - golang
+      - go
+      - gop
+      - goplus
+      - programming
+      - language
+      - compiler
+      - interpreter
+      - data science
+      - engineering
+      - education
     short_description: The Go+ Programming Language
     description: |
       The Go+ programming language is designed for engineering, STEM education, and data science.
@@ -103,7 +116,7 @@ winget:
           branch: master
 
 nfpms:
-  - package_name: goplus-gop
+  - package_name: gop
     vendor: goplus
     homepage: https://goplus.org/
     maintainer: Li Jie <cpunion@gmail.com>
@@ -179,7 +192,7 @@ nfpms:
 
 snapcrafts:
   - id: gop
-    name: goplus
+    name: gop
     title: The Go+ Programming Language
     summary: The Go+ Programming Language
     description: |
diff --git a/README.md b/README.md
index 397bc498..f0f460e8 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,41 @@ For more details, see [Quick Start](doc/docs.md).
 
 ## How to install
 
+### on Windows
+
+```sh
+winget install goplus
+```
+
+Or
+
+```sh
+winget install goplus.gop
+```
+
+### on Debian/Ubuntu
+
+```sh
+sudo bash -c ' echo "deb [trusted=yes] https://pkgs.goplus.org/apt/ /" > /etc/apt/sources.list.d/goplus.list'
+sudo apt update
+sudo apt install gop
+```
+
+### on RedHat/CentOS/Fedora
+
+```sh
+sudo bash -c 'echo -e "[goplus]\nname=Go+ Repo\nbaseurl=https://pkgs.goplus.org/yum/\nenabled=1\ngpgcheck=0" > /etc/yum.repos.d/goplus.repo'
+sudo yum install gop
+```
+
+### on macOS/Linux(Homebrew)
+
+Install via [brew](https://brew.sh/)
+
+```sh
+$ brew install goplus
+```
+
 ### from source code
 
 For now, we suggest you install Go+ from source code.
@@ -63,14 +98,6 @@ cd gop
 all.bat
 ```
 
-### on macOS/Linux
-
-Install via [brew](https://brew.sh/)
-```sh
-$ brew install goplus
-```
-
-
 ## Go+ Applications
 
 ### 2D Games powered by Go+
diff --git a/ast/ast.go b/ast/ast.go
index 423e60b9..117002be 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -176,6 +176,13 @@ type (
 		Elt      Expr      // ellipsis element type (parameter lists only); or nil
 	}
 
+	// A BasicLit node represents a literal of basic type.
+	BasicLit struct {
+		ValuePos token.Pos   // literal position
+		Kind     token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING or token.CSTRING
+		Value    string      // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o`
+	}
+
 	// A FuncLit node represents a function literal.
 	FuncLit struct {
 		Type *FuncType  // function type
@@ -1135,7 +1142,7 @@ type File struct {
 }
 
 // There is no entrypoint func to indicate the module entry point.
-func (f *File) HasShadowEntry() bool {
+func (f *File) NoEntrypoint() bool {
 	return f.ShadowEntry != nil
 }
 
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 239a73a8..b0c398b6 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -22,69 +22,6 @@ import (
 
 // -----------------------------------------------------------------------------
 
-// OverloadFuncDecl node represents an overload function declaration:
-//
-// `func name = (overloadFuncs)`
-// `func (T).nameOrOp = (overloadFuncs)`
-//
-// here overloadFunc represents
-//
-// `func(params) {...}`
-// `funcName`
-// `(*T).methodName`
-type OverloadFuncDecl struct {
-	Doc      *CommentGroup // associated documentation; or nil
-	Func     token.Pos     // position of "func" keyword
-	Recv     *FieldList    // receiver (methods); or nil (functions)
-	Name     *Ident        // function/method name
-	Assign   token.Pos     // position of token "="
-	Lparen   token.Pos     // position of "("
-	Funcs    []Expr        // overload functions. here `Expr` can be *FuncLit, *Ident or *SelectorExpr
-	Rparen   token.Pos     // position of ")"
-	Operator bool          // is operator or not
-	IsClass  bool          // recv set by class
-}
-
-// Pos - position of first character belonging to the node.
-func (p *OverloadFuncDecl) Pos() token.Pos {
-	return p.Func
-}
-
-// End - position of first character immediately after the node.
-func (p *OverloadFuncDecl) End() token.Pos {
-	return p.Rparen + 1
-}
-
-func (*OverloadFuncDecl) declNode() {}
-
-// -----------------------------------------------------------------------------
-
-// A BasicLit node represents a literal of basic type.
-type BasicLit struct {
-	ValuePos token.Pos    // literal position
-	Kind     token.Token  // token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING
-	Value    string       // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 3r, 'a', '\x7f', "foo" or `\m\n\o`
-	Extra    *StringLitEx // optional (only available when Kind == token.STRING)
-}
-
-type StringLitEx struct {
-	Parts []any // can be (val string) or (xval Expr)
-}
-
-// NextPartPos - position of first character of next part.
-// pos - position of this part (not including quote character).
-func NextPartPos(pos token.Pos, part any) (nextPos token.Pos) {
-	switch v := part.(type) {
-	case string: // normal string literal or end with "$$"
-		return pos + token.Pos(len(v))
-	case Expr:
-		return v.End()
-	}
-	panic("NextPartPos: unexpected parameters")
-}
-
-// -----------------------------------------------------------------------------
-
 // A SliceLit node represents a slice literal.
 type SliceLit struct {
 	Lbrack     token.Pos // position of "["
@@ -93,12 +30,12 @@ type SliceLit struct {
 	Incomplete bool      // true if (source) expressions are missing in the Elts list
 }
 
-// Pos - position of first character belonging to the node.
+// Pos - position of first character belonging to the node
 func (p *SliceLit) Pos() token.Pos {
 	return p.Lbrack
 }
 
-// End - position of first character immediately after the node.
+// End - position of first character immediately after the node
 func (p *SliceLit) End() token.Pos {
 	return p.Rbrack + 1
 }
@@ -107,7 +44,7 @@ func (*SliceLit) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// ErrWrapExpr represents `expr!`, `expr?` or `expr?: defaultValue`.
+// ErrWrapExpr represents `expr!`, `expr?` or `expr?: defaultValue`
 type ErrWrapExpr struct {
 	X       Expr
 	Tok     token.Token // ! or ?
@@ -115,12 +52,12 @@ type ErrWrapExpr struct {
 	Default Expr // can be nil
 }
 
-// Pos - position of first character belonging to the node.
+// Pos - position of first character belonging to the node
 func (p *ErrWrapExpr) Pos() token.Pos {
 	return p.X.Pos()
 }
 
-// End - position of first character immediately after the node.
+// End - position of first character immediately after the node
 func (p *ErrWrapExpr) End() token.Pos {
 	if p.Default != nil {
 		return p.Default.End()
@@ -132,16 +69,13 @@ func (*ErrWrapExpr) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// LambdaExpr represents one of the following expressions:
-//
-//	`(x, y, ...) => exprOrExprTuple`
-//	`x => exprOrExprTuple`
-//	`=> exprOrExprTuple`
-//
+// LambdaExpr represents
+//    `(x, y, ...) => exprOrExprTuple`
+//    `x => exprOrExprTuple`
+//    `=> exprOrExprTuple`
 // here exprOrExprTuple represents
-//
-//	`expr`
-//	`(expr1, expr2, ...)`
+//    `expr`
+//    `(expr1, expr2, ...)`
 type LambdaExpr struct {
 	First       token.Pos
 	Lhs         []*Ident
@@ -152,11 +86,10 @@ type LambdaExpr struct {
 	RhsHasParen bool
 }
 
-// LambdaExpr2 represents one of the following expressions:
-//
-//	`(x, y, ...) => { ... }`
-//	`x => { ... }`
-//	`=> { ... }`
+// LambdaExpr2 represents
+//    `(x, y, ...) => { ... }`
+//    `x => { ... }`
+//    `=> { ... }`
 type LambdaExpr2 struct {
 	First       token.Pos
 	Lhs         []*Ident
@@ -165,22 +98,18 @@ type LambdaExpr2 struct {
 	LhsHasParen bool
 }
 
-// Pos - position of first character belonging to the node.
 func (p *LambdaExpr) Pos() token.Pos {
 	return p.First
 }
 
-// End - position of first character immediately after the node.
 func (p *LambdaExpr) End() token.Pos {
 	return p.Last
 }
 
-// Pos - position of first character belonging to the node.
 func (p *LambdaExpr2) Pos() token.Pos {
 	return p.First
 }
 
-// End - position of first character immediately after the node.
 func (p *LambdaExpr2) End() token.Pos {
 	return p.Body.End()
 }
@@ -190,7 +119,7 @@ func (*LambdaExpr2) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// ForPhrase represents `for k, v <- container, cond` phrase.
+// ForPhrase represents `for k, v <- container, cond`
 type ForPhrase struct {
 	For        token.Pos // position of "for" keyword
 	Key, Value *Ident    // Key may be nil
@@ -209,12 +138,11 @@ func (p *ForPhrase) End() token.Pos { return p.X.End() }
 
 func (p *ForPhrase) exprNode() {}
 
-// ComprehensionExpr represents one of the following expressions:
-//
-//	`[vexpr for k1, v1 <- container1, cond1 ...]` or
-//	`{vexpr for k1, v1 <- container1, cond1 ...}` or
-//	`{kexpr: vexpr for k1, v1 <- container1, cond1 ...}` or
-//	`{for k1, v1 <- container1, cond1 ...}` or
+// ComprehensionExpr represents
+//    `[vexpr for k1, v1 <- container1, cond1 ...]` or
+//    `{vexpr for k1, v1 <- container1, cond1 ...}` or
+//    `{kexpr: vexpr for k1, v1 <- container1, cond1 ...}` or
+//    `{for k1, v1 <- container1, cond1 ...}` or
 type ComprehensionExpr struct {
 	Lpos token.Pos   // position of "[" or "{"
 	Tok  token.Token // token.LBRACK '[' or token.LBRACE '{'
@@ -223,12 +151,12 @@ type ComprehensionExpr struct {
 	Rpos token.Pos // position of "]" or "}"
 }
 
-// Pos - position of first character belonging to the node.
+// Pos - position of first character belonging to the node
 func (p *ComprehensionExpr) Pos() token.Pos {
 	return p.Lpos
 }
 
-// End - position of first character immediately after the node.
+// End - position of first character immediately after the node
 func (p *ComprehensionExpr) End() token.Pos {
 	return p.Rpos + 1
 }
@@ -243,12 +171,12 @@ type ForPhraseStmt struct {
 	Body *BlockStmt
 }
 
-// Pos - position of first character belonging to the node.
+// Pos - position of first character belonging to the node
 func (p *ForPhraseStmt) Pos() token.Pos {
 	return p.For
 }
 
-// End - position of first character immediately after the node.
+// End - position of first character immediately after the node
 func (p *ForPhraseStmt) End() token.Pos {
 	return p.Body.End()
 }
@@ -266,7 +194,7 @@ type RangeExpr struct {
 	Expr3  Expr      // step (or max) of composite elements; or nil
 }
 
-// Pos - position of first character belonging to the node.
+// Pos - position of first character belonging to the node
 func (p *RangeExpr) Pos() token.Pos {
 	if p.First != nil {
 		return p.First.Pos()
@@ -274,7 +202,7 @@ func (p *RangeExpr) Pos() token.Pos {
 	return p.To
 }
 
-// End - position of first character immediately after the node.
+// End - position of first character immediately after the node
 func (p *RangeExpr) End() token.Pos {
 	if p.Expr3 != nil {
 		return p.Expr3.End()
diff --git a/build_install_run.go b/build_install_run.go
index 0a665241..9972ee65 100644
--- a/build_install_run.go
+++ b/build_install_run.go
@@ -26,8 +26,6 @@ import (
 
 // -----------------------------------------------------------------------------
 
-// InstallDir installs a Go+ package directory.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err error) {
 	_, _, err = GenGo(dir, conf, false)
 	if err != nil {
@@ -36,8 +34,6 @@ func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err err
 	return gocmd.Install(dir, install)
 }
 
-// InstallPkgPath installs a Go+ package.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, true)
 	if err != nil {
@@ -55,8 +51,6 @@ func cwdParam(recursively bool) string {
 	return "."
 }
 
-// InstallFiles installs specified Go+ files.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func InstallFiles(files []string, conf *Config, install *gocmd.InstallConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
@@ -79,8 +73,6 @@ func chdir(dir string) string {
 
 // -----------------------------------------------------------------------------
 
-// BuildDir builds a Go+ package directory.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	_, _, err = GenGo(dir, conf, false)
 	if err != nil {
@@ -89,8 +81,6 @@ func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	return gocmd.Build(dir, build)
 }
 
-// BuildPkgPath builds a Go+ package.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
 	if err != nil {
@@ -101,8 +91,6 @@ func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfi
 	return gocmd.Build(cwdParam(recursively), build)
 }
 
-// BuildFiles builds specified Go+ files.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
@@ -128,8 +116,6 @@ func restoreDirAndMod(old string, mod os.FileMode) {
 
 // -----------------------------------------------------------------------------
 
-// RunDir runs an application from a Go+ package directory.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
 	_, _, err = GenGo(dir, conf, false)
 	if err != nil {
@@ -138,8 +124,6 @@ func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err
 	return gocmd.RunDir(dir, args, run)
 }
 
-// RunPkgPath runs an application from a Go+ package.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath("", pkgPath, conf, true)
 	if err != nil {
@@ -156,8 +140,6 @@ func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *go
 	return gocmd.RunDir(localDir, args, run)
 }
 
-// RunFiles runs an application from specified Go+ files.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func RunFiles(autogen string, files []string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
 	files, err = GenGoFiles(autogen, files, conf)
 	if err != nil {
@@ -168,8 +150,6 @@ func RunFiles(autogen string, files []string, args []string, conf *Config, run *
 
 // -----------------------------------------------------------------------------
 
-// TestDir tests a Go+ package directory.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
 	_, _, err = GenGo(dir, conf, true)
 	if err != nil {
@@ -178,8 +158,6 @@ func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
 	return gocmd.Test(dir, test)
 }
 
-// TestPkgPath tests a Go+ package.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
 	if err != nil {
@@ -190,8 +168,6 @@ func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig)
 	return gocmd.Test(cwdParam(recursively), test)
 }
 
-// TestFiles tests specified Go+ files.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func TestFiles(files []string, conf *Config, test *gocmd.TestConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
diff --git a/cl/builtin.go b/cl/builtin.go
index 661635ad..3058e246 100644
--- a/cl/builtin.go
+++ b/cl/builtin.go
@@ -51,7 +51,6 @@ func initBuiltin(pkg *gox.Package, builtin *types.Package, os, fmt, ng, iox, bui
 		scope.Insert(types.NewTypeName(token.NoPos, builtin, "int128", ng.Ref("Int128").Type()))
 	}
 	if fmt != nil {
-		scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "echo", fmt.Ref("Println")))
 		initBuiltinFns(builtin, scope, fmt, []string{
 			"print", "println", "printf", "errorf",
 			"fprint", "fprintln", "fprintf",
diff --git a/cl/classfile.go b/cl/classfile.go
index d51e17d6..72777289 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -20,7 +20,6 @@ import (
 	goast "go/ast"
 	"go/constant"
 	"go/types"
-	"log"
 	"path/filepath"
 	"strings"
 
@@ -32,17 +31,10 @@ import (
 
 // -----------------------------------------------------------------------------
 
-type gmxClass struct {
-	tname string // class type
-	ext   string
-	proj  *gmxProject
-}
-
-type gmxProject struct {
-	gameClass  string             // <gmtype>.gmx
-	game       gox.Ref            // Game
-	sprite     map[string]gox.Ref // .spx => Sprite
-	sptypes    []string           // <sptype>.spx
+type gmxSettings struct {
+	gameClass  string
+	game       gox.Ref
+	sprite     map[string]gox.Ref
 	scheds     []string
 	schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload)
 	pkgImps    []*gox.PkgRef
@@ -51,7 +43,7 @@ type gmxProject struct {
 	gameIsPtr  bool
 }
 
-func (p *gmxProject) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
+func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
 	if p == nil || !p.hasScheds {
 		return nil
 	}
@@ -77,49 +69,36 @@ func ClassNameAndExt(file string) (name, ext string) {
 	return
 }
 
-func loadClass(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxProject {
+func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxSettings {
 	tname, ext := ClassNameAndExt(file)
 	gt, ok := conf.LookupClass(ext)
 	if !ok {
 		panic("TODO: class not found")
 	}
-	p, ok := ctx.projs[gt.Ext]
-	if !ok {
-		pkgPaths := gt.PkgPaths
-		p = &gmxProject{pkgPaths: pkgPaths}
-		ctx.projs[gt.Ext] = p
-
-		p.pkgImps = make([]*gox.PkgRef, len(pkgPaths))
-		for i, pkgPath := range pkgPaths {
-			p.pkgImps[i] = pkg.Import(pkgPath)
-		}
-		spx := p.pkgImps[0]
-		if gt.Class != "" {
-			p.game, p.gameIsPtr = spxRef(spx, gt.Class)
-		}
-		p.sprite = make(map[string]types.Object)
-		for _, v := range gt.Works {
-			obj, _ := spxRef(spx, v.Class)
-			p.sprite[v.Ext] = obj
-		}
-		if x := getStringConst(spx, "Gop_sched"); x != "" {
-			p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true
-		}
-	}
+	var name string
 	if f.IsProj {
-		if p.gameClass != "" {
-			panic("TODO: multiple project files found")
+		name = tname
+		if name == "main" {
+			name = gt.Class
 		}
-		if tname == "main" {
-			tname = gt.Class
-		}
-		p.gameClass = tname
-	} else {
-		p.sptypes = append(p.sptypes, tname)
 	}
-	ctx.classes[f] = gmxClass{tname, ext, p}
-	if debugLoad {
-		log.Println("==> InitClass", tname, "isProj:", f.IsProj)
+	pkgPaths := gt.PkgPaths
+	p := &gmxSettings{gameClass: name, pkgPaths: pkgPaths}
+	p.pkgImps = make([]*gox.PkgRef, len(pkgPaths))
+	for i, pkgPath := range pkgPaths {
+		p.pkgImps[i] = pkg.Import(pkgPath)
+	}
+	spx := p.pkgImps[0]
+	if gt.Class != "" {
+		p.game, p.gameIsPtr = spxRef(spx, gt.Class)
+	}
+	p.sprite = make(map[string]types.Object)
+	for _, v := range gt.Works {
+		obj, _ := spxRef(spx, v.Class)
+		p.sprite[v.Ext] = obj
+	}
+	if x := getStringConst(spx, "Gop_sched"); x != "" {
+		p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true
 	}
 	return p
 }
@@ -172,8 +151,8 @@ func getFields(f *ast.File) []ast.Spec {
 }
 
 func setBodyHandler(ctx *blockCtx) {
-	if proj := ctx.proj; proj != nil { // in a Go+ class file
-		if scheds := proj.getScheds(ctx.cb); scheds != nil {
+	if ctx.isClass { // in a Go+ class file
+		if scheds := ctx.getScheds(ctx.cb); scheds != nil {
 			ctx.cb.SetBodyHandler(func(body *goast.BlockStmt, kind int) {
 				idx := 0
 				if len(body.List) == 0 {
@@ -185,47 +164,14 @@ func setBodyHandler(ctx *blockCtx) {
 	}
 }
 
-func gmxMainFunc(p *gox.Package, ctx *pkgCtx) func() {
-	if len(ctx.projs) == 1 { // only one project file
-		var proj *gmxProject
-		for _, v := range ctx.projs {
-			proj = v
-			break
-		}
-		scope := p.Types.Scope()
-		if o := scope.Lookup(proj.gameClass); o != nil && hasMethod(o, "MainEntry") {
-			// new(Game).Main()
-			// new(Game).Main(workers...)
-			fn := p.NewFunc(nil, "main", nil, nil, false)
-			return func() {
-				new := p.Builtin().Ref("new")
-				cb := fn.BodyStart(p).Val(new).Val(o).Call(1).MemberVal("Main")
-
-				sig := cb.Get(-1).Type.(*types.Signature)
-				narg := gmxMainNarg(sig)
-				if narg > 0 {
-					narg = len(proj.sptypes)
-					for _, spt := range proj.sptypes {
-						sp := scope.Lookup(spt)
-						cb.Val(new).Val(sp).Call(1)
-					}
-				}
-
-				cb.Call(narg).EndStmt().End()
-			}
-		}
-	}
-	return nil
-}
-
-func gmxMainNarg(sig *types.Signature) int {
-	if fex, ok := gox.CheckFuncEx(sig); ok {
-		if trm, ok := fex.(*gox.TyTemplateRecvMethod); ok {
-			sig = trm.Func.Type().(*types.Signature)
-			return sig.Params().Len() - 1
-		}
+func gmxMainFunc(p *gox.Package, ctx *pkgCtx) {
+	if o := p.Types.Scope().Lookup(ctx.gameClass); o != nil && hasMethod(o, "MainEntry") {
+		// new(Game).Main()
+		p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).
+			Val(p.Builtin().Ref("new")).Val(o).Call(1).
+			MemberVal("Main").Call(0).EndStmt().
+			End()
 	}
-	return sig.Params().Len()
 }
 
 // -----------------------------------------------------------------------------
diff --git a/cl/compile.go b/cl/compile.go
index 54a9c21c..a242bcaf 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -41,7 +41,6 @@ type dbgFlags int
 const (
 	DbgFlagLoad dbgFlags = 1 << iota
 	DbgFlagLookup
-	FlagNoMarkAutogen
 	DbgFlagAll = DbgFlagLoad | DbgFlagLookup
 )
 
@@ -50,9 +49,8 @@ var (
 )
 
 var (
-	debugLoad     bool
-	debugLookup   bool
-	noMarkAutogen bool // add const _ = true
+	debugLoad   bool
+	debugLookup bool
 )
 
 func SetDisableRecover(disableRecover bool) {
@@ -62,7 +60,6 @@ func SetDisableRecover(disableRecover bool) {
 func SetDebug(flags dbgFlags) {
 	debugLoad = (flags & DbgFlagLoad) != 0
 	debugLookup = (flags & DbgFlagLookup) != 0
-	noMarkAutogen = (flags & FlagNoMarkAutogen) != 0
 }
 
 // -----------------------------------------------------------------------------
@@ -175,9 +172,6 @@ type Config struct {
 	// Fset provides source position information for syntax trees and types (required).
 	Fset *token.FileSet
 
-	// Context represents all things between packages (optional).
-	Context *gox.Context
-
 	// RelativeBase is the root directory of relative path.
 	RelativeBase string
 
@@ -336,14 +330,13 @@ func doInitMethods(ld *typeLoader) {
 
 type pkgCtx struct {
 	*nodeInterp
-	projs   map[string]*gmxProject // .gmx => project
-	classes map[*ast.File]gmxClass
-	fset    *token.FileSet
-	cpkgs   *cpackages.Importer
-	syms    map[string]loader
-	inits   []func()
-	tylds   []*typeLoader
-	errs    errors.List
+	*gmxSettings
+	fset  *token.FileSet
+	cpkgs *cpackages.Importer
+	syms  map[string]loader
+	inits []func()
+	tylds []*typeLoader
+	errs  errors.List
 
 	generics map[string]bool // generic type record
 	idents   []*ast.Ident    // toType ident recored
@@ -357,7 +350,6 @@ type pkgImp struct {
 
 type blockCtx struct {
 	*pkgCtx
-	proj       *gmxProject
 	pkg        *gox.Package
 	cb         *gox.CodeBuilder
 	imports    map[string]pkgImp
@@ -472,17 +464,12 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		fset: fset, files: files, relBaseDir: relBaseDir,
 	}
 	ctx := &pkgCtx{
-		fset:       fset,
-		nodeInterp: interp,
-		projs:      make(map[string]*gmxProject),
-		classes:    make(map[*ast.File]gmxClass),
-		syms:       make(map[string]loader),
-		generics:   make(map[string]bool),
+		fset: fset,
+		syms: make(map[string]loader), nodeInterp: interp, generics: make(map[string]bool),
 	}
 	confGox := &gox.Config{
 		Types:           conf.Types,
 		Fset:            fset,
-		Context:         conf.Context,
 		Importer:        conf.Importer,
 		LoadNamed:       ctx.loadNamed,
 		HandleErr:       ctx.handleErr,
@@ -507,24 +494,37 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		}()
 	}
 	p = gox.NewPackage(pkgPath, pkg.Name, confGox)
-
-	if !noMarkAutogen {
-		p.CB().NewConstStart(nil, "_").Val(true).EndInit(1)
-	}
-
 	ctx.cpkgs = cpackages.NewImporter(&cpackages.Config{
 		Pkg: p, LookupPub: conf.LookupPub,
 	})
-
 	for file, gmx := range files {
-		if gmx.IsClass && !gmx.IsNormalGox {
-			if debugLoad {
-				log.Println("==> File", file, "normalGox:", gmx.IsNormalGox)
+		if gmx.IsProj {
+			ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf)
+			break
+		}
+	}
+	if ctx.gmxSettings == nil {
+		for file, gmx := range files {
+			if gmx.IsClass && !gmx.IsNormalGox {
+				ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf)
+				break
 			}
-			loadClass(ctx, p, file, gmx, conf)
 		}
 	}
 
+	gofiles := make([]*ast.File, 0, len(pkg.GoFiles))
+	for fpath, gof := range pkg.GoFiles {
+		f := fromgo.ASTFile(gof, 0)
+		gofiles = append(gofiles, f)
+		ctx := &blockCtx{
+			pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir,
+			imports: make(map[string]pkgImp),
+		}
+		preloadFile(p, ctx, fpath, f, false, false)
+	}
+
+	initGopPkg(ctx, p)
+
 	for fpath, f := range files {
 		fileLine := !conf.NoFileLine
 		fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), fpath)
@@ -539,24 +539,6 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		preloadGopFile(p, ctx, fpath, f, conf)
 	}
 
-	gopSyms := make(map[string]bool) // TODO: remove this map
-	for name := range ctx.syms {
-		gopSyms[name] = true
-	}
-
-	gofiles := make([]*ast.File, 0, len(pkg.GoFiles))
-	for fpath, gof := range pkg.GoFiles {
-		f := fromgo.ASTFile(gof, 0)
-		gofiles = append(gofiles, f)
-		ctx := &blockCtx{
-			pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir,
-			imports: make(map[string]pkgImp),
-		}
-		preloadFile(p, ctx, fpath, f, false, false)
-	}
-
-	initGopPkg(ctx, p, gopSyms)
-
 	// sort files
 	type File struct {
 		*ast.File
@@ -570,25 +552,15 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		return sfiles[i].path < sfiles[j].path
 	})
 
-	// genMain = true if it is main package and no main func
-	var genMain bool
-	var gen func()
-	if pkg.Name == "main" {
-		_, hasMain := ctx.syms["main"]
-		genMain = !hasMain
-	}
-
 	for _, f := range sfiles {
 		if f.IsProj {
 			loadFile(ctx, f.File)
+			gmxMainFunc(p, ctx)
+			break
 		}
 	}
-	if genMain { // make classfile main func if need
-		gen = gmxMainFunc(p, ctx)
-	}
-
 	for _, f := range sfiles {
-		if !f.IsProj {
+		if !f.IsProj { // only one .gmx file
 			loadFile(ctx, f.File)
 		}
 	}
@@ -605,12 +577,12 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	}
 	err = ctx.complete()
 
-	if gen != nil { // generate classfile main func
-		gen()
-	} else if genMain && !conf.NoAutoGenMain { // generate empty main func
-		old, _ := p.SetCurFile(defaultGoFile, false)
-		p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End()
-		p.RestoreCurFile(old)
+	if !conf.NoAutoGenMain && pkg.Name == "main" {
+		if obj := p.Types.Scope().Lookup("main"); obj == nil {
+			old, _ := p.SetCurFile(defaultGoFile, false)
+			p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End()
+			p.RestoreCurFile(old)
+		}
 	}
 	return
 }
@@ -627,11 +599,8 @@ func isOverloadFunc(name string) bool {
 //go:linkname initThisGopPkg github.com/goplus/gox.initThisGopPkg
 func initThisGopPkg(pkg *types.Package)
 
-func initGopPkg(ctx *pkgCtx, pkg *gox.Package, gopSyms map[string]bool) {
+func initGopPkg(ctx *pkgCtx, pkg *gox.Package) {
 	for name, f := range ctx.syms {
-		if gopSyms[name] {
-			continue
-		}
 		if _, ok := f.(*typeLoader); ok {
 			ctx.loadType(name)
 		} else if isOverloadFunc(name) {
@@ -712,27 +681,25 @@ func genGoFile(file string, gopFile bool) string {
 }
 
 func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, conf *Config) {
-	var proj *gmxProject
+	var parent = ctx.pkgCtx
 	var classType string
 	var baseTypeName string
 	var baseType types.Type
 	var spxClass bool
-	var parent = ctx.pkgCtx
-	if f.IsClass {
-		if f.IsNormalGox {
-			classType, _ = ClassNameAndExt(file)
-		} else {
-			c := parent.classes[f]
-			classType, proj = c.tname, c.proj
-			ctx.proj = proj
-			if f.IsProj {
-				o := proj.game
-				baseTypeName, baseType = o.Name(), o.Type()
-				if proj.gameIsPtr {
-					baseType = types.NewPointer(baseType)
-				}
-			} else {
-				o := proj.sprite[c.ext]
+	switch {
+	case f.IsProj:
+		classType = parent.gameClass
+		o := parent.game
+		baseTypeName, baseType = o.Name(), o.Type()
+		if parent.gameIsPtr {
+			baseType = types.NewPointer(baseType)
+		}
+	case f.IsClass:
+		var classExt string
+		classType, classExt = ClassNameAndExt(file)
+		if parent.gmxSettings != nil {
+			o, ok := parent.sprite[classExt]
+			if ok {
 				baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
 			}
 		}
@@ -741,9 +708,9 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 		if debugLoad {
 			log.Println("==> Preload type", classType)
 		}
-		if proj != nil {
-			ctx.lookups = make([]*gox.PkgRef, len(proj.pkgPaths))
-			for i, pkgPath := range proj.pkgPaths {
+		if parent.gmxSettings != nil {
+			ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths))
+			for i, pkgPath := range parent.pkgPaths {
 				ctx.lookups[i] = p.Import(pkgPath)
 			}
 		}
@@ -764,13 +731,13 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 				var flds []*types.Var
 				var tags []string
 				chk := newCheckRedecl()
-				if baseTypeName != "" {
+				if len(baseTypeName) != 0 {
 					flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true))
 					tags = append(tags, "")
 					chk.chkRedecl(ctx, baseTypeName, pos)
 				}
-				if spxClass && proj.gameClass != "" {
-					typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: proj.gameClass}})
+				if spxClass && parent.gmxSettings != nil && parent.gameClass != "" {
+					typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: parent.gameClass}})
 					name := getTypeName(typ)
 					if !chk.chkRedecl(ctx, name, pos) {
 						fld := types.NewField(pos, pkg, name, typ, true)
@@ -822,7 +789,7 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 		}}}
 	}
 	// check class project no MainEntry and auto added
-	if f.IsProj && !conf.NoAutoGenMain && !f.HasShadowEntry() && f.Name.Name == "main" {
+	if f.IsProj && !conf.NoAutoGenMain && !f.NoEntrypoint() && f.Name.Name == "main" {
 		entry := getEntrypoint(f)
 		var hasEntry bool
 		for _, decl := range f.Decls {
diff --git a/cl/expr.go b/cl/expr.go
index e014d1ce..15356c2e 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -778,46 +778,7 @@ func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) {
 		}
 		cb.Val(rune(0)).ArrayLit(typ, n+1).UnaryOp(gotoken.AND).Call(1).Call(1)
 	default:
-		if v.Extra == nil {
-			basicLit(cb, v)
-			return
-		}
-		compileStringLitEx(ctx, cb, v)
-	}
-}
-
-func basicLit(cb *gox.CodeBuilder, v *ast.BasicLit) {
-	cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v)
-}
-
-func compileStringLitEx(ctx *blockCtx, cb *gox.CodeBuilder, lit *ast.BasicLit) {
-	pos := lit.ValuePos + 1
-	quote := lit.Value[:1]
-	notFirst := false
-	for _, part := range lit.Extra.Parts {
-		switch v := part.(type) {
-		case string: // normal string literal or end with "$$"
-			next := pos + token.Pos(len(v))
-			if strings.HasSuffix(v, "$$") {
-				v = v[:len(v)-1]
-			}
-			basicLit(cb, &ast.BasicLit{ValuePos: pos - 1, Value: quote + v + quote, Kind: token.STRING})
-			pos = next
-		case ast.Expr:
-			compileExpr(ctx, v)
-			t := cb.Get(-1).Type
-			if t.Underlying() != types.Typ[types.String] {
-				cb.Member("string", gox.MemberFlagAutoProperty)
-			}
-			pos = v.End()
-		default:
-			panic("compileStringLitEx TODO: unexpected part")
-		}
-		if notFirst {
-			cb.BinaryOp(gotoken.ADD)
-		} else {
-			notFirst = true
-		}
+		cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v)
 	}
 }
 
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 98d5e42a..6d195d2a 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -438,26 +438,28 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type {
 	return intf
 }
 
-func instantiate(ctx *blockCtx, exprX ast.Expr, indices ...ast.Expr) types.Type {
+func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type {
 	ctx.inInst++
 	defer func() {
 		ctx.inInst--
 	}()
-
-	x := toType(ctx, exprX)
-	idx := make([]types.Type, len(indices))
-	for i, index := range indices {
-		idx[i] = toType(ctx, index)
-	}
-	return ctx.pkg.Instantiate(x, idx, exprX)
-}
-
-func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type {
-	return instantiate(ctx, v.X, v.Index)
+	ctx.cb.Typ(toType(ctx, v.X), v.X)
+	ctx.cb.Typ(toType(ctx, v.Index), v.Index)
+	ctx.cb.Index(1, false, v)
+	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
 }
 
 func toIndexListType(ctx *blockCtx, v *ast.IndexListExpr) types.Type {
-	return instantiate(ctx, v.X, v.Indices...)
+	ctx.inInst++
+	defer func() {
+		ctx.inInst--
+	}()
+	ctx.cb.Typ(toType(ctx, v.X), v.X)
+	for _, index := range v.Indices {
+		ctx.cb.Typ(toType(ctx, index), index)
+	}
+	ctx.cb.Index(len(v.Indices), false, v)
+	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
 }
 
 // -----------------------------------------------------------------------------
diff --git a/cl/internal/spx3/spx3.go b/cl/internal/spx3/spx3.go
deleted file mode 100644
index 6fe29474..00000000
--- a/cl/internal/spx3/spx3.go
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package spx3
-
-const (
-	GopPackage = true
-)
-
-type Game struct {
-}
-
-func (p *Game) initGame() {}
-
-func (p *Game) Run() {}
-
-type Sprite struct {
-}
-
-func (p *Sprite) initSprite() {}
-
-func Gopt_Game_Main(game interface{ initGame() }, workers ...interface{ initSprite() }) {
-}
diff --git a/doc/docs.md b/doc/docs.md
index d10ae9fc..f2434cf9 100644
--- a/doc/docs.md
+++ b/doc/docs.md
@@ -266,7 +266,7 @@ s += "world"
 println s // Hello world
 ```
 
-Most Go+ operators must have values of the same type on both sides. You cannot concatenate an
+All operators in Go+ must have values of the same type on both sides. You cannot concatenate an
 integer to a string:
 
 ```go failcompile
@@ -281,24 +281,6 @@ age := 10
 println "age = " + age.string
 ```
 
-However, you can replace `age.string` to `"${age}"`:
-
-```go
-age := 10
-println "age = ${age}"
-```
-
-Here is a more complex example of `${expr}`:
-
-```go
-host := "example.com"
-page := 0
-limit := 20
-println "https://${host}/items?page=${page+1}&limit=${limit}" // https://example.com/items?page=1&limit=20
-println "$$" // $
-```
-
-
 <h5 align="right"><a href="#table-of-contents">⬆ back to toc</a></h5>
 
 
diff --git a/gengo.go b/gengo.go
index 967bc478..4e0103f8 100644
--- a/gengo.go
+++ b/gengo.go
@@ -24,7 +24,6 @@ import (
 	"strings"
 	"syscall"
 
-	"github.com/goplus/gox"
 	"github.com/goplus/mod/gopmod"
 	"github.com/goplus/mod/modcache"
 	"github.com/goplus/mod/modfetch"
@@ -49,14 +48,10 @@ const (
 
 // -----------------------------------------------------------------------------
 
-// GenGo generates gop_autogen.go for a Go+ package directory.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGo(dir string, conf *Config, genTestPkg bool) (string, bool, error) {
 	return GenGoEx(dir, conf, genTestPkg, 0)
 }
 
-// GenGoEx generates gop_autogen.go for a Go+ package directory.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string, bool, error) {
 	recursively := strings.HasSuffix(dir, "/...")
 	if recursively {
@@ -66,12 +61,6 @@ func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string,
 }
 
 func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenFlags) (err error) {
-	if conf == nil {
-		conf = new(Config)
-	}
-	if false && conf.Context == nil { // set Context
-		conf.Context = gox.NewContext()
-	}
 	if recursively {
 		var (
 			list errors.List
@@ -212,8 +201,6 @@ const (
 	modReadonly = 0555
 )
 
-// GenGoPkgPath generates gop_autogen.go for a Go+ package.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, recursively bool, err error) {
 	return GenGoPkgPathEx(workDir, pkgPath, conf, allowExtern, 0)
 }
@@ -231,8 +218,6 @@ func remotePkgPath(pkgPath string, conf *Config, recursively bool, flags GenFlag
 	return
 }
 
-// GenGoPkgPathEx generates gop_autogen.go for a Go+ package.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoPkgPathEx(workDir, pkgPath string, conf *Config, allowExtern bool, flags GenFlags) (localDir string, recursively bool, err error) {
 	recursively = strings.HasSuffix(pkgPath, "/...")
 	if recursively {
@@ -274,15 +259,7 @@ func remotePkgPathDo(pkgPath string, doSth func(pkgDir, modDir string), onErr fu
 
 // -----------------------------------------------------------------------------
 
-// GenGoFiles generates gop_autogen.go for specified Go+ files.
-// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoFiles(autogen string, files []string, conf *Config) (result []string, err error) {
-	if conf == nil {
-		conf = new(Config)
-	}
-	if conf.Context == nil { // set Context
-		conf.Context = gox.NewContext()
-	}
 	if autogen == "" {
 		autogen = "gop_autogen.go"
 		if len(files) == 1 {
diff --git a/go.mod b/go.mod
index c59fbc53..aa02884a 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,8 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.20-0.20240115161159-762f74054b17
-	github.com/goplus/gox v1.13.1-0.20240121122555-748a917ccef7
+	github.com/goplus/c2go v0.7.19
+	github.com/goplus/gox v1.13.2
 	github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51
 	github.com/qiniu/x v1.13.2
 	golang.org/x/tools v0.17.0
@@ -16,7 +16,7 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	golang.org/x/mod v0.14.0 // indirect
-	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/sys v0.15.0 // indirect
 )
 
 retract v1.1.12
diff --git a/go.sum b/go.sum
index 2ecf5fc2..b091087c 100644
--- a/go.sum
+++ b/go.sum
@@ -4,11 +4,11 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.20-0.20240115161159-762f74054b17 h1:2+AWW9KjzTwaM8pfvRZqhI3PCXs7cMxzsn98AqDpgH0=
-github.com/goplus/c2go v0.7.20-0.20240115161159-762f74054b17/go.mod h1:WFbERV86IVV4wJ2/mLa8756Fy6iSXtlwGQbEqkYoIR4=
-github.com/goplus/gox v1.13.1-0.20240115155941-e657d899cb2e/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
-github.com/goplus/gox v1.13.1-0.20240121122555-748a917ccef7 h1:ld6FOGBInJLgnneCcrKoqDm2oMg+sf0kc9pjr770U6E=
-github.com/goplus/gox v1.13.1-0.20240121122555-748a917ccef7/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
+github.com/goplus/c2go v0.7.19 h1:pPMarGN1RiNJFAYlL8oZlM7VNXeg054B4czDgBlck0o=
+github.com/goplus/c2go v0.7.19/go.mod h1:EJgnt9CEHnXuWQc4y78w3DhKErL1aBzgE41NZaEa/3c=
+github.com/goplus/gox v1.13.2 h1:XZZtLcZasRVKOYNzTnJ9E2Cu350TWu9K+CYMJTItkis=
+github.com/goplus/gox v1.13.2/go.mod h1:iIchh0wp8Ye0DOPcFgHc5d0qlMOx8/OJ+DBQfe7hcTs=
+github.com/goplus/mod v0.12.0/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs=
 github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51 h1:SXuaj7lYEL2fAQTrKzbCAUuRre2s2nMfGEyBqbUWooc=
 github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -28,7 +28,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -40,13 +40,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
-golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -55,14 +55,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
-golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -75,6 +75,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
 golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
 golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/load.go b/load.go
index f5706c99..17990ce4 100644
--- a/load.go
+++ b/load.go
@@ -121,9 +121,6 @@ type Config struct {
 	Filter   func(fs.FileInfo) bool
 	Importer types.Importer
 
-	// Context represents all things between packages (optional).
-	Context *gox.Context
-
 	IgnoreNotatedError bool
 }
 
@@ -187,7 +184,6 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 	var pkgTest *ast.Package
 	var clConf = &cl.Config{
 		Fset:         fset,
-		Context:      conf.Context,
 		RelativeBase: relativeBaseOf(mod),
 		Importer:     imp,
 		LookupClass:  mod.LookupClass,
@@ -272,7 +268,6 @@ func LoadFiles(dir string, files []string, conf *Config) (out *gox.Package, err
 		}
 		clConf := &cl.Config{
 			Fset:         fset,
-			Context:      conf.Context,
 			RelativeBase: relativeBaseOf(mod),
 			Importer:     imp,
 			LookupClass:  mod.LookupClass,
diff --git a/parser/interface.go b/parser/interface.go
index 1b61209b..d9cff8e0 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -20,7 +20,6 @@ package parser
 
 import (
 	goparser "go/parser"
-	"go/scanner"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
@@ -120,7 +119,7 @@ func parseFile(fset *token.FileSet, filename string, src interface{}, mode Mode)
 
 // ParseExprFrom is a convenience function for parsing an expression.
 // The arguments have the same meaning as for ParseFile, but the source must
-// be a valid Go/Go+ (type or value) expression. Specifically, fset must not
+// be a valid Go (type or value) expression. Specifically, fset must not
 // be nil.
 //
 // If the source couldn't be read, the returned AST is nil and the error
@@ -161,42 +160,6 @@ func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ex
 	return
 }
 
-// parseExprEx is a convenience function for parsing an expression.
-// The arguments have the same meaning as for ParseFile, but the source must
-// be a valid Go/Go+ (type or value) expression. Specifically, fset must not
-// be nil.
-//
-// If the source couldn't be read, the returned AST is nil and the error
-// indicates the specific failure. If the source was read but syntax
-// errors were found, the result is a partial AST (with ast.Bad* nodes
-// representing the fragments of erroneous source code). Multiple errors
-// are returned via a scanner.ErrorList which is sorted by source position.
-func parseExprEx(file *token.File, src []byte, offset int, mode Mode) (expr ast.Expr, err scanner.ErrorList) {
-	var p parser
-	defer func() {
-		if e := recover(); e != nil {
-			// resume same panic if it's not a bailout
-			if _, ok := e.(bailout); !ok {
-				panic(e)
-			}
-		}
-		err = p.errors
-	}()
-
-	// parse expr
-	p.initSub(file, src, offset, mode)
-	expr = p.parseRHS()
-
-	// If a semicolon was inserted, consume it;
-	// report an error if there's more tokens.
-	if p.tok == token.SEMICOLON && p.lit == "\n" {
-		p.next()
-	}
-	p.expect(token.EOF)
-
-	return
-}
-
 // ParseExpr is a convenience function for obtaining the AST of an expression x.
 // The position information recorded in the AST is undefined. The filename used
 // in error messages is the empty string.
diff --git a/parser/parser.go b/parser/parser.go
index 83d27eb5..ef1d8a64 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -109,16 +109,6 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod
 	p.next()
 }
 
-func (p *parser) initSub(file *token.File, src []byte, offset int, mode Mode) {
-	p.file = file
-	eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) }
-	p.scanner.InitEx(p.file, src, offset, eh, 0)
-
-	p.mode = mode
-	p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
-	p.next()
-}
-
 // ----------------------------------------------------------------------------
 // Scoping support
 
@@ -1600,90 +1590,6 @@ func (p *parser) parseFuncTypeOrLit() ast.Expr {
 	return &ast.FuncLit{Type: typ, Body: body}
 }
 
-func (p *parser) stringLit(pos token.Pos, val string) *ast.StringLitEx {
-	parts := p.stringLitEx(nil, pos+1, val[1:len(val)-1])
-	if parts != nil {
-		return &ast.StringLitEx{Parts: parts}
-	}
-	return nil
-}
-
-func (p *parser) stringLitEx(parts []any, pos token.Pos, text string) []any {
-	extra := false
-loop:
-	at := strings.IndexByte(text, '$')
-	if at < 0 || at+1 == len(text) { // no '$' or end with '$'
-		if extra {
-			goto normal
-		}
-		return nil
-	}
-	switch text[at+1] {
-	case '{': // ${
-		from := at + 2
-		left := text[from:]
-		if left == "" { // "...${" (string end with "${")
-			goto normal
-		}
-		end := strings.IndexByte(left, '}')
-		if end < 0 {
-			p.error(pos+token.Pos(at+1), "invalid $ expression: ${ doesn't end with }")
-			goto normal
-		}
-		if at != 0 {
-			parts = append(parts, text[:at])
-		}
-		to := pos + token.Pos(from+end)
-		parts = p.stringLitExpr(parts, pos+token.Pos(from), to)
-		pos = to + 1
-		text = left[end+1:]
-	case '$': // $$
-		parts = append(parts, text[:at+2])
-		pos += token.Pos(at + 2)
-		text = text[at+2:]
-	default:
-		if extra || hasExtra(text[at+1:]) {
-			p.error(pos+token.Pos(at), "invalid $ expression: neither `${ ... }` nor `$$`")
-		}
-		return nil
-	}
-	if text != "" {
-		extra = true
-		goto loop
-	}
-	return parts
-normal:
-	parts = append(parts, text)
-	return parts
-}
-
-func hasExtra(text string) bool {
-	for {
-		at := strings.IndexByte(text, '$')
-		if at < 0 || at+1 == len(text) { // no '$' or end with '$'
-			return false
-		}
-		ch := text[at+1]
-		if ch == '{' || ch == '$' {
-			return true
-		}
-		text = text[at+2:]
-	}
-}
-
-func (p *parser) stringLitExpr(parts []any, off, end token.Pos) []any {
-	file := p.file
-	base := file.Base()
-	src := p.scanner.CodeTo(int(end) - base)
-	expr, err := parseExprEx(p.file, src, int(off)-base, 0)
-	if err != nil {
-		p.errors = append(p.errors, err...)
-		expr = &ast.BadExpr{From: off, To: end}
-	}
-	parts = append(parts, expr)
-	return parts
-}
-
 // parseOperand may return an expression or a raw type (incl. array
 // types of the form [...]T. Callers must verify the result.
 // If lhs is set and the result is an identifier, it is not resolved.
@@ -1701,11 +1607,7 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 		return
 
 	case token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
-		bl := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
-		if p.tok == token.STRING {
-			bl.Extra = p.stringLit(p.pos, p.lit)
-		}
-		x = bl
+		x = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
 		if debugParseOutput {
 			log.Printf("ast.BasicLit{Kind: %v, Value: %v}\n", p.tok, p.lit)
 		}
@@ -3584,65 +3486,9 @@ func isOverloadOps(tok token.Token) bool {
 	return int(tok) < len(overloadOps) && overloadOps[tok] != 0
 }
 
-// `funcName`
-// `(*T).methodName`
-// `func(params) results {...}`
-func (p *parser) parseOverloadFunc() (ast.Expr, bool) {
-	switch p.tok {
-	case token.IDENT:
-		return p.parseIdent(), true
-	case token.FUNC:
-		return p.parseFuncTypeOrLit(), true
-	case token.LPAREN:
-		x, _ := p.parsePrimaryExpr(false, false, false)
-		return x, true
-	}
-	return nil, false
-}
-
-// `= (overloadFuncs)`
-//
-// here overloadFunc represents
-//
-// `funcName`
-// `(*T).methodName`
-// `func(params) results {...}`
-func (p *parser) parseOverloadDecl(decl *ast.OverloadFuncDecl) *ast.OverloadFuncDecl {
-	decl.Assign = p.expect(token.ASSIGN)
-	decl.Lparen = p.expect(token.LPAREN)
-	funcs := make([]ast.Expr, 0, 4)
-	for {
-		f, ok := p.parseOverloadFunc()
-		if !ok {
-			break
-		}
-		funcs = append(funcs, f)
-		if p.tok == token.SEMICOLON {
-			p.next()
-		}
-	}
-	decl.Funcs = funcs
-	decl.Rparen = p.expect(token.RPAREN)
-	p.expectSemi()
-	if debugParseOutput {
-		var recvt ast.Expr
-		if recv := decl.Recv; recv != nil {
-			recvt = recv.List[0].Type
-		}
-		log.Printf("ast.OverloadFuncDecl{Recv: %v, Name: %v, ...}\n", recvt, decl.Name)
-	}
-	return decl
-}
-
-// `func identOrOp(params) results {...}`
-// `func identOrOp = (overloadFuncs)`
-//
-// `func (recv) identOrOp(params) results { ... }`
-// `func (T).identOrOp = (overloadFuncs)`
-// `func (params) results { ... }()`
-func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
+func (p *parser) parseFuncDeclOrCall() (*ast.FuncDecl, *ast.CallExpr) {
 	if p.trace {
-		defer un(trace(p, "FunctionDeclOrCall"))
+		defer un(trace(p, "FunctionDecl"))
 	}
 
 	doc := p.leadComment
@@ -3653,39 +3499,16 @@ func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 	var ident *ast.Ident
 	var isOp, isFunLit, ok bool
 
-	if p.tok != token.LPAREN {
-		// func: `func identOrOp(...) results`
-		// overload: `func identOrOp = (overloadFuncs)`
+	if p.tok != token.LPAREN { // func identOrOp(...)
 		ident, isOp = p.parseIdentOrOp()
-		if p.tok == token.ASSIGN {
-			// func identOrOp = (overloadFuncs)
-			return p.parseOverloadDecl(&ast.OverloadFuncDecl{
-				Doc:      doc,
-				Func:     pos,
-				Name:     ident,
-				Operator: isOp,
-			}), nil
-		}
 		params, results = p.parseSignature(scope)
 	} else {
-		// method: `func (recv) identOrOp(params) results { ... }`
-		// overload: `func (T).identOrOp = (overloadFuncs)`
-		// funlit: `func (params) results { ... }()`
+		// method: func (recv) XXX(params) results { ... }
+		// funlit: func (params) results { ... }()
 		params = p.parseParameters(scope, true)
 		if p.tok == token.LPAREN {
 			// func (params) (results) { ... }()
 			isFunLit, results = true, p.parseParameters(scope, false)
-		} else if p.tok == token.PERIOD {
-			p.next()
-			// func (T).identOrOp = (overloadFuncs)
-			ident, isOp = p.parseIdentOrOp()
-			return p.parseOverloadDecl(&ast.OverloadFuncDecl{
-				Doc:      doc,
-				Func:     pos,
-				Recv:     params,
-				Name:     ident,
-				Operator: isOp,
-			}), nil
 		} else if isOp = isOverloadOps(p.tok); isOp {
 			oldtok, oldpos := p.tok, p.pos
 			p.next()
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index d3fe3e99..d2befd9d 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -173,19 +173,13 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk
 				} else {
 					first = err
 				}
-			} else {
-				f, err := ParseFSFile(fset, fs, filename, nil, mode)
-				if f != nil {
-					f.IsProj, f.IsClass = isProj, isClass
-					f.IsNormalGox = isNormalGox
-					if f.Name != nil {
-						pkg := reqPkg(pkgs, f.Name.Name)
-						pkg.Files[filename] = f
-					}
-				}
-				if err != nil && first == nil {
-					first = err
-				}
+			} else if f, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
+				f.IsProj, f.IsClass = isProj, isClass
+				f.IsNormalGox = isNormalGox
+				pkg := reqPkg(pkgs, f.Name.Name)
+				pkg.Files[filename] = f
+			} else if first == nil {
+				first = err
 			}
 		}
 	}
@@ -220,7 +214,7 @@ func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src inter
 		mode |= ParseGoPlusClass
 	}
 	f, err = ParseFSFile(fset, fs, filename, src, mode)
-	if f != nil {
+	if err == nil {
 		f.IsProj, f.IsClass = isProj, isClass
 		f.IsNormalGox = isNormalGox
 	}
diff --git a/printer/nodes.go b/printer/nodes.go
index 4bbf900a..0d732502 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -1705,9 +1705,7 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) {
 		p.setComment(s.Doc)
 		p.identList(s.Names, doIndent) // always present
 		if s.Type != nil {
-			if len(s.Names) > 0 {
-				p.print(blank)
-			}
+			p.print(blank)
 			p.expr(s.Type)
 		}
 		if s.Values != nil {
@@ -1994,28 +1992,6 @@ func (p *printer) funcDecl(d *ast.FuncDecl) {
 	p.funcBody(p.distanceFrom(d.Pos(), startCol), vtab, d.Body)
 }
 
-func (p *printer) overloadFuncDecl(d *ast.OverloadFuncDecl) {
-	if debugFormat {
-		log.Println("==> Format OverloadFunc", d.Name.Name)
-	}
-	p.setComment(d.Doc)
-
-	pos := d.Pos()
-	p.print(pos, token.FUNC, blank)
-	if d.Recv != nil && !d.IsClass {
-		p.parameters(d.Recv) // method: print receiver
-		p.print(token.PERIOD)
-	}
-	p.expr(d.Name)
-	p.print(blank, token.ASSIGN, blank, token.LPAREN, newline)
-	for _, fn := range d.Funcs {
-		p.print(indent)
-		p.expr1(fn, token.LowestPrec, 1)
-		p.print(unindent, newline)
-	}
-	p.print(token.RPAREN)
-}
-
 func (p *printer) decl(decl ast.Decl) {
 	switch d := decl.(type) {
 	case *ast.BadDecl:
@@ -2024,8 +2000,6 @@ func (p *printer) decl(decl ast.Decl) {
 		p.genDecl(d)
 	case *ast.FuncDecl:
 		p.funcDecl(d)
-	case *ast.OverloadFuncDecl:
-		p.overloadFuncDecl(d)
 	default:
 		panic("unreachable")
 	}
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 08189102..5143470b 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -17,6 +17,7 @@
 // Package scanner implements a scanner for Go+ source text.
 // It takes a []byte as source which can then be tokenized
 // through repeated calls to the Scan method.
+//
 package scanner
 
 import (
@@ -35,11 +36,13 @@ import (
 // encountered and a handler was installed, the handler is called with a
 // position and an error message. The position points to the beginning of
 // the offending token.
+//
 type ErrorHandler = scanner.ErrorHandler
 
 // A Scanner holds the scanner's internal state while processing
 // a given text. It can be allocated as part of another data
 // structure but must be initialized via Init before use.
+//
 type Scanner struct {
 	// immutable state
 	file *token.File  // source file handle
@@ -63,6 +66,7 @@ const bom = 0xFEFF // byte order mark, only permitted as very first character
 
 // Read the next Unicode char into s.ch.
 // s.ch < 0 means end-of-file.
+//
 func (s *Scanner) next() {
 	if s.rdOffset < len(s.src) {
 		s.offset = s.rdOffset
@@ -106,6 +110,7 @@ func (s *Scanner) peek() byte {
 
 // A Mode value is a set of flags (or 0).
 // They control scanner behavior.
+//
 type Mode uint
 
 const (
@@ -128,16 +133,12 @@ const (
 //
 // Note that Init may call err if there is an error in the first character
 // of the file.
+//
 func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) {
 	// Explicitly initialize all fields since a scanner may be reused.
 	if file.Size() != len(src) {
 		panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src)))
 	}
-	s.InitEx(file, src, 0, err, mode)
-}
-
-// InitEx init the scanner with an offset (this means src[offset:] is all the code to scan).
-func (s *Scanner) InitEx(file *token.File, src []byte, offset int, err ErrorHandler, mode Mode) {
 	s.file = file
 	s.dir, _ = filepath.Split(file.Name())
 	s.src = src
@@ -146,7 +147,7 @@ func (s *Scanner) InitEx(file *token.File, src []byte, offset int, err ErrorHand
 
 	s.ch = ' '
 	s.offset = 0
-	s.rdOffset = offset
+	s.rdOffset = 0
 	s.lineOffset = 0
 	s.insertSemi = false
 	s.ErrorCount = 0
@@ -157,10 +158,6 @@ func (s *Scanner) InitEx(file *token.File, src []byte, offset int, err ErrorHand
 	}
 }
 
-func (s *Scanner) CodeTo(end int) []byte {
-	return s.src[:end]
-}
-
 func (s *Scanner) error(offs int, msg string) {
 	if s.err != nil {
 		s.err(s.file.Position(s.file.Pos(offs)), msg)
@@ -813,6 +810,7 @@ func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Tok
 // Scan adds line information to the file added to the file
 // set with Init. Token positions are relative to that file
 // and thus relative to the file set.
+//
 func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) {
 scanAgain:
 	s.skipWhitespace()
diff --git a/x/typesutil/check.go b/x/typesutil/check.go
index 0f4d10a0..dad82567 100644
--- a/x/typesutil/check.go
+++ b/x/typesutil/check.go
@@ -69,9 +69,6 @@ type Config struct {
 	// Fset provides source position information for syntax trees and types (required).
 	Fset *token.FileSet
 
-	// Context represents all things between packages (optional).
-	Context *gox.Context
-
 	// WorkingDir is the directory in which to run gop compiler (optional).
 	// If WorkingDir is not set, os.Getwd() is used.
 	WorkingDir string
@@ -147,7 +144,6 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error)
 	_, err = cl.NewPackage(pkgTypes.Path(), pkg, &cl.Config{
 		Types:          pkgTypes,
 		Fset:           fset,
-		Context:        opts.Context,
 		C2goBase:       opts.C2goBase,
 		LookupPub:      c2go.LookupPub(mod),
 		LookupClass:    mod.LookupClass,


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index b1b3298e..5df2153d 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,40 +39,6 @@ func getGoxConf() *gox.Config {
 	return &gox.Config{Fset: fset, Importer: imp}
 }
 
-func TestErrStringLit(t *testing.T) {
-	defer func() {
-		if e := recover(); e == nil {
-			t.Fatal("TestErrStringLit: no panic?")
-		}
-	}()
-	compileStringLitEx(nil, nil, &ast.BasicLit{
-		Value: "Hello",
-		Extra: &ast.StringLitEx{
-			Parts: []any{1},
-		},
-	})
-}
-
-func TestNodeInterp(t *testing.T) {
-	ni := &nodeInterp{}
-	if v := ni.Caller(&ast.Ident{}); v != "the function call" {
-		t.Fatal("TestNodeInterp:", v)
-	}
-}
-
-func TestMarkAutogen(t *testing.T) {
-	old := noMarkAutogen
-	noMarkAutogen = false
-
-	NewPackage("", &ast.Package{Files: map[string]*ast.File{
-		"main.t2gmx": {IsProj: true},
-	}}, &Config{
-		LookupClass: lookupClassErr,
-	})
-
-	noMarkAutogen = old
-}
-
 func TestClassNameAndExt(t *testing.T) {
 	name, ext := ClassNameAndExt("/foo/bar.abc_yap.gox")
 	if name != "bar" || ext != "_yap.gox" {
@@ -236,25 +202,20 @@ func isError(e interface{}, msg string) bool {
 	return false
 }
 
-func TestGmxProject(t *testing.T) {
+func TestGmxSettings(t *testing.T) {
 	pkg := gox.NewPackage("", "foo", goxConf)
-	ctx := &pkgCtx{
-		projs:   make(map[string]*gmxProject),
-		classes: make(map[*ast.File]gmxClass),
-	}
-	gmx := loadClass(ctx, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
+	gmx := newGmx(nil, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
 		LookupClass: lookupClass,
 	})
 	scheds := gmx.getScheds(pkg.CB())
 	if len(scheds) != 2 || scheds[0] == nil || scheds[0] != scheds[1] {
-		t.Fatal("TestGmxProject failed")
+		t.Fatal("TestGmxSettings failed")
 	}
 	gmx.hasScheds = false
 	if gmx.getScheds(nil) != nil {
-		t.Fatal("TestGmxProject failed: hasScheds?")
+		t.Fatal("TestGmxSettings failed: hasScheds?")
 	}
-
-	/* _, err := NewPackage("", &ast.Package{Files: map[string]*ast.File{
+	_, err := NewPackage("", &ast.Package{Files: map[string]*ast.File{
 		"main.t2gmx": {
 			IsProj: true,
 		},
@@ -263,28 +224,16 @@ func TestGmxProject(t *testing.T) {
 	})
 	if e := err.Error(); e != `github.com/goplus/gop/cl/internal/libc.Game not found` {
 		t.Fatal("newGmx:", e)
-	} */
+	}
 
-	func() {
-		defer func() {
-			if e := recover(); e != "TODO: class not found" {
-				t.Fatal("TestGmxProject failed:", e)
-			}
-		}()
-		loadClass(nil, pkg, "main.abcx", &ast.File{IsProj: true}, &Config{
-			LookupClass: lookupClass,
-		})
-	}()
-	func() {
-		defer func() {
-			if e := recover(); e != "TODO: multiple project files found" {
-				t.Fatal("TestGmxProject failed:", e)
-			}
-		}()
-		loadClass(ctx, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
-			LookupClass: lookupClass,
-		})
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("TestGmxSettings failed: no error?")
+		}
 	}()
+	newGmx(nil, pkg, "main.abcx", &ast.File{IsProj: true}, &Config{
+		LookupClass: lookupClass,
+	})
 }
 
 func TestSpxLookup(t *testing.T) {
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 8768d39e..314fdf2e 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -19,6 +19,7 @@ package cl_test
 import (
 	"bytes"
 	"os"
+	"path"
 	"testing"
 
 	"github.com/goplus/gop/cl"
@@ -46,11 +47,6 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) {
 			Works: []*modfile.Class{{Ext: "_t3spx.gox", Class: "Sprite"},
 				{Ext: ".t3spx2", Class: "Sprite2"}},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
-	case "_spx.gox":
-		return &modfile.Project{
-			Ext: "_spx.gox", Class: "Game",
-			Works:    []*modfile.Class{{Ext: "_spx.gox", Class: "Sprite"}},
-			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx3", "math"}}, true
 	}
 	return
 }
@@ -58,10 +54,7 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) {
 func spxParserConf() parser.Config {
 	return parser.Config{
 		ClassKind: func(fname string) (isProj bool, ok bool) {
-			ext := modfile.ClassExt(fname)
-			if ext == "_spx.gox" {
-				return fname == "main_spx.gox", true
-			}
+			ext := path.Ext(fname)
 			c, ok := lookupClass(ext)
 			if ok {
 				isProj = (c.Ext == ext)
@@ -376,43 +369,6 @@ func (this *Kai) Main() {
 `, "index.tgmx", "Kai.tspx")
 }
 
-func TestSpxRunWithWorkers(t *testing.T) {
-	gopSpxTestEx(t, `
-var (
-	Kai Kai
-)
-
-run
-`, `
-echo "Hi"
-`, `package main
-
-import (
-	"fmt"
-	"github.com/goplus/gop/cl/internal/spx3"
-)
-
-type Kai struct {
-	spx3.Sprite
-	*Game
-}
-type Game struct {
-	spx3.Game
-	Kai Kai
-}
-
-func (this *Game) MainEntry() {
-	this.Run()
-}
-func main() {
-	spx3.Gopt_Game_Main(new(Game), new(Kai))
-}
-func (this *Kai) Main() {
-	fmt.Println("Hi")
-}
-`, "main_spx.gox", "Kai_spx.gox")
-}
-
 func TestSpx2(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi")
@@ -445,9 +401,7 @@ type Kai struct {
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx")
-}
 
-func TestSpx3(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi, Sprite2")
 `, `
@@ -479,9 +433,7 @@ type Kai struct {
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx2")
-}
 
-func TestSpx4(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi, Sprite")
 `, `
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 3e157d12..8220c126 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -45,7 +45,7 @@ var (
 
 func init() {
 	gox.SetDebug(gox.DbgFlagAll)
-	cl.SetDebug(cl.DbgFlagAll | cl.FlagNoMarkAutogen)
+	cl.SetDebug(cl.DbgFlagAll)
 	gblFset = token.NewFileSet()
 	imp := gop.NewImporter(nil, &env.Gop{Root: gopRootDir, Version: "1.0"}, gblFset)
 	gblConf = &cl.Config{
@@ -122,34 +122,6 @@ func gopClTestFS(t *testing.T, conf *cl.Config, fs parser.FileSystem, pkgname, e
 	}
 }
 
-func TestStringLitBasic(t *testing.T) {
-	gopClTest(t, `echo "$$"`, `package main
-
-import "fmt"
-
-func main() {
-	fmt.Println("$")
-}
-`)
-}
-
-func TestStringLitVar(t *testing.T) {
-	gopClTest(t, `
-x := 1
-println "Hi, " + "a${x}b"`, `package main
-
-import (
-	"fmt"
-	"strconv"
-)
-
-func main() {
-	x := 1
-	fmt.Println("Hi, " + ("a" + strconv.Itoa(x) + "b"))
-}
-`)
-}
-
 func TestVargCommand(t *testing.T) {
 	gopClTest(t, `
 type foo int
diff --git a/parser/_testdata/goxtest1/bar.gox b/parser/_testdata/goxtest/bar.gox
similarity index 100%
rename from parser/_testdata/goxtest1/bar.gox
rename to parser/_testdata/goxtest/bar.gox
diff --git a/parser/_testdata/goxtest1/parser.expect b/parser/_testdata/goxtest/parser.expect
similarity index 100%
rename from parser/_testdata/goxtest1/parser.expect
rename to parser/_testdata/goxtest/parser.expect
diff --git a/parser/_testdata/goxtest2/bar.gox b/parser/_testdata/goxtest2/bar.gox
deleted file mode 100644
index 954f1881..00000000
--- a/parser/_testdata/goxtest2/bar.gox
+++ /dev/null
@@ -1,3 +0,0 @@
-var (
-	x.App
-)
diff --git a/parser/_testdata/goxtest2/parser.expect b/parser/_testdata/goxtest2/parser.expect
deleted file mode 100644
index 43479424..00000000
--- a/parser/_testdata/goxtest2/parser.expect
+++ /dev/null
@@ -1,15 +0,0 @@
-package main
-
-file bar.gox
-ast.GenDecl:
-  Tok: var
-  Specs:
-    ast.ValueSpec:
-      Type:
-        ast.SelectorExpr:
-          X:
-            ast.Ident:
-              Name: x
-          Sel:
-            ast.Ident:
-              Name: App
diff --git a/parser/_testdata/overload1/overload.gop b/parser/_testdata/overload1/overload.gop
deleted file mode 100644
index d5b458bf..00000000
--- a/parser/_testdata/overload1/overload.gop
+++ /dev/null
@@ -1,13 +0,0 @@
-func foo = (
-	func(a, b float64) float64 {
-		return a + b
-	}
-	func(a, b string) string {
-		return a + b
-	}
-)
-
-func bar = (
-	addComplex
-	(T).add
-)
diff --git a/parser/_testdata/overload1/parser.expect b/parser/_testdata/overload1/parser.expect
deleted file mode 100644
index aa686f6c..00000000
--- a/parser/_testdata/overload1/parser.expect
+++ /dev/null
@@ -1,94 +0,0 @@
-package main
-
-file overload.gop
-ast.OverloadFuncDecl:
-  Name:
-    ast.Ident:
-      Name: foo
-  Funcs:
-    ast.FuncLit:
-      Type:
-        ast.FuncType:
-          Params:
-            ast.FieldList:
-              List:
-                ast.Field:
-                  Names:
-                    ast.Ident:
-                      Name: a
-                    ast.Ident:
-                      Name: b
-                  Type:
-                    ast.Ident:
-                      Name: float64
-          Results:
-            ast.FieldList:
-              List:
-                ast.Field:
-                  Type:
-                    ast.Ident:
-                      Name: float64
-      Body:
-        ast.BlockStmt:
-          List:
-            ast.ReturnStmt:
-              Results:
-                ast.BinaryExpr:
-                  X:
-                    ast.Ident:
-                      Name: a
-                  Op: +
-                  Y:
-                    ast.Ident:
-                      Name: b
-    ast.FuncLit:
-      Type:
-        ast.FuncType:
-          Params:
-            ast.FieldList:
-              List:
-                ast.Field:
-                  Names:
-                    ast.Ident:
-                      Name: a
-                    ast.Ident:
-                      Name: b
-                  Type:
-                    ast.Ident:
-                      Name: string
-          Results:
-            ast.FieldList:
-              List:
-                ast.Field:
-                  Type:
-                    ast.Ident:
-                      Name: string
-      Body:
-        ast.BlockStmt:
-          List:
-            ast.ReturnStmt:
-              Results:
-                ast.BinaryExpr:
-                  X:
-                    ast.Ident:
-                      Name: a
-                  Op: +
-                  Y:
-                    ast.Ident:
-                      Name: b
-ast.OverloadFuncDecl:
-  Name:
-    ast.Ident:
-      Name: bar
-  Funcs:
-    ast.Ident:
-      Name: addComplex
-    ast.SelectorExpr:
-      X:
-        ast.ParenExpr:
-          X:
-            ast.Ident:
-              Name: T
-      Sel:
-        ast.Ident:
-          Name: add
diff --git a/parser/_testdata/overload2/overload2.gop b/parser/_testdata/overload2/overload2.gop
deleted file mode 100644
index 6d0eeeea..00000000
--- a/parser/_testdata/overload2/overload2.gop
+++ /dev/null
@@ -1,11 +0,0 @@
-func (T).* = (
-	mul1
-	mul2
-)
-
-func (T).add = (
-	add1
-	func(a, b T) T {
-		return a + b
-	}
-)
diff --git a/parser/_testdata/overload2/parser.expect b/parser/_testdata/overload2/parser.expect
deleted file mode 100644
index 0a6f83f9..00000000
--- a/parser/_testdata/overload2/parser.expect
+++ /dev/null
@@ -1,68 +0,0 @@
-package main
-
-file overload2.gop
-ast.OverloadFuncDecl:
-  Recv:
-    ast.FieldList:
-      List:
-        ast.Field:
-          Type:
-            ast.Ident:
-              Name: T
-  Name:
-    ast.Ident:
-      Name: *
-  Funcs:
-    ast.Ident:
-      Name: mul1
-    ast.Ident:
-      Name: mul2
-ast.OverloadFuncDecl:
-  Recv:
-    ast.FieldList:
-      List:
-        ast.Field:
-          Type:
-            ast.Ident:
-              Name: T
-  Name:
-    ast.Ident:
-      Name: add
-  Funcs:
-    ast.Ident:
-      Name: add1
-    ast.FuncLit:
-      Type:
-        ast.FuncType:
-          Params:
-            ast.FieldList:
-              List:
-                ast.Field:
-                  Names:
-                    ast.Ident:
-                      Name: a
-                    ast.Ident:
-                      Name: b
-                  Type:
-                    ast.Ident:
-                      Name: T
-          Results:
-            ast.FieldList:
-              List:
-                ast.Field:
-                  Type:
-                    ast.Ident:
-                      Name: T
-      Body:
-        ast.BlockStmt:
-          List:
-            ast.ReturnStmt:
-              Results:
-                ast.BinaryExpr:
-                  X:
-                    ast.Ident:
-                      Name: a
-                  Op: +
-                  Y:
-                    ast.Ident:
-                      Name: b
diff --git a/parser/_testdata/stringex1/parser.expect b/parser/_testdata/stringex1/parser.expect
deleted file mode 100644
index ef2ca684..00000000
--- a/parser/_testdata/stringex1/parser.expect
+++ /dev/null
@@ -1,73 +0,0 @@
-package main
-
-file string_lit.gop
-noEntrypoint
-ast.FuncDecl:
-  Name:
-    ast.Ident:
-      Name: main
-  Type:
-    ast.FuncType:
-      Params:
-        ast.FieldList:
-  Body:
-    ast.BlockStmt:
-      List:
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "$"
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "$$"
-                    Extra:
-                      $$
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "a$$b$"
-                    Extra:
-                      a$$
-                      b$
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "a$$b$$"
-                    Extra:
-                      a$$
-                      b$$
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "a$b$%"
diff --git a/parser/_testdata/stringex1/string_lit.gop b/parser/_testdata/stringex1/string_lit.gop
deleted file mode 100644
index b7d7154f..00000000
--- a/parser/_testdata/stringex1/string_lit.gop
+++ /dev/null
@@ -1,5 +0,0 @@
-println "$"
-println "$$"
-println "a$$b$"
-println "a$$b$$"
-println "a$b$%"
diff --git a/parser/_testdata/stringex2/parser.expect b/parser/_testdata/stringex2/parser.expect
deleted file mode 100644
index f8758a24..00000000
--- a/parser/_testdata/stringex2/parser.expect
+++ /dev/null
@@ -1,67 +0,0 @@
-package main
-
-file string_lit.gop
-noEntrypoint
-ast.FuncDecl:
-  Name:
-    ast.Ident:
-      Name: main
-  Type:
-    ast.FuncType:
-      Params:
-        ast.FieldList:
-  Body:
-    ast.BlockStmt:
-      List:
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "${"
-                    Extra:
-                      ${
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "${ b }"
-                    Extra:
-                      ast.Ident:
-                        Name: b
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "a${"
-                    Extra:
-                      a${
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "a${b}c"
-                    Extra:
-                      a
-                      ast.Ident:
-                        Name: b
-                      c
diff --git a/parser/_testdata/stringex2/string_lit.gop b/parser/_testdata/stringex2/string_lit.gop
deleted file mode 100644
index 47881474..00000000
--- a/parser/_testdata/stringex2/string_lit.gop
+++ /dev/null
@@ -1,4 +0,0 @@
-println "${"
-println "${ b }"
-println "a${"
-println "a${b}c"
diff --git a/parser/_testdata/stringex3/parser.expect b/parser/_testdata/stringex3/parser.expect
deleted file mode 100644
index 320578eb..00000000
--- a/parser/_testdata/stringex3/parser.expect
+++ /dev/null
@@ -1,38 +0,0 @@
-package main
-
-file string_lit.gop
-noEntrypoint
-ast.FuncDecl:
-  Name:
-    ast.Ident:
-      Name: main
-  Type:
-    ast.FuncType:
-      Params:
-        ast.FieldList:
-  Body:
-    ast.BlockStmt:
-      List:
-        ast.ExprStmt:
-          X:
-            ast.CallExpr:
-              Fun:
-                ast.Ident:
-                  Name: println
-              Args:
-                ast.BasicLit:
-                  Kind: STRING
-                  Value: "file:${args[0]}?${query}"
-                    Extra:
-                      file:
-                      ast.IndexExpr:
-                        X:
-                          ast.Ident:
-                            Name: args
-                        Index:
-                          ast.BasicLit:
-                            Kind: INT
-                            Value: 0
-                      ?
-                      ast.Ident:
-                        Name: query
diff --git a/parser/_testdata/stringex3/string_lit.gop b/parser/_testdata/stringex3/string_lit.gop
deleted file mode 100644
index fdb21995..00000000
--- a/parser/_testdata/stringex3/string_lit.gop
+++ /dev/null
@@ -1,1 +0,0 @@
-println "file:${args[0]}?${query}"
diff --git a/parser/parser_test.go b/parser/parser_test.go
index b0dd4b7e..9a90a7de 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -63,7 +63,6 @@ func testErrCode(t *testing.T, code string, errExp, panicExp string) {
 			}
 		}
 	}()
-	t.Helper()
 	fset := token.NewFileSet()
 	_, err := Parse(fset, "/foo/bar.gop", code, 0)
 	if err == nil || err.Error() != errExp {
@@ -79,7 +78,6 @@ func testErrCodeParseExpr(t *testing.T, code string, errExp, panicExp string) {
 			}
 		}
 	}()
-	t.Helper()
 	_, err := ParseExpr(code)
 	if err == nil || err.Error() != errExp {
 		t.Fatal("testErrCodeParseExpr error:", err)
@@ -94,7 +92,6 @@ func testClassErrCode(t *testing.T, code string, errExp, panicExp string) {
 			}
 		}
 	}()
-	t.Helper()
 	fset := token.NewFileSet()
 	_, err := Parse(fset, "/foo/bar.gox", code, ParseGoPlusClass)
 	if err == nil || err.Error() != errExp {
@@ -507,23 +504,4 @@ func TestErrCompositeLiteral(t *testing.T) {
 `, `/foo/bar.gop:1:10: cannot parenthesize type in composite literal`, ``)
 }
 
-func TestErrSelectorExpr(t *testing.T) {
-	testErrCode(t, `
-x.
-*p
-`, `/foo/bar.gop:3:1: expected selector or type assertion, found '*'`, ``)
-}
-
-func TestErrStringLitEx(t *testing.T) {
-	testErrCode(t, `
-println "${ ... }"
-`, "/foo/bar.gop:2:13: expected operand, found '...'", ``)
-	testErrCode(t, `
-println "${b"
-`, "/foo/bar.gop:2:11: invalid $ expression: ${ doesn't end with }", ``)
-	testErrCode(t, `
-println "$a${b}"
-`, "/foo/bar.gop:2:10: invalid $ expression: neither `${ ... }` nor `$$`", ``)
-}
-
 // -----------------------------------------------------------------------------
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index d43bfcae..ca6686dc 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -100,7 +100,6 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
 	if sel != "" && !strings.Contains(pkgDir, sel) {
 		return
 	}
-	t.Helper()
 	log.Println("Parsing", pkgDir)
 	fset := token.NewFileSet()
 	pkgs, err := ParseDir(fset, pkgDir, nil, (Trace|ParseComments|ParseGoAsGoPlus)&^exclude)
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index 081d233a..47f2821e 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -84,16 +84,6 @@ func FprintNode(w io.Writer, lead string, v interface{}, prefix, indent string)
 					FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent)
 				}
 			}
-		} else if lit, ok := v.(*ast.StringLitEx); ok {
-			fmt.Fprintf(w, "%sExtra:\n", prefix)
-			prefix += indent
-			for _, part := range lit.Parts {
-				if val, ok := part.(string); ok {
-					fmt.Fprintf(w, "%s%v\n", prefix, val)
-				} else {
-					FprintNode(w, "", part, prefix, indent)
-				}
-			}
 		} else {
 			log.Panicln("FprintNode unexpected type:", t)
 		}
@@ -111,7 +101,7 @@ func Fprint(w io.Writer, pkg *ast.Package) {
 	for _, fpath := range paths {
 		fmt.Fprintf(w, "\nfile %s\n", filepath.Base(fpath))
 		file := pkg.Files[fpath]
-		if file.HasShadowEntry() {
+		if file.NoEntrypoint() {
 			fmt.Fprintf(w, "noEntrypoint\n")
 		}
 		FprintNode(w, "", file.Decls, "", "  ")
diff --git a/printer/gop_test.go b/printer/gop_test.go
index 8aadd297..30bd7aba 100644
--- a/printer/gop_test.go
+++ b/printer/gop_test.go
@@ -19,6 +19,7 @@ package printer_test
 import (
 	"bytes"
 	"io/fs"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -113,7 +114,7 @@ func testFrom(t *testing.T, fpath, sel string, mode int) {
 	if sel != "" && !strings.Contains(fpath, sel) {
 		return
 	}
-	src, err := os.ReadFile(fpath)
+	src, err := ioutil.ReadFile(fpath)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/testdata/mixgo-complex/bar.go b/testdata/_mixgo-complex/bar.go
similarity index 100%
rename from testdata/mixgo-complex/bar.go
rename to testdata/_mixgo-complex/bar.go
diff --git a/testdata/mixgo-complex/foo.gop b/testdata/_mixgo-complex/foo.gop
similarity index 100%
rename from testdata/mixgo-complex/foo.gop
rename to testdata/_mixgo-complex/foo.gop
diff --git a/x/build/build_test.go b/x/build/build_test.go
index e4c692fc..cb867bd2 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 	"go/printer"
 	"go/types"
+	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -37,7 +38,6 @@ var (
 )
 
 func init() {
-	cl.SetDebug(cl.FlagNoMarkAutogen)
 	ctx.LoadConfig = func(cfg *cl.Config) {
 		cfg.NoFileLine = true
 	}
@@ -407,7 +407,7 @@ func testFromDir(t *testing.T, relDir string) {
 		t.Fatal("Getwd failed:", err)
 	}
 	dir = path.Join(dir, relDir)
-	fis, err := os.ReadDir(dir)
+	fis, err := ioutil.ReadDir(dir)
 	if err != nil {
 		t.Fatal("ReadDir failed:", err)
 	}