Repository: goplus/gop
Pull request #1377 (2023-06-04 05:21:36)

cl:parser: support .gopx and classfile embed type
implement https://github.com/goplus/gop/issues/1371

add default classfile .gopx
support classfile embed type


Patch:
diff --git a/cl/compile.go b/cl/compile.go
index 10d75996..c472b751 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -536,16 +536,19 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 			classType = getDefaultClass(file)
 			o := parent.sprite
 			baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
+		} else {
+			classType = getDefaultClass(file)
 		}
-		// TODO: panic
 	}
 	if classType != "" {
 		if debugLoad {
 			log.Println("==> Preload type", classType)
 		}
-		ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths))
-		for i, pkgPath := range parent.pkgPaths {
-			ctx.lookups[i] = p.Import(pkgPath)
+		if parent.gmxSettings != nil {
+			ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths))
+			for i, pkgPath := range parent.pkgPaths {
+				ctx.lookups[i] = p.Import(pkgPath)
+			}
 		}
 		syms := parent.syms
 		pos := f.Pos()
@@ -561,27 +564,40 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 					log.Println("==> Load > InitType", classType)
 				}
 				pkg := p.Types
-				flds := make([]*types.Var, 1, 2)
-				flds[0] = types.NewField(pos, pkg, baseTypeName, baseType, true)
+				var flds []*types.Var
+				chk := newCheckRedecl()
+				if len(baseTypeName) != 0 {
+					flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true))
+					chk.chkRedecl(ctx, baseTypeName, pos)
+				}
 				if spxClass {
 					typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: parent.gameClass}})
-					fld := types.NewField(pos, pkg, getTypeName(typ), typ, true)
-					flds = append(flds, fld)
+					name := getTypeName(typ)
+					if !chk.chkRedecl(ctx, name, pos) {
+						fld := types.NewField(pos, pkg, name, typ, true)
+						flds = append(flds, fld)
+					}
 				}
 				for _, v := range specs {
 					spec := v.(*ast.ValueSpec)
-					if spec.Type == nil {
-						pos := ctx.Position(v.Pos())
-						ctx.handleCodeErrorf(&pos, "missing field type in class file")
-						continue
-					}
 					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])
+						embed = true
+					} else {
+						typ = toType(ctx, spec.Type)
 					}
-					typ := toType(ctx, spec.Type)
 					for _, name := range spec.Names {
-						flds = append(flds, types.NewField(name.Pos(), pkg, name.Name, typ, false))
+						if chk.chkRedecl(ctx, name.Name, name.Pos()) {
+							continue
+						}
+						flds = append(flds, types.NewField(name.Pos(), pkg, name.Name, typ, embed))
 					}
 				}
 				decl.InitType(p, types.NewStruct(flds, nil))
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 89b5965a..503b1053 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -242,30 +242,41 @@ func lookupType(ctx *blockCtx, name string) (types.Object, types.Object) {
 	return o, o
 }
 
+type checkRedecl struct {
+	ctx   *blockCtx
+	names map[string]token.Pos
+}
+
+func newCheckRedecl() *checkRedecl {
+	p := &checkRedecl{names: make(map[string]token.Pos)}
+	return p
+}
+
+func (p *checkRedecl) chkRedecl(ctx *blockCtx, name string, pos token.Pos) bool {
+	if name == "_" {
+		return false
+	}
+	if opos, ok := p.names[name]; ok {
+		npos := ctx.Position(pos)
+		ctx.handleCodeErrorf(&npos, "%v redeclared\n\t%v other declaration of %v",
+			name, ctx.Position(opos), name)
+		return true
+	}
+	p.names[name] = pos
+	return false
+}
+
 func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 	pkg := ctx.pkg.Types
 	fieldList := v.Fields.List
 	fields := make([]*types.Var, 0, len(fieldList))
 	tags := make([]string, 0, len(fieldList))
-	names := make(map[string]token.Pos)
-	chkRedecl := func(name string, pos token.Pos) bool {
-		if name == "_" {
-			return false
-		}
-		if opos, ok := names[name]; ok {
-			npos := ctx.Position(pos)
-			ctx.handleCodeErrorf(&npos, "%v redeclared\n\t%v other declaration of %v",
-				name, ctx.Position(opos), name)
-			return true
-		}
-		names[name] = pos
-		return false
-	}
+	chk := newCheckRedecl()
 	for _, field := range fieldList {
 		typ := toType(ctx, field.Type)
 		if len(field.Names) == 0 { // embedded
 			name := getTypeName(typ)
-			if chkRedecl(name, field.Type.Pos()) {
+			if chk.chkRedecl(ctx, name, field.Type.Pos()) {
 				continue
 			}
 			if t, ok := typ.(*types.Named); ok { // #1196: embedded type should ensure loaded
@@ -277,7 +288,7 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 			continue
 		}
 		for _, name := range field.Names {
-			if chkRedecl(name.Name, name.NamePos) {
+			if chk.chkRedecl(ctx, name.Name, name.NamePos) {
 				continue
 			}
 			fld := types.NewField(token.NoPos, pkg, name.Name, typ, false)
diff --git a/parser/interface.go b/parser/interface.go
index a0fd6bff..ef62b016 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -26,7 +26,6 @@ import (
 // A Mode value is a set of flags (or 0).
 // They control the amount of source code parsed and other optional
 // parser functionality.
-//
 type Mode uint
 
 const (
@@ -44,6 +43,8 @@ const (
 	AllErrors
 	// ParseGoAsGoPlus - parse Go files by gop/parser
 	ParseGoAsGoPlus
+	// ParserGoPlusClass - parse Go+ classfile by gop/parser
+	ParseGoPlusClass
 )
 
 // ParseFile parses the source code of a single Go source file and returns
@@ -64,7 +65,6 @@ const (
 // 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 parseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
 	if fset == nil {
 		panic("parser.ParseFile: no token.FileSet provided (fset == nil)")
diff --git a/parser/parser.go b/parser/parser.go
index 3d6f9fc0..75d3709b 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -3364,7 +3364,13 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota
 	switch keyword {
 	case token.VAR:
 		if typ == nil && values == nil {
-			p.error(pos, "missing variable type or initialization")
+			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")
+			}
 		}
 	case token.CONST:
 		if values == nil && (iota == 0 || typ != nil) {
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 5b80f460..8642ecd4 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -99,7 +99,6 @@ func astFileToPkg(file *ast.File, fileName string) (pkg *ast.Package) {
 // -----------------------------------------------------------------------------
 
 // ParseDir calls ParseFSDir by passing a local filesystem.
-//
 func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) {
 	return ParseFSDir(fset, local, path, Config{Filter: filter, Mode: mode})
 }
@@ -111,7 +110,6 @@ type Config struct {
 }
 
 // ParseDirEx calls ParseFSDir by passing a local filesystem.
-//
 func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]*ast.Package, first error) {
 	return ParseFSDir(fset, local, path, conf)
 }
@@ -128,7 +126,6 @@ func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]
 // If the directory couldn't be read, a nil map and the respective error are
 // returned. If a parse error occurred, a non-nil but incomplete map and the
 // first error encountered are returned.
-//
 func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (pkgs map[string]*ast.Package, first error) {
 	list, err := fs.ReadDir(path)
 	if err != nil {
@@ -152,11 +149,17 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 				continue
 			}
 			useGoParser = (conf.Mode & ParseGoAsGoPlus) == 0
+		case ".gopx":
+			isClass = true
 		default:
 			if isProj, isClass = conf.IsClass(ext); !isClass {
 				continue
 			}
 		}
+		mode := conf.Mode
+		if isProj || isClass {
+			mode |= ParseGoPlusClass
+		}
 		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || conf.Filter(d)) {
 			filename := fs.Join(path, fname)
 			if useGoParser {
@@ -173,7 +176,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 				} else {
 					first = err
 				}
-			} else if src, err := ParseFSFile(fset, fs, filename, nil, conf.Mode); err == nil {
+			} else if src, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
 				src.IsProj, src.IsClass = isProj, isClass
 				pkg := reqPkg(pkgs, src.Name.Name)
 				pkg.Files[filename] = src


Test patch:
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 41d14455..5cf85672 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -121,7 +121,7 @@ var (
 println "hi"
 `, "Game.tgmx", "Kai.tspx")
 
-	gopSpxErrorTestEx(t, `./Kai.tspx:3:2: missing field type in class file`, `
+	gopSpxErrorTestEx(t, `./Kai.tspx:3:2: cannot assign value to field in class file`, `
 var (
 	Kai Kai
 	userScore int
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 46076cf0..e799c9cf 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -68,6 +68,11 @@ func gopClTest(t *testing.T, gopcode, expected string) {
 	gopClTestEx(t, gblConf, "main", gopcode, expected)
 }
 
+func gopClTestFile(t *testing.T, gopcode, expected string, fname string) {
+	fs := parsertest.NewSingleFileFS("/foo", fname, gopcode)
+	gopClTestFS(t, gblConf, fs, "main", expected)
+}
+
 func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) {
 	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", gopcode)
 	gopClTestFS(t, conf, fs, pkgname, expected)
@@ -4308,3 +4313,41 @@ func main() {
 }
 `)
 }
+
+func TestClassFileGopx(t *testing.T) {
+	gopClTestFile(t, `
+var (
+	BaseClass
+	Width, Height float64
+	AggClass
+)
+
+type BaseClass struct{
+	x int
+	y int
+}
+type AggClass struct{}
+
+func Area() float64 {
+	return Width * Height
+}
+`, `package main
+
+type BaseClass struct {
+	x int
+	y int
+}
+type AggClass struct {
+}
+type Rect struct {
+	BaseClass
+	Width  float64
+	Height float64
+	AggClass
+}
+
+func (this *Rect) Area() float64 {
+	return this.Width * this.Height
+}
+`, "Rect.gopx")
+}
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 19661867..31085b14 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -30,11 +30,11 @@ import (
 )
 
 func codeErrorTest(t *testing.T, msg, src string) {
-	codeErrorTestEx(t, "main", msg, src)
+	codeErrorTestEx(t, "main", "bar.gop", msg, src)
 }
 
-func codeErrorTestEx(t *testing.T, pkgname, msg, src string) {
-	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", src)
+func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) {
+	fs := parsertest.NewSingleFileFS("/foo", filename, src)
 	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
 	if err != nil {
 		scanner.PrintError(os.Stderr, err)
@@ -684,7 +684,7 @@ func TestErrNoEntrypoint(t *testing.T) {
 		`./bar.gop:1:9: undefined: abc`,
 		`println abc
 `)
-	codeErrorTestEx(t, "bar",
+	codeErrorTestEx(t, "bar", "bar.gop",
 		`./bar.gop:2:9: undefined: abc`,
 		`package bar
 println abc
@@ -867,6 +867,27 @@ 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`, `
+var (
+	A
+	i int
+	A
+)
+type A struct{}
+println "hello"
+`)
+}
+
 func TestErrVarInFunc(t *testing.T) {
 	codeErrorTest(t, `./bar.gop:6:10: too few arguments in call to set("box")
 	have (untyped string)
diff --git a/parser/_testdata/gopxtest/bar.gopx b/parser/_testdata/gopxtest/bar.gopx
new file mode 100644
index 00000000..783e9630
--- /dev/null
+++ b/parser/_testdata/gopxtest/bar.gopx
@@ -0,0 +1,9 @@
+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
new file mode 100644
index 00000000..a4a5dabf
--- /dev/null
+++ b/parser/_testdata/gopxtest/parser.expect
@@ -0,0 +1,43 @@
+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/parser_test.go b/parser/parser_test.go
index c8995720..d96c6c86 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -50,6 +50,21 @@ func testErrCode(t *testing.T, code string, errExp, panicExp string) {
 	}
 }
 
+func testClassErrCode(t *testing.T, code string, errExp, panicExp string) {
+	defer func() {
+		if e := recover(); e != nil {
+			if panicMsg(e) != panicExp {
+				t.Fatal("testErrCode panic:", e)
+			}
+		}
+	}()
+	fset := token.NewFileSet()
+	_, err := Parse(fset, "/foo/bar.gopx", code, ParseGoPlusClass)
+	if err == nil || err.Error() != errExp {
+		t.Fatal("testErrCode error:", err)
+	}
+}
+
 func TestErrTuple(t *testing.T) {
 	testErrCode(t, `println (1,2)*2`, `/foo/bar.gop:1:9: tuple is not supported`, ``)
 	testErrCode(t, `println 2*(1,2)`, `/foo/bar.gop:1:13: expected ')', found ','`, ``)
@@ -375,4 +390,12 @@ ast.FuncDecl:
 `)
 }
 
+func TestClassErrCode(t *testing.T) {
+	testClassErrCode(t, `var (
+	A,B
+	v int
+)
+`, `/foo/bar.gopx:2:2: syntax error: unexpected newline, expected type`, ``)
+}
+
 // -----------------------------------------------------------------------------