Repository: goplus/gop
Pull request #1405 (2023-08-26 17:17:08)

v1.1.6


Patch:
diff --git a/.gitattributes b/.gitattributes
index b541ff26..04f25b95 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2,4 +2,5 @@
 *.golden text eol=lf
 
 *.gop text eol=lf
-*.go text eol=lf
\ No newline at end of file
+*.go text eol=lf
+*.gox text eol=lf
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 2d810abb..5800d2a8 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -10,14 +10,14 @@ jobs:
   Test:
     strategy:
       matrix:
-        go-version: [1.16.x, 1.17.x, 1.18.x]
+        go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x]
         os: [ubuntu-latest, windows-latest, macos-11]
     runs-on: ${{ matrix.os }}
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Set up Go
-      uses: actions/setup-go@v2
+      uses: actions/setup-go@v4
       with:
         go-version: ${{ matrix.go-version }}
 
@@ -34,4 +34,4 @@ jobs:
       run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./...
 
     - name: Codecov
-      uses: codecov/codecov-action@v2
+      uses: codecov/codecov-action@v3
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index b79c95fd..2e288a65 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -9,7 +9,7 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Checkout tag
       run: |
@@ -20,7 +20,7 @@ jobs:
         echo "TAG_NAME=${tag_name}" >> $GITHUB_ENV
 
     - name: Set up Go
-      uses: actions/setup-go@v2
+      uses: actions/setup-go@v4
       with:
         go-version: 1.17
 
@@ -35,4 +35,4 @@ jobs:
         tag: ${{ env.TAG_NAME }}
         file: ./build-dir/gop-*.zip
         file_glob: true
-        prerelease: true
\ No newline at end of file
+        prerelease: true
diff --git a/.gitignore b/.gitignore
index ffb5ce04..18f4ec7e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,8 +33,7 @@ coverage.txt
 .gop/
 gop_autogen*.go
 go.json
-go.work
-go.work.sum
+go.work*
 format.result
 *.cache
 
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 3e0c29c6..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.1.5
diff --git a/ast/ast.go b/ast/ast.go
index 77a55156..ac758818 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -16,7 +16,6 @@
 
 // Package ast declares the types used to represent syntax trees for Go+
 // packages.
-//
 package ast
 
 import (
@@ -71,7 +70,6 @@ type Comment = ast.Comment
 
 // A CommentGroup represents a sequence of comments
 // with no other tokens and no empty lines between.
-//
 type CommentGroup = ast.CommentGroup
 
 // ----------------------------------------------------------------------------
@@ -526,7 +524,6 @@ func (x *ChanType) End() token.Pos { return x.Value.End() }
 
 // exprNode() ensures that only expression/type nodes can be
 // assigned to an Expr.
-//
 func (*BadExpr) exprNode()        {}
 func (*Ident) exprNode()          {}
 func (*Ellipsis) exprNode()       {}
@@ -577,7 +574,6 @@ func (x *Ident) String() string {
 
 // A statement is represented by a tree consisting of one
 // or more of the following concrete statement nodes.
-//
 type (
 	// A BadStmt node is a placeholder for statements containing
 	// syntax errors for which no correct statement nodes can be
@@ -910,7 +906,6 @@ func (s *RangeStmt) End() token.Pos { return s.Body.End() }
 
 // stmtNode() ensures that only statement nodes can be
 // assigned to a Stmt.
-//
 func (*BadStmt) stmtNode()        {}
 func (*DeclStmt) stmtNode()       {}
 func (*EmptyStmt) stmtNode()      {}
@@ -938,7 +933,6 @@ func (*RangeStmt) stmtNode()      {}
 
 // A Spec node represents a single (non-parenthesized) import,
 // constant, type, or variable declaration.
-//
 type (
 	// The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec.
 	Spec interface {
@@ -962,6 +956,7 @@ type (
 		Doc     *CommentGroup // associated documentation; or nil
 		Names   []*Ident      // value names (len(Names) > 0)
 		Type    Expr          // value type; or nil
+		Tag     *BasicLit     // classfile field tag; or nil
 		Values  []Expr        // initial values; or nil
 		Comment *CommentGroup // line comments; or nil
 	}
@@ -988,7 +983,12 @@ func (s *ImportSpec) Pos() token.Pos {
 }
 
 // Pos returns position of first character belonging to the node.
-func (s *ValueSpec) Pos() token.Pos { return s.Names[0].Pos() }
+func (s *ValueSpec) Pos() token.Pos {
+	if len(s.Names) == 0 {
+		return s.Type.Pos()
+	}
+	return s.Names[0].Pos()
+}
 
 // Pos returns position of first character belonging to the node.
 func (s *TypeSpec) Pos() token.Pos { return s.Name.Pos() }
@@ -1017,13 +1017,11 @@ func (s *TypeSpec) End() token.Pos { return s.Type.End() }
 
 // specNode() ensures that only spec nodes can be
 // assigned to a Spec.
-//
 func (*ImportSpec) specNode() {}
 func (*ValueSpec) specNode()  {}
 func (*TypeSpec) specNode()   {}
 
 // A declaration is represented by one of the following declaration nodes.
-//
 type (
 	// A BadDecl node is a placeholder for declarations containing
 	// syntax errors for which no correct declaration nodes can be
@@ -1096,7 +1094,6 @@ func (d *FuncDecl) End() token.Pos {
 
 // declNode() ensures that only declaration nodes can be
 // assigned to a Decl.
-//
 func (*BadDecl) declNode()  {}
 func (*GenDecl) declNode()  {}
 func (*FuncDecl) declNode() {}
@@ -1124,7 +1121,6 @@ type FileType = int16
 // interpretation of the syntax tree by the manipulating program: Except for Doc
 // and Comment comments directly associated with nodes, the remaining comments
 // are "free-floating" (see also issues #18593, #20744).
-//
 type File struct {
 	Doc          *CommentGroup   // associated documentation; or nil
 	Package      token.Pos       // position of "package" keyword
@@ -1139,6 +1135,7 @@ type File struct {
 	NoPkgDecl    bool // no `package xxx` declaration
 	IsClass      bool // is a classfile
 	IsProj       bool // is a project classfile
+	IsNormalGox  bool // is a normal .gox file
 }
 
 // Pos returns position of first character belonging to the node.
@@ -1154,7 +1151,6 @@ func (f *File) End() token.Pos {
 
 // A Package node represents a set of source files
 // collectively building a Go package.
-//
 type Package struct {
 	Name    string               // package name
 	Scope   *Scope               // package scope across all files
diff --git a/ast/filter.go b/ast/filter.go
index 2a48f4b2..baa1e8be 100644
--- a/ast/filter.go
+++ b/ast/filter.go
@@ -37,7 +37,6 @@ func exportFilter(name string) bool {
 // stripped. The File.Comments list is not changed.
 //
 // FileExports reports whether there are exported declarations.
-//
 func FileExports(src *File) bool {
 	return filterFile(src, exportFilter, true)
 }
@@ -48,7 +47,6 @@ func FileExports(src *File) bool {
 //
 // PackageExports reports whether there are exported declarations;
 // it returns false otherwise.
-//
 func PackageExports(pkg *Package) bool {
 	return filterPackage(pkg, exportFilter, true)
 }
@@ -73,7 +71,6 @@ func filterIdentList(list []*Ident, f Filter) []*Ident {
 // fieldName assumes that x is the type of an anonymous field and
 // returns the corresponding field name. If x is not an acceptable
 // anonymous field, the result is nil.
-//
 func fieldName(x Expr) *Ident {
 	switch t := x.(type) {
 	case *Ident:
@@ -243,7 +240,6 @@ func filterSpecList(list []Spec, f Filter, export bool) []Spec {
 //
 // FilterDecl reports whether there are any declared names left after
 // filtering.
-//
 func FilterDecl(decl Decl, f Filter) bool {
 	return filterDecl(decl, f, false)
 }
@@ -268,7 +264,6 @@ func filterDecl(decl Decl, f Filter, export bool) bool {
 //
 // FilterFile reports whether there are any top-level declarations
 // left after filtering.
-//
 func FilterFile(src *File, f Filter) bool {
 	return filterFile(src, f, false)
 }
@@ -295,7 +290,6 @@ func filterFile(src *File, f Filter, export bool) bool {
 //
 // FilterPackage reports whether there are any top-level declarations
 // left after filtering.
-//
 func FilterPackage(pkg *Package, f Filter) bool {
 	return filterPackage(pkg, f, false)
 }
@@ -329,7 +323,6 @@ const (
 // nameOf returns the function (foo) or method name (foo.bar) for
 // the given function declaration. If the AST is incorrect for the
 // receiver, it assumes a function instead.
-//
 func nameOf(f *FuncDecl) string {
 	if r := f.Recv; r != nil && len(r.List) == 1 {
 		// looks like a correct receiver declaration
@@ -349,12 +342,10 @@ func nameOf(f *FuncDecl) string {
 
 // separator is an empty //-style comment that is interspersed between
 // different comment groups when they are concatenated into a single group
-//
 var separator = &Comment{Slash: token.NoPos, Text: "//"}
 
 // MergePackageFiles creates a file AST by merging the ASTs of the
 // files belonging to a package. The mode flags control merging behavior.
-//
 func MergePackageFiles(pkg *Package, mode MergeMode) *File {
 	// Count the number of package docs, comments and declarations across
 	// all package files. Also, compute sorted list of filenames, so that
@@ -509,6 +500,6 @@ func MergePackageFiles(pkg *Package, mode MergeMode) *File {
 	// TODO(gri) need to compute unresolved identifiers!
 	return &File{
 		doc, pos, NewIdent(pkg.Name), decls, pkg.Scope,
-		imports, nil, comments, nil, false, false, false, false,
+		imports, nil, comments, nil, false, false, false, false, false,
 	}
 }
diff --git a/builtin/ng/int128.go b/builtin/ng/int128.go
index 34127492..707caadd 100644
--- a/builtin/ng/int128.go
+++ b/builtin/ng/int128.go
@@ -7,6 +7,12 @@ import (
 	"math/bits"
 )
 
+const (
+	Int128_Max       = 1<<127 - 1
+	Int128_Min       = -1 << 127
+	Int128_IsUntyped = true
+)
+
 const (
 	signBit  = 0x8000000000000000
 	maxInt64 = 1<<63 - 1
@@ -634,7 +640,6 @@ func (i Int128) Gop_Rsh(n Gop_ninteger) Int128 {
 // Mul returns the product of two I128s.
 //
 // Overflow should wrap around, as per the Go spec.
-//
 func (i Int128) Gop_Mul__1(n Int128) (dest Int128) {
 	hi, lo := bits.Mul64(i.lo, n.lo)
 	hi += i.hi*n.lo + i.lo*n.hi
diff --git a/builtin/ng/uint128.go b/builtin/ng/uint128.go
index b1852c36..cfa8ebdf 100644
--- a/builtin/ng/uint128.go
+++ b/builtin/ng/uint128.go
@@ -12,6 +12,12 @@ const (
 	GopPackage = true // to indicate this is a Go+ package
 )
 
+const (
+	Uint128_Max       = 1<<128 - 1
+	Uint128_Min       = 0
+	Uint128_IsUntyped = true
+)
+
 const (
 	maxUint64 = (1 << 64) - 1
 	intSize   = 32 << (^uint(0) >> 63)
@@ -349,7 +355,6 @@ func (u Uint128) ReverseBytes() Uint128 {
 //
 // The specific value returned by Cmp is undefined, but it is guaranteed to
 // satisfy the above constraints.
-//
 func (u Uint128) Cmp__1(n Uint128) int {
 	if u.hi == n.hi {
 		if u.lo > n.lo {
diff --git a/cl/classfile.go b/cl/classfile.go
index 432e1a2d..6e0fe6c4 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -24,6 +24,7 @@ import (
 	"strings"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
 )
@@ -32,9 +33,8 @@ import (
 
 type gmxSettings struct {
 	gameClass  string
-	extSpx     string
 	game       gox.Ref
-	sprite     gox.Ref
+	sprite     map[string]gox.Ref
 	scheds     []string
 	schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload)
 	pkgImps    []*gox.PkgRef
@@ -60,35 +60,36 @@ func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
 	return p.schedStmts
 }
 
-func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, conf *Config) *gmxSettings {
-	_, name := filepath.Split(file)
-	ext := filepath.Ext(name)
-	if idx := strings.Index(name, "."); idx > 0 {
-		name = name[:idx]
-		if name == "main" {
-			name = "_main"
-		}
-	}
+func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxSettings {
+	ext := parser.ClassFileExt(file)
 	gt, ok := conf.LookupClass(ext)
 	if !ok {
 		panic("TODO: class not found")
 	}
+	var name string
+	if f.IsProj {
+		_, name = filepath.Split(file)
+		if idx := strings.Index(name, "."); idx > 0 {
+			name = name[:idx]
+			if name == "main" {
+				name = "_main"
+			}
+		}
+	}
 	pkgPaths := gt.PkgPaths
-	p := &gmxSettings{extSpx: gt.WorkExt, gameClass: name, pkgPaths: 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]
-	p.game, p.gameIsPtr = spxTryRef(spx, "Gop_proj", "Proj")
-	if p.game == nil {
-		p.game, p.gameIsPtr = spxRef(spx, "Gop_game", "Game")
-	}
-	if gt.WorkExt != "" {
-		p.sprite, _ = spxTryRef(spx, "Gop_work", "Work")
-		if p.sprite == nil {
-			p.sprite, _ = spxRef(spx, "Gop_sprite", "Sprite")
-		}
+	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
@@ -113,22 +114,19 @@ func getDefaultClass(file string) string {
 	return name
 }
 
-func spxRef(spx *gox.PkgRef, name, typ string) (obj gox.Ref, isPtr bool) {
-	obj, isPtr = spxTryRef(spx, name, typ)
-	if obj == nil {
-		panic(spx.Path() + "." + name + " not found")
+func spxTryRef(spx *gox.PkgRef, typ string) (obj types.Object, isPtr bool) {
+	if strings.HasPrefix(typ, "*") {
+		typ, isPtr = typ[1:], true
 	}
+	obj = spx.Ref(typ)
 	return
 }
 
-func spxTryRef(spx *gox.PkgRef, name, typ string) (obj gox.Ref, isPtr bool) {
-	if v := getStringConst(spx, name); v != "" {
-		typ = v
-		if strings.HasPrefix(typ, "*") {
-			typ, isPtr = typ[1:], true
-		}
+func spxRef(spx *gox.PkgRef, typ string) (obj gox.Ref, isPtr bool) {
+	obj, isPtr = spxTryRef(spx, typ)
+	if obj == nil {
+		panic(spx.Path() + "." + typ + " not found")
 	}
-	obj = spx.TryRef(typ)
 	return
 }
 
diff --git a/cl/compile.go b/cl/compile.go
index c472b751..b1d71e1f 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -23,10 +23,12 @@ import (
 	"log"
 	"os"
 	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/ast/fromgo"
+	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
 	"github.com/goplus/gox/cpackages"
@@ -60,7 +62,8 @@ func SetDebug(flags int) {
 
 // -----------------------------------------------------------------------------
 
-type Class = modfile.Classfile
+type Project = modfile.Project
+type Class = modfile.Class
 
 // Config of loading Go+ packages.
 type Config struct {
@@ -82,7 +85,7 @@ type Config struct {
 	LookupPub func(pkgPath string) (pubfile string, err error)
 
 	// LookupClass lookups a class by specified file extension.
-	LookupClass func(ext string) (c *Class, ok bool)
+	LookupClass func(ext string) (c *Project, ok bool)
 
 	// An Importer resolves import paths to Packages.
 	Importer types.Importer
@@ -399,10 +402,18 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	})
 	for file, gmx := range files {
 		if gmx.IsProj {
-			ctx.gmxSettings = newGmx(ctx, p, file, conf)
+			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
+			}
+		}
+	}
 	for fpath, f := range files {
 		fileLine := !conf.NoFileLine
 		ctx := &blockCtx{
@@ -412,6 +423,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		}
 		preloadGopFile(p, ctx, fpath, f, conf)
 	}
+
 	for fpath, gof := range pkg.GoFiles {
 		f := fromgo.ASTFile(gof, 0)
 		ctx := &blockCtx{
@@ -420,16 +432,30 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		}
 		preloadFile(p, ctx, fpath, f, false)
 	}
-	for _, f := range files {
+
+	// sort files
+	type File struct {
+		*ast.File
+		path string
+	}
+	var sfiles []*File
+	for fpath, f := range files {
+		sfiles = append(sfiles, &File{f, fpath})
+	}
+	sort.Slice(sfiles, func(i, j int) bool {
+		return sfiles[i].path < sfiles[j].path
+	})
+
+	for _, f := range sfiles {
 		if f.IsProj {
-			loadFile(ctx, f)
+			loadFile(ctx, f.File)
 			gmxMainFunc(p, ctx)
 			break
 		}
 	}
-	for _, f := range files {
+	for _, f := range sfiles {
 		if !f.IsProj { // only one .gmx file
-			loadFile(ctx, f)
+			loadFile(ctx, f.File)
 		}
 	}
 	for _, ld := range ctx.tylds {
@@ -532,12 +558,13 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 			baseType = types.NewPointer(baseType)
 		}
 	case f.IsClass:
+		classType = getDefaultClass(file)
 		if parent.gmxSettings != nil {
-			classType = getDefaultClass(file)
-			o := parent.sprite
-			baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
-		} else {
-			classType = getDefaultClass(file)
+			ext := parser.ClassFileExt(file)
+			o, ok := parent.sprite[ext]
+			if ok {
+				baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
+			}
 		}
 	}
 	if classType != "" {
@@ -565,42 +592,40 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 				}
 				pkg := p.Types
 				var flds []*types.Var
+				var tags []string
 				chk := newCheckRedecl()
 				if len(baseTypeName) != 0 {
 					flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true))
+					tags = append(tags, "")
 					chk.chkRedecl(ctx, baseTypeName, pos)
 				}
-				if spxClass {
+				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)
 						flds = append(flds, fld)
+						tags = append(tags, "")
 					}
 				}
 				for _, v := range specs {
 					spec := v.(*ast.ValueSpec)
-					if len(spec.Values) > 0 {
-						pos := ctx.Position(v.Pos())
-						ctx.handleCodeErrorf(&pos, "cannot assign value to field in class file")
-						continue
-					}
 					var embed bool
-					var typ types.Type
-					if spec.Type == nil {
-						typ = toType(ctx, spec.Names[0])
+					if spec.Names == nil {
 						embed = true
-					} else {
-						typ = toType(ctx, spec.Type)
+						v := parseTypeEmbedName(spec.Type)
+						spec.Names = []*ast.Ident{v}
 					}
+					typ := toType(ctx, spec.Type)
 					for _, name := range spec.Names {
 						if chk.chkRedecl(ctx, name.Name, name.Pos()) {
 							continue
 						}
 						flds = append(flds, types.NewField(name.Pos(), pkg, name.Name, typ, embed))
+						tags = append(tags, toFieldTag(spec.Tag))
 					}
 				}
-				decl.InitType(p, types.NewStruct(flds, nil))
+				decl.InitType(p, types.NewStruct(flds, tags))
 			}
 			parent.tylds = append(parent.tylds, ld)
 		}
@@ -636,6 +661,20 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 	preloadFile(p, ctx, file, f, true)
 }
 
+func parseTypeEmbedName(typ ast.Expr) *ast.Ident {
+retry:
+	switch t := typ.(type) {
+	case *ast.Ident:
+		return t
+	case *ast.SelectorExpr:
+		return t.Sel
+	case *ast.StarExpr:
+		typ = t.X
+		goto retry
+	}
+	return nil
+}
+
 func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, genCode bool) {
 	parent := ctx.pkgCtx
 	syms := parent.syms
diff --git a/cl/expr.go b/cl/expr.go
index 5d185db1..5aa4d80b 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -430,6 +430,7 @@ func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags, pkgKind i
 }
 
 type fnType struct {
+	next     *fnType
 	params   *types.Tuple
 	n1       int
 	variadic bool
@@ -472,8 +473,15 @@ func (p *fnType) initWith(fnt types.Type, idx, nin int) {
 	p.inited = true
 	if t, ok := fnt.(*gox.TypeType); ok {
 		p.initTypeType(t)
-	} else if t := gox.CheckSignature(fnt, idx, nin); t != nil {
-		p.init(t)
+	} else if t := gox.CheckSignatures(fnt, idx, nin); t != nil {
+		p.init(t[0])
+		for i := 1; i < len(t); i++ {
+			fn := &fnType{}
+			fn.inited = true
+			fn.init(t[i])
+			p.next = fn
+			p = p.next
+		}
 	}
 }
 
@@ -496,7 +504,6 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 	default:
 		compileExpr(ctx, fn)
 	}
-	var fn fnType
 	var fnt = ctx.cb.Get(-1).Type
 	var flags gox.InstrFlags
 	var ellipsis = v.Ellipsis != gotoken.NoPos
@@ -506,6 +513,27 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 	if (inFlags & clCallWithTwoValue) != 0 {
 		flags |= gox.InstrFlagTwoValue
 	}
+	var fn *fnType = &fnType{}
+	for fn != nil {
+		err := compileCallArgs(fn, fnt, ctx, v, ellipsis, flags)
+		if err == nil {
+			break
+		}
+		if fn.next == nil {
+			panic(err)
+		}
+		fn = fn.next
+	}
+}
+
+func compileCallArgs(fn *fnType, fnt types.Type, ctx *blockCtx, v *ast.CallExpr, ellipsis bool, flags gox.InstrFlags) (err error) {
+	n := ctx.cb.InternalStack().Len()
+	defer func() {
+		if v := recover(); v != nil {
+			err = v.(error)
+			ctx.cb.InternalStack().SetLen(n)
+		}
+	}()
 	for i, arg := range v.Args {
 		switch expr := arg.(type) {
 		case *ast.LambdaExpr:
@@ -531,7 +559,7 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 			default:
 				t = nil
 			}
-			typetype := fn.typetype && t != nil && len(v.Args) == 1
+			typetype := fn.typetype && t != nil
 			if typetype {
 				ctx.cb.InternalStack().Pop()
 			}
@@ -544,6 +572,7 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 		}
 	}
 	ctx.cb.CallWith(len(v.Args), flags, v)
+	return
 }
 
 type clLambaFlag string
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 503b1053..7fe59d7f 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -243,7 +243,7 @@ func lookupType(ctx *blockCtx, name string) (types.Object, types.Object) {
 }
 
 type checkRedecl struct {
-	ctx   *blockCtx
+	// ctx *blockCtx
 	names map[string]token.Pos
 }
 
diff --git a/cl/internal/overload/foo/foo.go b/cl/internal/overload/foo/foo.go
new file mode 100644
index 00000000..ec75b1fc
--- /dev/null
+++ b/cl/internal/overload/foo/foo.go
@@ -0,0 +1,58 @@
+package foo
+
+const GopPackage = true
+
+type Mesher interface {
+	Name() string
+}
+
+type N struct {
+}
+
+func (m *N) OnKey__0(a string, fn func()) {
+}
+
+func (m *N) OnKey__1(a string, fn func(key string)) {
+}
+
+func (m *N) OnKey__2(a []string, fn func()) {
+}
+
+func (m *N) OnKey__3(a []string, fn func(key string)) {
+}
+
+func (m *N) OnKey__4(a []Mesher, fn func()) {
+}
+
+func (m *N) OnKey__5(a []Mesher, fn func(key Mesher)) {
+}
+
+func (m *N) OnKey__6(a []string, b []string, fn func(key string)) {
+}
+
+func (m *N) OnKey__7(a []string, b []Mesher, fn func(key string)) {
+}
+
+func OnKey__0(a string, fn func()) {
+}
+
+func OnKey__1(a string, fn func(key string)) {
+}
+
+func OnKey__2(a []string, fn func()) {
+}
+
+func OnKey__3(a []string, fn func(key string)) {
+}
+
+func OnKey__4(a []Mesher, fn func()) {
+}
+
+func OnKey__5(a []Mesher, fn func(key Mesher)) {
+}
+
+func OnKey__6(a []string, b []string, fn func(key string)) {
+}
+
+func OnKey__7(a []string, b []Mesher, fn func(key string)) {
+}
diff --git a/cl/internal/spx/game.go b/cl/internal/spx/game.go
index 9162dea9..a201d089 100644
--- a/cl/internal/spx/game.go
+++ b/cl/internal/spx/game.go
@@ -18,7 +18,6 @@ package spx
 
 const (
 	GopPackage = true
-	Gop_game   = "*MyGame"
 	Gop_sched  = "Sched,SchedNow"
 )
 
diff --git a/cl/internal/spx2/spx2.go b/cl/internal/spx2/spx2.go
index 8b83f551..12172a7a 100644
--- a/cl/internal/spx2/spx2.go
+++ b/cl/internal/spx2/spx2.go
@@ -32,3 +32,6 @@ type Sprite struct {
 
 func Sched() {
 }
+
+type Sprite2 struct {
+}
diff --git a/cl/types_go118.go b/cl/types_go118.go
index 4c723913..52b2ea4b 100644
--- a/cl/types_go118.go
+++ b/cl/types_go118.go
@@ -81,7 +81,7 @@ L:
 	}
 	switch t := typ.(type) {
 	case *ast.IndexExpr:
-		if 1 != orgTypeParams.Len() {
+		if orgTypeParams.Len() != 1 {
 			panic(ctx.newCodeErrorf(typ.Pos(), "got 1 type parameter, but receiver base type declares %v", orgTypeParams.Len()))
 		}
 		v := t.Index.(*ast.Ident)
diff --git a/cmd/gopfmt/gopfmt.go b/cmd/gopfmt/gopfmt.go
index e627fc86..500eb557 100644
--- a/cmd/gopfmt/gopfmt.go
+++ b/cmd/gopfmt/gopfmt.go
@@ -24,7 +24,6 @@ import (
 	"go/token"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -64,7 +63,7 @@ func processFile(filename string, class bool, in io.Reader, out io.Writer) error
 			return err
 		}
 	}
-	src, err := ioutil.ReadAll(in)
+	src, err := io.ReadAll(in)
 	if err != nil {
 		return err
 	}
@@ -90,7 +89,7 @@ func processFile(filename string, class bool, in io.Reader, out io.Writer) error
 
 	if *write {
 		dir, file := filepath.Split(filename)
-		f, err := ioutil.TempFile(dir, file)
+		f, err := os.CreateTemp(dir, file)
 		if err != nil {
 			return err
 		}
@@ -138,13 +137,11 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 					switch ext {
 					case ".go", ".gop":
 						ok = true
-					case ".gopx", ".spx", ".gmx":
+					case ".gox", ".spx", ".gmx":
 						ok, class = true, true
 					default:
-						_, class = mod.IsClass(ext)
-						if class {
-							ok = true
-						}
+						class = mod.IsClass(ext)
+						ok = class
 					}
 					return
 				}
@@ -153,7 +150,7 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 					switch ext {
 					case ".go", ".gop":
 						ok = true
-					case ".gopx", ".spx", ".gmx":
+					case ".gox", ".spx", ".gmx":
 						ok, class = true, true
 					}
 					return
diff --git a/cmd/internal/gopfmt/fmt.go b/cmd/internal/gopfmt/fmt.go
index 41a1e5bb..d03045d0 100644
--- a/cmd/internal/gopfmt/fmt.go
+++ b/cmd/internal/gopfmt/fmt.go
@@ -21,7 +21,6 @@ import (
 	"bytes"
 	"fmt"
 	"io/fs"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -63,7 +62,7 @@ var (
 )
 
 func gopfmt(path string, class, smart, mvgo bool) (err error) {
-	src, err := ioutil.ReadFile(path)
+	src, err := os.ReadFile(path)
 	if err != nil {
 		return
 	}
@@ -106,7 +105,7 @@ func gopfmt(path string, class, smart, mvgo bool) (err error) {
 
 func writeFileWithBackup(path string, target []byte) (err error) {
 	dir, file := filepath.Split(path)
-	f, err := ioutil.TempFile(dir, file)
+	f, err := os.CreateTemp(dir, file)
 	if err != nil {
 		return
 	}
@@ -147,13 +146,11 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 					switch ext {
 					case ".go", ".gop":
 						ok = true
-					case ".gopx", ".spx", ".gmx":
+					case ".gox", ".spx", ".gmx":
 						ok, class = true, true
 					default:
-						_, class = mod.IsClass(ext)
-						if class {
-							ok = true
-						}
+						class = mod.IsClass(ext)
+						ok = class
 					}
 					return
 				}
@@ -162,7 +159,7 @@ func (w *walker) walk(path string, d fs.DirEntry, err error) error {
 					switch ext {
 					case ".go", ".gop":
 						ok = true
-					case ".gopx", ".spx", ".gmx":
+					case ".gox", ".spx", ".gmx":
 						ok, class = true, true
 					}
 					return
diff --git a/cmd/internal/gopget/get.go b/cmd/internal/gopget/get.go
index b1ddf4cc..1142f37f 100644
--- a/cmd/internal/gopget/get.go
+++ b/cmd/internal/gopget/get.go
@@ -81,8 +81,8 @@ func get(pkgPath string) {
 
 	pkgMod, err := modload.Load(pkgModRoot, 0)
 	check(err)
-	if pkgMod.Classfile != nil {
-		mod.AddRegister(pkgModVer.Path)
+	if pkgMod.Project != nil {
+		mod.AddImport(pkgModVer.Path)
 		fmt.Fprintf(os.Stderr, "gop get: registered %s\n", pkgModVer.Path)
 	}
 
diff --git a/cmd/make.go b/cmd/make.go
index 2651b354..03fd9b2c 100644
--- a/cmd/make.go
+++ b/cmd/make.go
@@ -444,7 +444,21 @@ func uninstall() {
 	println("Go+ and related tools uninstalled successfully.")
 }
 
+func isInChinaWindows() bool {
+	// Run `systeminfo` command on windows to check locale.
+	out, err := execCommand("systeminfo")
+	if err != nil {
+		fmt.Println("Run [systeminfo] command failed with error: ", err)
+		return false
+	}
+	// Check if output contains `zh-cn;`
+	return strings.Contains(out, "zh-cn;")
+}
+
 func isInChina() bool {
+	if inWindows {
+		return isInChinaWindows()
+	}
 	const prefix = "LANG=\""
 	out, err := execCommand("locale")
 	if err != nil {
@@ -452,7 +466,7 @@ func isInChina() bool {
 	}
 	if strings.HasPrefix(out, prefix) {
 		out = out[len(prefix):]
-		return strings.HasPrefix(out, "zh_CN") || strings.HasPrefix(out, "zh_HK")
+		return strings.HasPrefix(out, "zh_CN")
 	}
 	return false
 }
diff --git a/gendeps.go b/gendeps.go
index be9bf9e2..c02d461a 100644
--- a/gendeps.go
+++ b/gendeps.go
@@ -36,7 +36,7 @@ import (
 func GenDepMods(mod *gopmod.Module, dir string, recursively bool) (ret map[string]struct{}, err error) {
 	modBase := mod.Path()
 	ret = make(map[string]struct{})
-	for _, r := range mod.Register {
+	for _, r := range mod.Import {
 		ret[r.ClassfileMod] = struct{}{}
 	}
 	err = HandleDeps(mod, dir, recursively, func(pkgPath string) {
@@ -81,8 +81,8 @@ type depsGen struct {
 
 func (p depsGen) gen(dir string) (err error) {
 	pkgs, err := parser.ParseDirEx(p.fset, dir, parser.Config{
-		IsClass: p.mod.IsClass,
-		Mode:    parser.ImportsOnly,
+		ClassKind: p.mod.ClassKind,
+		Mode:      parser.ImportsOnly,
 	})
 	if err != nil {
 		return
diff --git a/go.mod b/go.mod
index fad1c8e7..410cdcf1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,8 @@ go 1.16
 
 require (
 	github.com/goplus/c2go v0.7.13
-	github.com/goplus/gox v1.11.35
+	github.com/goplus/gox v1.11.37
 	github.com/goplus/libc v0.3.13
-	github.com/goplus/mod v0.9.12
+	github.com/goplus/mod v0.11.2
 	github.com/qiniu/x v1.11.9
 )
diff --git a/go.sum b/go.sum
index 0ff584da..75d4d088 100644
--- a/go.sum
+++ b/go.sum
@@ -7,12 +7,13 @@ github.com/goplus/c2go v0.7.13 h1:188dkHVVS3Qn/cwFtFwNgvlgjY2tBrZRjugv8icolt4=
 github.com/goplus/c2go v0.7.13/go.mod h1:s5NULWzUKi3erd3l4ahvpq+3qDGOvMjGb00a5o3h6zk=
 github.com/goplus/gox v1.11.21/go.mod h1:wRCRSNukie4cDqADF4w0Btc2Gk6V3p3V6hI5+rsVqa8=
 github.com/goplus/gox v1.11.32/go.mod h1:hdKq5ghywtKWnBJNQNVBkPITmWCqCFRwwd2LTYTfg2U=
-github.com/goplus/gox v1.11.35 h1:IZtsYhHyjClJx5IRBbdYXCtiVoaoX4koYyv+/t7rXXA=
-github.com/goplus/gox v1.11.35/go.mod h1:GcmPyIyC2t4Nfam7x6YRpe1IW9rf4nchgRpE19hF4L4=
+github.com/goplus/gox v1.11.37 h1:8oAXjokhvttO/4NZgfAJ02GE9OivbX2VSsEJWu3fGng=
+github.com/goplus/gox v1.11.37/go.mod h1:NkgUJWIjKxrhUwM4bgyUt3ZE0WlTunqfksMzrbnh7V8=
 github.com/goplus/libc v0.3.13 h1:2yHG8ghezBTK3Da7Fw1lNiitmt+Va+RzwKJ5GuqypCQ=
 github.com/goplus/libc v0.3.13/go.mod h1:xqG4/g3ilKBE/UDn5vkaE7RRQPQPyspj7ecuMuvlQJ8=
-github.com/goplus/mod v0.9.12 h1:CjgBGQIYqUTPGl3MrAS5CICzJwxbIfSa4OlEb141Gs4=
 github.com/goplus/mod v0.9.12/go.mod h1:YoPIowz71rnLLROA4YG0AC8bzDtPRyMaQwgTRLr8ri4=
+github.com/goplus/mod v0.11.2 h1:kgIdro905lTZgqGecTbZ416O8ayN+ca2DR5OB4ayeWI=
+github.com/goplus/mod v0.11.2/go.mod h1:d40I3nOr2qkGfLUjwc/BqLSq3oUUlQy5+/SpdiBKgY4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@@ -32,12 +33,12 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
+golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
-golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -45,7 +46,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 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.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
+golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -62,14 +63,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
+golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 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.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
+golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -77,13 +78,13 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
-golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
+golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
+golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/load.go b/load.go
index ad568952..3394b74b 100644
--- a/load.go
+++ b/load.go
@@ -58,7 +58,7 @@ func LoadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err er
 		return
 	}
 	if mod != nil {
-		err = mod.RegisterClasses()
+		err = mod.ImportClasses()
 		if err != nil {
 			err = errors.NewWith(err, `mod.RegisterClasses()`, -2, "(*gopmod.Module).RegisterClasses", mod)
 			return
@@ -107,9 +107,9 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 	}
 
 	pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{
-		IsClass: mod.IsClass,
-		Filter:  conf.Filter,
-		Mode:    parser.ParseComments,
+		ClassKind: mod.ClassKind,
+		Filter:    conf.Filter,
+		Mode:      parser.ParseComments,
 	})
 	if err != nil {
 		return
diff --git a/parser/parser.go b/parser/parser.go
index 75d3709b..2e5a803f 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -77,6 +77,8 @@ type parser struct {
 	syncPos token.Pos // last synchronization position
 	syncCnt int       // number of parser.advance calls without progress
 
+	varDeclCnt int // number of var decl
+
 	// Non-syntactic parser control
 	exprLev int  // < 0: in control clause, >= 0: in expression
 	inRHS   bool // if set, the parser is parsing a rhs expression
@@ -104,7 +106,6 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod
 
 	p.mode = mode
 	p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
-
 	p.next()
 }
 
@@ -2103,6 +2104,7 @@ func isLiteralType(x ast.Expr) bool {
 	return true
 }
 
+/*
 // If x is of the form *T, deref returns T, otherwise it returns x.
 func deref(x ast.Expr) ast.Expr {
 	if p, isPtr := x.(*ast.StarExpr); isPtr {
@@ -2110,6 +2112,7 @@ func deref(x ast.Expr) ast.Expr {
 	}
 	return x
 }
+*/
 
 // If x is of the form (T), unparen returns unparen(T), otherwise it returns x.
 func unparen(x ast.Expr) ast.Expr {
@@ -3351,26 +3354,69 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota
 	}
 
 	pos := p.pos
-	idents := p.parseIdentList()
-	typ := p.tryType()
+	var idents []*ast.Ident
+	var typ ast.Expr
+	var tag *ast.BasicLit
 	var values []ast.Expr
-	// always permit optional initialization for more tolerant parsing
-	if p.tok == token.ASSIGN {
-		p.next()
-		values = p.parseRHSList()
+	if p.mode&ParseGoPlusClass != 0 && p.topScope == p.pkgScope && keyword == token.VAR && p.varDeclCnt == 1 {
+		var starPos token.Pos
+		if p.tok == token.MUL {
+			starPos = p.pos
+			p.next()
+		}
+		ident := p.parseIdent()
+		if p.tok == token.PERIOD {
+			p.next()
+			typ = &ast.SelectorExpr{
+				X:   ident,
+				Sel: p.parseIdent(),
+			}
+			if starPos != token.NoPos {
+				typ = &ast.StarExpr{
+					Star: starPos,
+					X:    typ,
+				}
+			}
+		} else if starPos != token.NoPos {
+			typ = &ast.StarExpr{
+				Star: starPos,
+				X:    ident,
+			}
+		} else {
+			idents = append(idents, ident)
+			for p.tok == token.COMMA {
+				p.next()
+				idents = append(idents, p.parseIdent())
+			}
+			typ = p.tryType()
+			if len(idents) == 1 && typ == nil {
+				typ = ident
+				idents = nil
+			}
+			if p.tok == token.ASSIGN {
+				p.error(p.pos, "syntax error: cannot assign value to field in class file")
+			}
+		}
+		if p.tok == token.STRING {
+			tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
+			p.next()
+		}
+		p.expect(token.SEMICOLON)
+	} else {
+		idents = p.parseIdentList()
+		typ = p.tryType()
+		// always permit optional initialization for more tolerant parsing
+		if p.tok == token.ASSIGN {
+			p.next()
+			values = p.parseRHSList()
+		}
+		p.expectSemi() // call before accessing p.linecomment
 	}
-	p.expectSemi() // call before accessing p.linecomment
 
 	switch keyword {
 	case token.VAR:
 		if typ == nil && values == nil {
-			if p.mode&ParseGoPlusClass != 0 {
-				if len(idents) != 1 {
-					p.error(pos, "syntax error: unexpected newline, expected type")
-				}
-			} else {
-				p.error(pos, "missing variable type or initialization")
-			}
+			p.error(pos, "missing variable type or initialization")
 		}
 	case token.CONST:
 		if values == nil && (iota == 0 || typ != nil) {
@@ -3385,6 +3431,7 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota
 		Doc:     doc,
 		Names:   idents,
 		Type:    typ,
+		Tag:     tag,
 		Values:  values,
 		Comment: p.lineComment,
 	}
@@ -3425,7 +3472,9 @@ func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.Gen
 	if p.trace {
 		defer un(trace(p, "GenDecl("+keyword.String()+")"))
 	}
-
+	if keyword == token.VAR {
+		p.varDeclCnt++
+	}
 	doc := p.leadComment
 	pos := p.expect(keyword)
 	var lparen, rparen token.Pos
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 8642ecd4..3f3cac3d 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -19,10 +19,12 @@ package parser
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"io"
 	"io/fs"
 	"io/ioutil"
 	"os"
+	"path"
 	"path/filepath"
 	"strings"
 
@@ -104,9 +106,9 @@ func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, m
 }
 
 type Config struct {
-	IsClass func(ext string) (isProj bool, ok bool)
-	Filter  func(fs.FileInfo) bool
-	Mode    Mode
+	ClassKind func(fname string) (isProj, ok bool)
+	Filter    func(fs.FileInfo) bool
+	Mode      Mode
 }
 
 // ParseDirEx calls ParseFSDir by passing a local filesystem.
@@ -114,6 +116,17 @@ func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]
 	return ParseFSDir(fset, local, path, conf)
 }
 
+// ClassFileExt returns the classfile extension
+func ClassFileExt(path string) (ext string) {
+	ext = filepath.Ext(path)
+	if ext == ".gox" {
+		if c := filepath.Ext(path[:len(path)-4]); c != "" {
+			return c
+		}
+	}
+	return
+}
+
 // ParseFSDir calls ParseFile for all files with names ending in ".gop" in the
 // directory specified by path and returns a map of package name -> package
 // AST with all the packages found.
@@ -131,8 +144,8 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 	if err != nil {
 		return nil, err
 	}
-	if conf.IsClass == nil {
-		conf.IsClass = defaultIsClass
+	if conf.ClassKind == nil {
+		conf.ClassKind = defaultClassKind
 	}
 	pkgs = make(map[string]*ast.Package)
 	for _, d := range list {
@@ -140,8 +153,9 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 			continue
 		}
 		fname := d.Name()
+		fnameRmGox := fname
 		ext := filepath.Ext(fname)
-		var isProj, isClass, useGoParser bool
+		var isProj, isClass, isNormalGox, useGoParser, rmGox bool
 		switch ext {
 		case ".gop":
 		case ".go":
@@ -149,15 +163,27 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 				continue
 			}
 			useGoParser = (conf.Mode & ParseGoAsGoPlus) == 0
-		case ".gopx":
+		case ".gox":
 			isClass = true
+			t := fname[:len(fname)-4]
+			if c := filepath.Ext(t); c != "" {
+				ext, fnameRmGox, rmGox = c, t, true
+			} else {
+				isNormalGox = true
+			}
+			fallthrough
 		default:
-			if isProj, isClass = conf.IsClass(ext); !isClass {
-				continue
+			if !isNormalGox {
+				if isProj, isClass = conf.ClassKind(fnameRmGox); !isClass {
+					if rmGox {
+						return nil, fmt.Errorf("not found Go+ class by ext %q for %q", ext, fname)
+					}
+					continue
+				}
 			}
 		}
 		mode := conf.Mode
-		if isProj || isClass {
+		if isClass {
 			mode |= ParseGoPlusClass
 		}
 		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || conf.Filter(d)) {
@@ -178,6 +204,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 				}
 			} else if src, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
 				src.IsProj, src.IsClass = isProj, isClass
+				src.IsNormalGox = isNormalGox
 				pkg := reqPkg(pkgs, src.Name.Name)
 				pkg.Files[filename] = src
 			} else if first == nil {
@@ -200,13 +227,13 @@ func reqPkg(pkgs map[string]*ast.Package, name string) *ast.Package {
 	return pkg
 }
 
-func defaultIsClass(ext string) (isProj bool, ok bool) {
+func defaultClassKind(fname string) (isProj bool, ok bool) {
+	ext := path.Ext(fname)
 	switch ext {
 	case ".gmx":
-		isProj = true
-		fallthrough
+		return true, true
 	case ".spx":
-		ok = true
+		return ext == "main.spx", true
 	}
 	return
 }
diff --git a/printer/nodes.go b/printer/nodes.go
index c575c291..67b2452c 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -66,13 +66,13 @@ func SetDebug(flags int) {
 // printed.
 //
 // TODO(gri): linebreak may add too many lines if the next statement at "line"
-//            is preceded by comments because the computation of n assumes
-//            the current position before the comment and the target position
-//            after the comment. Thus, after interspersing such comments, the
-//            space taken up by them is not considered to reduce the number of
-//            linebreaks. At the moment there is no easy way to know about
-//            future (not yet interspersed) comments in this function.
 //
+//	is preceded by comments because the computation of n assumes
+//	the current position before the comment and the target position
+//	after the comment. Thus, after interspersing such comments, the
+//	space taken up by them is not considered to reduce the number of
+//	linebreaks. At the moment there is no easy way to know about
+//	future (not yet interspersed) comments in this function.
 func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (nbreaks int) {
 	n := nlimit(line - p.pos.Line)
 	if n < min {
@@ -153,8 +153,9 @@ const filteredMsg = "contains filtered or unexported fields"
 // expressions.
 //
 // TODO(gri) Consider rewriting this to be independent of []ast.Expr
-//           so that we can use the algorithm for any kind of list
-//           (e.g., pass list via a channel over which to range).
+//
+//	so that we can use the algorithm for any kind of list
+//	(e.g., pass list via a channel over which to range).
 func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool) {
 	if len(list) == 0 {
 		if isIncomplete {
@@ -690,6 +691,7 @@ func reduceDepth(depth int) int {
 // (Algorithm suggestion by Russ Cox.)
 //
 // The precedences are:
+//
 //	5             *  /  %  <<  >>  &  &^
 //	4             +  -  |  ^
 //	3             ==  !=  <  <=  >  >=
@@ -702,25 +704,24 @@ func reduceDepth(depth int) int {
 // To choose the cutoff, look at the whole expression but excluding primary
 // expressions (function calls, parenthesized exprs), and apply these rules:
 //
-//	1) If there is a binary operator with a right side unary operand
-//	   that would clash without a space, the cutoff must be (in order):
-//
-//		/*	6
-//		&&	6
-//		&^	6
-//		++	5
-//		--	5
+//  1. If there is a binary operator with a right side unary operand
+//     that would clash without a space, the cutoff must be (in order):
 //
-//         (Comparison operators always have spaces around them.)
+//     /*	6
+//     &&	6
+//     &^	6
+//     ++	5
+//     --	5
 //
-//	2) If there is a mix of level 5 and level 4 operators, then the cutoff
-//	   is 5 (use spaces to distinguish precedence) in Normal mode
-//	   and 4 (never use spaces) in Compact mode.
+//     (Comparison operators always have spaces around them.)
 //
-//	3) If there are no level 4 operators or no level 5 operators, then the
-//	   cutoff is 6 (always use spaces) in Normal mode
-//	   and 4 (never use spaces) in Compact mode.
+//  2. If there is a mix of level 5 and level 4 operators, then the cutoff
+//     is 5 (use spaces to distinguish precedence) in Normal mode
+//     and 4 (never use spaces) in Compact mode.
 //
+//  3. If there are no level 4 operators or no level 5 operators, then the
+//     cutoff is 6 (always use spaces) in Normal mode
+//     and 4 (never use spaces) in Compact mode.
 func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) {
 	prec := x.Op.Precedence()
 	if prec < prec1 {
@@ -1293,7 +1294,6 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po
 // indentList reports whether an expression list would look better if it
 // were indented wholesale (starting with the very first element, rather
 // than starting at the first line break).
-//
 func (p *printer) indentList(list []ast.Expr) bool {
 	// Heuristic: indentList reports whether there are more than one multi-
 	// line element in the list, or if there is any element that is not
@@ -1549,24 +1549,23 @@ type NewlineStmt struct {
 //
 // For example, the declaration:
 //
-//	const (
-//		foobar int = 42 // comment
-//		x          = 7  // comment
-//		foo
-//              bar = 991
-//	)
+//		const (
+//			foobar int = 42 // comment
+//			x          = 7  // comment
+//			foo
+//	             bar = 991
+//		)
 //
 // leads to the type/values matrix below. A run of value columns (V) can
 // be moved into the type column if there is no type for any of the values
 // in that column (we only move entire columns so that they align properly).
 //
-//	matrix        formatted     result
-//                    matrix
-//	T  V    ->    T  V     ->   true      there is a T and so the type
-//	-  V          -  V          true      column must be kept
-//	-  -          -  -          false
-//	-  V          V  -          false     V is moved into T column
-//
+//		matrix        formatted     result
+//	                   matrix
+//		T  V    ->    T  V     ->   true      there is a T and so the type
+//		-  V          -  V          true      column must be kept
+//		-  -          -  -          false
+//		-  V          V  -          false     V is moved into T column
 func keepTypeColumn(specs []ast.Spec) []bool {
 	m := make([]bool, len(specs))
 
@@ -1612,12 +1611,22 @@ func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) {
 	p.identList(s.Names, false) // always present
 	extraTabs := 3
 	if s.Type != nil || keepType {
-		p.print(vtab)
+		if len(s.Names) > 0 {
+			p.print(vtab)
+		}
 		extraTabs--
 	}
 	if s.Type != nil {
 		p.expr(s.Type)
 	}
+	if s.Tag != nil {
+		if len(s.Names) > 0 {
+			p.print(vtab)
+		}
+		p.print(vtab)
+		p.expr(s.Tag)
+		extraTabs--
+	}
 	if s.Values != nil {
 		p.print(vtab, token.ASSIGN, blank)
 		p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false)
@@ -1677,7 +1686,6 @@ func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit {
 // The parameter n is the number of specs in the group. If doIndent is set,
 // multi-line identifier lists in the spec are indented when the first
 // linebreak is encountered.
-//
 func (p *printer) spec(spec ast.Spec, n int, doIndent bool) {
 	switch s := spec.(type) {
 	case *ast.ImportSpec:
@@ -1741,7 +1749,7 @@ func (p *printer) genDecl(d *ast.GenDecl) {
 				var line int
 				for i, s := range d.Specs {
 					if i > 0 {
-						p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0)
+						p.linebreak(p.lineFor(s.Pos()), 0, ignore, p.linesFrom(line) > 0)
 					}
 					p.recordLine(&line)
 					p.valueSpec(s.(*ast.ValueSpec), keepType[i])
@@ -1770,7 +1778,6 @@ func (p *printer) genDecl(d *ast.GenDecl) {
 // The result is <= maxSize if the node fits on one line with at
 // most maxSize chars and the formatted output doesn't contain
 // any control chars. Otherwise, the result is > maxSize.
-//
 func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
 	// nodeSize invokes the printer, which may invoke nodeSize
 	// recursively. For deep composite literal nests, this can
@@ -1844,7 +1851,6 @@ func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int {
 // the block is printed on the current line, without line breaks, spaced from the header
 // by sep. Otherwise the block's opening "{" is printed on the current line, followed by
 // lines for the block's statements and its closing "}".
-//
 func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
 	if b == nil {
 		return
@@ -1884,7 +1890,6 @@ func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
 // the block is printed on the current line, without line breaks, spaced from the header
 // by sep. Otherwise the block's opening "{" is printed on the current line, followed by
 // lines for the block's statements and its closing "}".
-//
 func (p *printer) funcBodyUnnamed(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
 	if b == nil {
 		return


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 0b7236f2..dff76851 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -26,7 +26,7 @@ import (
 	"github.com/goplus/gox"
 	"github.com/goplus/gox/cpackages"
 	"github.com/goplus/gox/packages"
-	"github.com/goplus/mod/gopmod"
+	"github.com/goplus/mod/modfile"
 )
 
 var (
@@ -114,7 +114,7 @@ func TestClRangeStmt(t *testing.T) {
 
 func TestGmxSettings(t *testing.T) {
 	pkg := gox.NewPackage("", "foo", goxConf)
-	gmx := newGmx(nil, pkg, "main.t2gmx", &Config{
+	gmx := newGmx(nil, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
 		LookupClass: lookupClass,
 	})
 	scheds := gmx.getScheds(pkg.CB())
@@ -132,7 +132,7 @@ func TestGmxSettings(t *testing.T) {
 	}}, &Config{
 		LookupClass: lookupClassErr,
 	})
-	if e := err.Error(); e != `github.com/goplus/gop/cl/internal/libc.Gop_game not found` {
+	if e := err.Error(); e != `github.com/goplus/gop/cl/internal/libc.Game not found` {
 		t.Fatal("newGmx:", e)
 	}
 }
@@ -146,21 +146,23 @@ func TestSpxLookup(t *testing.T) {
 	spxLookup(nil, "foo")
 }
 
-func lookupClass(ext string) (c *gopmod.Class, ok bool) {
+func lookupClass(ext string) (c *modfile.Project, ok bool) {
 	switch ext {
 	case ".t2gmx", ".t2spx":
-		return &gopmod.Class{
-			ProjExt: ".t2gmx", WorkExt: ".t2spx",
+		return &modfile.Project{
+			Ext: ".t2gmx", Class: "Game",
+			Works:    []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"}},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
 	}
 	return
 }
 
-func lookupClassErr(ext string) (c *gopmod.Class, ok bool) {
+func lookupClassErr(ext string) (c *modfile.Project, ok bool) {
 	switch ext {
 	case ".t2gmx", ".t2spx":
-		return &gopmod.Class{
-			ProjExt: ".t2gmx", WorkExt: ".t2spx",
+		return &modfile.Project{
+			Ext: ".t2gmx", Class: "Game",
+			Works:    []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"}},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/libc"}}, true
 	}
 	return
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 5cf85672..fc25f66f 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -19,24 +19,33 @@ package cl_test
 import (
 	"bytes"
 	"os"
+	"path"
 	"testing"
 
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/scanner"
-	"github.com/goplus/mod/gopmod"
+	"github.com/goplus/mod/modfile"
 )
 
-func lookupClass(ext string) (c *gopmod.Class, ok bool) {
+func lookupClass(ext string) (c *modfile.Project, ok bool) {
 	switch ext {
 	case ".tgmx", ".tspx":
-		return &gopmod.Class{
-			ProjExt: ".tgmx", WorkExt: ".tspx",
+		return &modfile.Project{
+			Ext: ".tgmx", Class: "*MyGame",
+			Works:    []*modfile.Class{{Ext: ".tspx", Class: "Sprite"}},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx", "math"}}, true
-	case ".t2gmx", ".t2spx":
-		return &gopmod.Class{
-			ProjExt: ".t2gmx", WorkExt: ".t2spx",
+	case ".t2gmx", ".t2spx", ".t2spx2":
+		return &modfile.Project{
+			Ext: ".t2gmx", Class: "Game",
+			Works: []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"},
+				{Ext: ".t2spx2", Class: "Sprite2"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
+	case ".t3spx", ".t3spx2":
+		return &modfile.Project{
+			Works: []*modfile.Class{{Ext: ".t3spx", Class: "Sprite"},
+				{Ext: ".t3spx2", Class: "Sprite2"}},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
 	}
 	return
@@ -44,10 +53,11 @@ func lookupClass(ext string) (c *gopmod.Class, ok bool) {
 
 func spxParserConf() parser.Config {
 	return parser.Config{
-		IsClass: func(ext string) (isProj bool, ok bool) {
+		ClassKind: func(fname string) (isProj bool, ok bool) {
+			ext := path.Ext(fname)
 			c, ok := lookupClass(ext)
 			if ok {
-				isProj = (c.ProjExt == ext)
+				isProj = (c.Ext == ext)
 			}
 			return
 		},
@@ -112,23 +122,28 @@ func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string)
 }
 
 func TestSpxError(t *testing.T) {
-	gopSpxErrorTestEx(t, `./Game.tgmx:4:2: cannot assign value to field in class file`, `
+	gopSpxErrorTestEx(t, `./Game.tgmx:6:2: userScore redeclared
+	./Game.tgmx:5:2 other declaration of userScore`, `
+import "bytes"
 var (
 	Kai Kai
-	userScore int = 100
+	userScore int
+	userScore string
 )
 `, `
 println "hi"
 `, "Game.tgmx", "Kai.tspx")
 
-	gopSpxErrorTestEx(t, `./Kai.tspx:3:2: cannot assign value to field in class file`, `
+	gopSpxErrorTestEx(t, `./Kai.tspx:4:2: id redeclared
+	./Kai.tspx:3:2 other declaration of id`, `
 var (
 	Kai Kai
 	userScore int
 )
 `, `
 var (
-	id = 100
+	id int
+	id string
 )
 println "hi"
 `, "Game.tgmx", "Kai.tspx")
@@ -389,6 +404,66 @@ type Kai struct {
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx")
+
+	gopSpxTestEx(t, `
+println("Hi, Sprite2")
+`, `
+func onMsg(msg string) {
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	spx2 "github.com/goplus/gop/cl/internal/spx2"
+)
+
+type Game struct {
+	spx2.Game
+}
+
+func (this *Game) MainEntry() {
+	fmt.Println("Hi, Sprite2")
+}
+func main() {
+	new(Game).Main()
+}
+
+type Kai struct {
+	spx2.Sprite2
+	*Game
+}
+
+func (this *Kai) onMsg(msg string) {
+}
+`, "Game.t2gmx", "Kai.t2spx2")
+
+	gopSpxTestEx(t, `
+println("Hi, Sprite")
+`, `
+func onMsg(msg string) {
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	spx2 "github.com/goplus/gop/cl/internal/spx2"
+)
+
+type Dog struct {
+	spx2.Sprite
+}
+
+func (this *Dog) Main() {
+	fmt.Println("Hi, Sprite")
+}
+
+type Kai struct {
+	spx2.Sprite2
+}
+
+func (this *Kai) onMsg(msg string) {
+}
+`, "Dog.t3spx", "Kai.t3spx2")
 }
 
 func TestSpxMainEntry(t *testing.T) {
@@ -482,3 +557,43 @@ func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx")
 }
+
+func TestSpxGoxBasic(t *testing.T) {
+	gopSpxTestEx(t, `
+func onInit() {
+	for {
+	}
+}
+`, `
+func onMsg(msg string) {
+	for {
+		say "Hi"
+	}
+}
+`, `package main
+
+import spx "github.com/goplus/gop/cl/internal/spx"
+
+type Game struct {
+	*spx.MyGame
+}
+
+func (this *Game) onInit() {
+	for {
+		spx.SchedNow()
+	}
+}
+
+type Kai struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Kai) onMsg(msg string) {
+	for {
+		spx.Sched()
+		this.Say("Hi")
+	}
+}
+`, "Game.tgmx.gox", "Kai.tspx.gox")
+}
diff --git a/cl/compile_test.go b/cl/compile_test.go
index e799c9cf..3335a989 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -2152,13 +2152,13 @@ c := foo{B: "Hi"}
 
 type foo struct {
 	A int
-	B string "tag1:123"
+	B string `+"`tag1:123`"+`
 }
 
 func main() {
 	a := struct {
 		A int
-		B string "tag1:123"
+		B string `+"`tag1:123`"+`
 	}{1, "Hello"}
 	b := foo{1, "Hello"}
 	c := foo{B: "Hi"}
@@ -2188,7 +2188,7 @@ type bar = foo
 type foo struct {
 	p *foo
 	A int
-	B string "tag1:123"
+	B string `+"`tag1:123`"+`
 }
 
 func main() {
@@ -4319,7 +4319,7 @@ func TestClassFileGopx(t *testing.T) {
 var (
 	BaseClass
 	Width, Height float64
-	AggClass
+	*AggClass
 )
 
 type BaseClass struct{
@@ -4343,11 +4343,175 @@ type Rect struct {
 	BaseClass
 	Width  float64
 	Height float64
-	AggClass
+	*AggClass
 }
 
 func (this *Rect) Area() float64 {
 	return this.Width * this.Height
 }
-`, "Rect.gopx")
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	*bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	*bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	*bytes.Buffer "spec:\"buffer\""
+	a int "json:\"a\""
+	b int
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	*bytes.Buffer `+"`spec:\"buffer\"`"+`
+	a             int `+"`json:\"a\"`"+`
+	b             int
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+}
+
+func TestOverload(t *testing.T) {
+	gopClTest(t, `
+import "github.com/goplus/gop/cl/internal/overload/foo"
+
+type Mesh struct {
+}
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+
+var (
+	m1 = &Mesh{}
+	m2 = &Mesh{}
+)
+
+foo.onKey "hello", => {
+}
+foo.onKey "hello", key => {
+}
+foo.onKey ["1"], => {
+}
+foo.onKey ["2"], key => {
+}
+foo.onKey [m1,m2], => {
+}
+foo.onKey [m1,m2], key => {
+}
+foo.onKey ["a"], ["b"], key => {
+}
+foo.onKey ["a"], [m1,m2], key => {
+}
+foo.onKey ["a"],nil,key => {
+}
+n := &foo.N{}
+n.onKey "hello", => {
+}
+n.onKey "hello", key => {
+}
+n.onKey ["1"], => {
+}
+n.onKey ["2"], key => {
+}
+n.onKey [m1,m2], => {
+}
+n.onKey [m1,m2], key => {
+}
+n.onKey ["a"], ["b"], key => {
+}
+n.onKey ["a"], [m1,m2], key => {
+}
+n.onKey ["a"],nil,key => {
+}
+`, `package main
+
+import foo "github.com/goplus/gop/cl/internal/overload/foo"
+
+type Mesh struct {
+}
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+
+var m1 = &Mesh{}
+var m2 = &Mesh{}
+
+func main() {
+	foo.OnKey__0("hello", func() {
+	})
+	foo.OnKey__1("hello", func(key string) {
+	})
+	foo.OnKey__2([]string{"1"}, func() {
+	})
+	foo.OnKey__3([]string{"2"}, func(key string) {
+	})
+	foo.OnKey__4([]foo.Mesher{m1, m2}, func() {
+	})
+	foo.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
+	})
+	foo.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	foo.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
+	})
+	foo.OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+	n := &foo.N{}
+	n.OnKey__0("hello", func() {
+	})
+	n.OnKey__1("hello", func(key string) {
+	})
+	n.OnKey__2([]string{"1"}, func() {
+	})
+	n.OnKey__3([]string{"2"}, func(key string) {
+	})
+	n.OnKey__4([]foo.Mesher{m1, m2}, func() {
+	})
+	n.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
+	})
+	n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	n.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
+	})
+	n.OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+}
+`)
 }
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 31085b14..28e92643 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -868,16 +868,9 @@ import (
 }
 
 func TestErrClassFileGopx(t *testing.T) {
-	codeErrorTestEx(t, "main", "Rect.gopx",
-		`./Rect.gopx:3:2: cannot assign value to field in class file`, `
-var (
-	i int = 1
-)
-println "hello"
-`)
-	codeErrorTestEx(t, "main", "Rect.gopx",
-		`./Rect.gopx:5:2: A redeclared
-	./Rect.gopx:3:2 other declaration of A`, `
+	codeErrorTestEx(t, "main", "Rect.gox",
+		`./Rect.gox:5:2: A redeclared
+	./Rect.gox:3:2 other declaration of A`, `
 var (
 	A
 	i int
@@ -902,3 +895,38 @@ func test() {
 }
 `)
 }
+
+func TestErrInt128(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:2:16: cannot use 1<<127 (type untyped int) as type github.com/goplus/gop/builtin/ng.Int128 in assignment`, `
+var a int128 = 1<<127
+`)
+	codeErrorTest(t, `./bar.gop:2:13: cannot convert 1<<127 (untyped int constant 170141183460469231731687303715884105728) to type Int128`, `
+a := int128(1<<127)
+`)
+	codeErrorTest(t, `./bar.gop:2:13: cannot convert -1<<127-1 (untyped int constant -170141183460469231731687303715884105729) to type Int128`, `
+a := int128(-1<<127-1)
+`)
+	codeErrorTest(t, `./bar.gop:3:13: cannot convert b (untyped int constant -170141183460469231731687303715884105729) to type Int128`, `
+const b = -1<<127-1
+a := int128(b)
+`)
+}
+
+func TestErrUint128(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:2:17: cannot use 1<<128 (type untyped int) as type github.com/goplus/gop/builtin/ng.Uint128 in assignment`, `
+var a uint128 = 1<<128
+`)
+	codeErrorTest(t, `./bar.gop:2:14: cannot convert 1<<128 (untyped int constant 340282366920938463463374607431768211456) to type Uint128`, `
+a := uint128(1<<128)
+`)
+	codeErrorTest(t, `./bar.gop:2:17: cannot use -1 (type untyped int) as type github.com/goplus/gop/builtin/ng.Uint128 in assignment`, `
+var a uint128 = -1
+`)
+	codeErrorTest(t, `./bar.gop:2:14: cannot convert -1 (untyped int constant -1) to type Uint128`, `
+a := uint128(-1)
+`)
+	codeErrorTest(t, `./bar.gop:3:14: cannot convert b (untyped int constant -1) to type Uint128`, `
+const b = -1
+a := uint128(b)
+`)
+}
diff --git a/parser/_testdata/gopxtest/bar.gopx b/parser/_testdata/gopxtest/bar.gopx
deleted file mode 100644
index 783e9630..00000000
--- a/parser/_testdata/gopxtest/bar.gopx
+++ /dev/null
@@ -1,9 +0,0 @@
-var (
-	A
-	v int
-	B
-)
-
-type A struct{}
-
-type B struct{}
diff --git a/parser/_testdata/gopxtest/parser.expect b/parser/_testdata/gopxtest/parser.expect
deleted file mode 100644
index a4a5dabf..00000000
--- a/parser/_testdata/gopxtest/parser.expect
+++ /dev/null
@@ -1,43 +0,0 @@
-package main
-
-file bar.gopx
-ast.GenDecl:
-  Tok: var
-  Specs:
-    ast.ValueSpec:
-      Names:
-        ast.Ident:
-          Name: A
-    ast.ValueSpec:
-      Names:
-        ast.Ident:
-          Name: v
-      Type:
-        ast.Ident:
-          Name: int
-    ast.ValueSpec:
-      Names:
-        ast.Ident:
-          Name: B
-ast.GenDecl:
-  Tok: type
-  Specs:
-    ast.TypeSpec:
-      Name:
-        ast.Ident:
-          Name: A
-      Type:
-        ast.StructType:
-          Fields:
-            ast.FieldList:
-ast.GenDecl:
-  Tok: type
-  Specs:
-    ast.TypeSpec:
-      Name:
-        ast.Ident:
-          Name: B
-      Type:
-        ast.StructType:
-          Fields:
-            ast.FieldList:
diff --git a/parser/_testdata/goxtest/bar.gox b/parser/_testdata/goxtest/bar.gox
new file mode 100644
index 00000000..ebb1b146
--- /dev/null
+++ b/parser/_testdata/goxtest/bar.gox
@@ -0,0 +1,12 @@
+var (
+	A `json:"a"`
+	*B
+	x, y string
+	C.A  `json:"ca"`
+	*C.B `json:"b"`
+	v    int `json:"v"`
+)
+
+type A struct{}
+
+type B struct{}
diff --git a/parser/_testdata/goxtest/parser.expect b/parser/_testdata/goxtest/parser.expect
new file mode 100644
index 00000000..bc1af21d
--- /dev/null
+++ b/parser/_testdata/goxtest/parser.expect
@@ -0,0 +1,90 @@
+package main
+
+file bar.gox
+ast.GenDecl:
+  Tok: var
+  Specs:
+    ast.ValueSpec:
+      Type:
+        ast.Ident:
+          Name: A
+      Tag:
+        ast.BasicLit:
+          Kind: STRING
+          Value: `json:"a"`
+    ast.ValueSpec:
+      Type:
+        ast.StarExpr:
+          X:
+            ast.Ident:
+              Name: B
+    ast.ValueSpec:
+      Names:
+        ast.Ident:
+          Name: x
+        ast.Ident:
+          Name: y
+      Type:
+        ast.Ident:
+          Name: string
+    ast.ValueSpec:
+      Type:
+        ast.SelectorExpr:
+          X:
+            ast.Ident:
+              Name: C
+          Sel:
+            ast.Ident:
+              Name: A
+      Tag:
+        ast.BasicLit:
+          Kind: STRING
+          Value: `json:"ca"`
+    ast.ValueSpec:
+      Type:
+        ast.StarExpr:
+          X:
+            ast.SelectorExpr:
+              X:
+                ast.Ident:
+                  Name: C
+              Sel:
+                ast.Ident:
+                  Name: B
+      Tag:
+        ast.BasicLit:
+          Kind: STRING
+          Value: `json:"b"`
+    ast.ValueSpec:
+      Names:
+        ast.Ident:
+          Name: v
+      Type:
+        ast.Ident:
+          Name: int
+      Tag:
+        ast.BasicLit:
+          Kind: STRING
+          Value: `json:"v"`
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: A
+      Type:
+        ast.StructType:
+          Fields:
+            ast.FieldList:
+ast.GenDecl:
+  Tok: type
+  Specs:
+    ast.TypeSpec:
+      Name:
+        ast.Ident:
+          Name: B
+      Type:
+        ast.StructType:
+          Fields:
+            ast.FieldList:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index d96c6c86..e1e866d5 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -25,6 +25,20 @@ import (
 
 // -----------------------------------------------------------------------------
 
+func TestExt(t *testing.T) {
+	cases := [][2]string{
+		{"t.spx.gox", ".spx"},
+		{"t.spx", ".spx"},
+		{"t.gox", ".gox"},
+		{"t.abc", ".abc"},
+	}
+	for _, c := range cases {
+		if ret := ClassFileExt(c[0]); ret != c[1] {
+			t.Fatal("ClassFileExt:", c[0], "expected:", c[1], "got:", ret)
+		}
+	}
+}
+
 func panicMsg(e interface{}) string {
 	switch v := e.(type) {
 	case string:
@@ -59,7 +73,7 @@ func testClassErrCode(t *testing.T, code string, errExp, panicExp string) {
 		}
 	}()
 	fset := token.NewFileSet()
-	_, err := Parse(fset, "/foo/bar.gopx", code, ParseGoPlusClass)
+	_, err := Parse(fset, "/foo/bar.gox", code, ParseGoPlusClass)
 	if err == nil || err.Error() != errExp {
 		t.Fatal("testErrCode error:", err)
 	}
@@ -395,7 +409,36 @@ func TestClassErrCode(t *testing.T) {
 	A,B
 	v int
 )
-`, `/foo/bar.gopx:2:2: syntax error: unexpected newline, expected type`, ``)
+`, `/foo/bar.gox:2:2: missing variable type or initialization`, ``)
+	testClassErrCode(t, `var (
+	A.*B
+	v int
+)
+`, `/foo/bar.gox:2:4: expected 'IDENT', found '*' (and 2 more errors)`, ``)
+	testClassErrCode(t, `var (
+	[]A
+	v int
+)
+`, `/foo/bar.gox:2:2: expected 'IDENT', found '['`, ``)
+	testClassErrCode(t, `var (
+	*[]A
+	v int
+)
+`, `/foo/bar.gox:2:3: expected 'IDENT', found '['`, ``)
+	testClassErrCode(t, `var (
+	v int = 10
+)
+`, `/foo/bar.gox:2:8: syntax error: cannot assign value to field in class file`, ``)
+	testClassErrCode(t, `var (
+	v = 10
+)
+`, `/foo/bar.gox:2:4: syntax error: cannot assign value to field in class file`, ``)
+	testClassErrCode(t, `
+var (
+)
+const c = 100
+const d
+`, `/foo/bar.gox:5:7: missing constant value`, ``)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 99f2016a..bb84de9f 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -173,6 +173,12 @@ func TestErrParse(t *testing.T) {
 	if err != syscall.ENOENT {
 		t.Fatal("ParseFSDir:", err)
 	}
+
+	fs = parsertest.NewSingleFileFS("/foo", "test.abc.gox", `package foo`)
+	_, err = ParseFSDir(fset, fs, "/foo", Config{})
+	if err == nil {
+		t.Fatal("ParseFSDir test.gop: no error?")
+	}
 }
 
 func testFromDir(t *testing.T, sel, relDir string) {
diff --git a/printer/gop_test.go b/printer/gop_test.go
index f9996874..30bd7aba 100644
--- a/printer/gop_test.go
+++ b/printer/gop_test.go
@@ -121,7 +121,11 @@ func testFrom(t *testing.T, fpath, sel string, mode int) {
 
 	if (mode & excludeFormatSource) == 0 {
 		t.Run("format.Source "+fpath, func(t *testing.T) {
-			res, err := format.Source(src, false, fpath)
+			var class bool
+			if filepath.Ext(fpath) == ".gox" {
+				class = true
+			}
+			res, err := format.Source(src, class, fpath)
 			if err != nil {
 				t.Fatal("Source failed:", err)
 			}
@@ -132,7 +136,11 @@ func testFrom(t *testing.T, fpath, sel string, mode int) {
 	if (mode & excludeFormatNode) == 0 {
 		t.Run("format.Node "+fpath, func(t *testing.T) {
 			fset := token.NewFileSet()
-			f, err := parser.ParseFile(fset, fpath, src, parser.ParseComments)
+			m := parser.ParseComments
+			if filepath.Ext(fpath) == ".gox" {
+				m |= parser.ParseGoPlusClass
+			}
+			f, err := parser.ParseFile(fset, fpath, src, m)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -182,7 +190,8 @@ func TestFromParse(t *testing.T) {
 			return err
 		}
 		name := info.Name()
-		if !info.IsDir() && filepath.Ext(name) == ".gop" {
+		ext := filepath.Ext(name)
+		if !info.IsDir() && (ext == ".gop" || ext == ".gox") {
 			testFrom(t, path, sel, 0)
 		}
 		return nil