Repository: goplus/gop
Pull request #2301 (2025-04-21 20:45:44)

embed work classes for project class; init project instance in work class


Patch:
diff --git a/cl/classfile.go b/cl/classfile.go
index 964b6b00..7f7a8380 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -24,6 +24,7 @@ import (
 	"log"
 	"path"
 	"path/filepath"
+	"strconv"
 	"strings"
 
 	"github.com/goplus/gogen"
@@ -51,12 +52,12 @@ func (p *gmxClass) getName(ctx *pkgCtx) string {
 }
 
 type spxObj struct {
-	obj   gogen.Ref
+	obj   gogen.Ref // work base class
 	ext   string
-	proto string
-	feats spriteFeat
+	proto string           // work class prototype
+	feats spriteFeat       // work class features
 	clone *types.Signature // prototype of Classclone
-	types []string         // <type>.spx
+	types []string         // work classes (ie. <type>.spx)
 }
 
 func spriteByProto(sprites []*spxObj, proto string) *spxObj {
@@ -84,6 +85,19 @@ type gmxProject struct {
 	hasMain_   bool
 }
 
+func (p *gmxProject) embed(flds []*types.Var, pkg *gogen.Package) []*types.Var {
+	for _, sp := range p.sprites {
+		if sp.feats&spriteEmbedded != 0 {
+			for _, spt := range sp.types {
+				spto := pkg.Ref(spt)                // work class
+				pt := types.NewPointer(spto.Type()) // pointer to work class
+				flds = append(flds, types.NewField(token.NoPos, pkg.Types, spto.Name(), pt, true))
+			}
+		}
+	}
+	return flds
+}
+
 func (p *gmxProject) spriteOf(ext string) *spxObj {
 	for _, sp := range p.sprites {
 		if sp.ext == ext {
@@ -98,6 +112,8 @@ type spriteFeat uint
 const (
 	spriteClassfname spriteFeat = 1 << iota
 	spriteClassclone
+
+	spriteEmbedded spriteFeat = 0x80
 )
 
 func spriteFeature(elt types.Type, sp *spxObj) {
@@ -145,12 +161,12 @@ func spriteFeatures(game gogen.Ref, sprites []*spxObj) {
 }
 
 func (p *gmxProject) getGameClass(ctx *pkgCtx) string {
-	tname := p.gameClass_
+	tname := p.gameClass_ // project class
 	if tname != "" && tname != "main" {
 		return tname
 	}
 	gt := p.gt
-	tname = gt.Class
+	tname = gt.Class // project base class
 	if p.gameIsPtr {
 		tname = tname[1:]
 	}
@@ -264,11 +280,15 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 		nWork := len(gt.Works)
 		sprites := make([]*spxObj, nWork)
 		for i, v := range gt.Works {
-			obj, _ := spxRef(spx, v.Class)
-			sprites[i] = &spxObj{obj: obj, ext: v.Ext, proto: v.Proto}
 			if nWork > 1 && v.Proto == "" {
 				panic("should have prototype if there are multiple work classes")
 			}
+			obj, _ := spxRef(spx, v.Class)
+			sp := &spxObj{obj: obj, ext: v.Ext, proto: v.Proto}
+			if v.Embedded {
+				sp.feats |= spriteEmbedded
+			}
+			sprites[i] = sp
 		}
 		p.sprites = sprites
 		if gt.Class != "" {
@@ -410,10 +430,10 @@ func gmxCheckProjs(pkg *gogen.Package, ctx *pkgCtx) (*gmxProject, bool) {
 }
 
 func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
-	base := proj.game
-	classType := proj.getGameClass(parent)
+	base := proj.game                      // project base class
+	classType := proj.getGameClass(parent) // project class
 	ld := getTypeLoader(parent, parent.syms, token.NoPos, classType)
-	if ld.typ == nil {
+	if ld.typ == nil { // no project class, use default
 		ld.typ = func() {
 			if debugLoad {
 				log.Println("==> Load > NewType", classType)
@@ -425,10 +445,11 @@ func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
 			if proj.gameIsPtr {
 				baseType = types.NewPointer(baseType)
 			}
-			name := base.Name()
-			flds := []*types.Var{
-				types.NewField(token.NoPos, pkg.Types, name, baseType, true),
-			}
+
+			flds := proj.embed([]*types.Var{
+				types.NewField(token.NoPos, pkg.Types, base.Name(), baseType, true),
+			}, pkg)
+
 			decl := pkg.NewTypeDefs().NewType(classType)
 			ld.typInit = func() { // decycle
 				if debugLoad {
@@ -456,45 +477,53 @@ func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
 			defer pkg.RestoreCurFile(old)
 
 			cb := fn.BodyStart(pkg).Typ(base.Type()).MemberVal("Main")
+			stk := cb.InternalStack()
 
 			// force remove //line comments for main func
 			cb.SetComments(nil, false)
 
-			sigParams := cb.Get(-1).Type.(*types.Signature).Params()
-			if _, ok := sigParams.At(0).Type().(*types.Pointer); !ok {
-				cb.Val(recv) // template recv method
-			} else {
-				cb.Val(recv).MemberRef(base.Name()).UnaryOp(gotoken.AND)
+			mainFn := stk.Pop()
+			sigParams := mainFn.Type.(*types.Signature).Params()
+			callMain := func() {
+				stk.Push(mainFn)
+				if _, isPtr := sigParams.At(0).Type().(*types.Pointer); isPtr {
+					cb.Val(recv).MemberRef(base.Name()).UnaryOp(gotoken.AND)
+				} else {
+					cb.Val(recv) // template recv method
+				}
 			}
 
+			iobj := 0
 			narg := sigParams.Len()
 			if narg > 1 {
 				sprites := proj.sprites
-				if len(sprites) == 1 && sprites[0].proto == "" {
+				if len(sprites) == 1 && sprites[0].proto == "" { // no work class prototype
 					sp := sprites[0]
 					narg = 1 + len(sp.types)
-					new := pkg.Builtin().Ref("new")
-					for _, spt := range sp.types {
-						spto := pkg.Ref(spt)
-						cb.Val(new).Val(spto).Call(1)
-					}
+					genWorkClasses(pkg, cb, recv, sp, iobj, -1, callMain)
 				} else {
-					new := pkg.Builtin().Ref("new")
+					lstNames := make([]string, narg)
 					for i := 1; i < narg; i++ {
 						tslice := sigParams.At(i).Type()
 						tn := tslice.(*types.Slice).Elem().(*types.Named)
-						sp := spriteByProto(sprites, tn.Obj().Name())
+						sp := spriteByProto(sprites, tn.Obj().Name()) // work class
 						if n := len(sp.types); n > 0 {
-							for _, spt := range sp.types {
-								spto := pkg.Ref(spt)
-								cb.Val(new).Val(spto).Call(1)
-							}
-							cb.SliceLitEx(tslice, n, false)
+							lstNames[i] = genWorkClasses(pkg, cb, recv, sp, iobj, i, nil)
+							cb.SliceLitEx(tslice, n, false).EndInit(1)
+							iobj += n
+						}
+					}
+					callMain()
+					for i := 1; i < narg; i++ {
+						if lstName := lstNames[i]; lstName != "" {
+							cb.VarVal(lstName)
 						} else {
 							cb.Val(nil)
 						}
 					}
 				}
+			} else {
+				callMain()
 			}
 
 			cb.Call(narg).EndStmt().End()
@@ -502,6 +531,39 @@ func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
 	})
 }
 
+func genWorkClasses(
+	pkg *gogen.Package, cb *gogen.CodeBuilder, recv *types.Var,
+	sp *spxObj, iobj, ilst int, callMain func()) (lstName string) {
+	const (
+		indexGame     = 1
+		objNamePrefix = "_gop_obj"
+		lstNamePrefix = "_gop_lst"
+	)
+	embedded := (sp.feats&spriteEmbedded != 0)
+	sptypes := sp.types
+	for i, spt := range sptypes {
+		spto := pkg.Ref(spt)
+		objName := objNamePrefix + strconv.Itoa(iobj+i)
+		cb.DefineVarStart(token.NoPos, objName).
+			Val(indexGame).Val(recv).StructLit(spto.Type(), 2, true).
+			UnaryOp(gotoken.AND).EndInit(1)
+		if embedded {
+			cb.Val(recv).MemberRef(spt).VarVal(objName).Assign(1)
+		}
+	}
+	if ilst > 0 {
+		lstName = lstNamePrefix + strconv.Itoa(ilst-1)
+		cb.DefineVarStart(token.NoPos, lstName)
+	} else {
+		callMain()
+	}
+	for i := range sptypes {
+		objName := objNamePrefix + strconv.Itoa(iobj+i)
+		cb.VarVal(objName)
+	}
+	return
+}
+
 func genMainFunc(pkg *gogen.Package, gameClass string) {
 	if o := pkg.TryRef(gameClass); o != nil {
 		// new(gameClass).Main()
@@ -528,17 +590,16 @@ func findMethod(o types.Object, name string) *types.Func {
 
 func makeMainSig(recv *types.Var, f *types.Func) *types.Signature {
 	const (
-		paramNameTempl = "_gop_arg0"
+		namePrefix = "_gop_arg"
 	)
 	sig := f.Type().(*types.Signature)
 	in := sig.Params()
 	nin := in.Len()
 	pkg := recv.Pkg()
 	params := make([]*types.Var, nin)
-	paramName := []byte(paramNameTempl)
 	for i := 0; i < nin; i++ {
-		paramName[len(paramNameTempl)-1] = byte('0' + i)
-		params[i] = types.NewParam(token.NoPos, pkg, string(paramName), in.At(i).Type())
+		paramName := namePrefix + strconv.Itoa(i)
+		params[i] = types.NewParam(token.NoPos, pkg, paramName, in.At(i).Type())
 	}
 	return types.NewSignatureType(recv, nil, nil, types.NewTuple(params...), sig.Results(), false)
 }
diff --git a/cl/compile.go b/cl/compile.go
index 385b6e22..b3400f06 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -803,22 +803,24 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 				var flds []*types.Var
 				var tags []string
 				chk := newCheckRedecl()
-				if baseTypeName != "" {
+				if baseTypeName != "" { // base class (not normal classfile)
 					flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true))
 					tags = append(tags, "")
 					chk.chkRedecl(ctx, baseTypeName, pos)
-				}
-				if sp != nil && !goxTestFile {
-					if gameClass != "" {
-						typ := toType(ctx, &ast.Ident{Name: gameClass})
-						getUnderlying(ctx, typ) // ensure type is loaded
-						typ = types.NewPointer(typ)
-						name := getTypeName(typ)
-						if !chk.chkRedecl(ctx, name, pos) {
-							fld := types.NewField(pos, pkg, name, typ, true)
-							flds = append(flds, fld)
-							tags = append(tags, "")
+					if sp != nil { // for work class
+						if !goxTestFile && gameClass != "" { // has project class
+							typ := toType(ctx, &ast.Ident{Name: gameClass})
+							getUnderlying(ctx, typ) // ensure type is loaded
+							typ = types.NewPointer(typ)
+							name := getTypeName(typ)
+							if !chk.chkRedecl(ctx, name, pos) {
+								fld := types.NewField(pos, pkg, name, typ, true)
+								flds = append(flds, fld)
+								tags = append(tags, "")
+							}
 						}
+					} else { // embed work classes for project class
+						flds = proj.embed(flds, p)
 					}
 				}
 				rec := ctx.recorder()


Test patch:
diff --git a/cl/_testspx/multiworks/out.go b/cl/_testspx/multiworks/out.go
index 33c5326a..97546d45 100644
--- a/cl/_testspx/multiworks/out.go
+++ b/cl/_testspx/multiworks/out.go
@@ -12,13 +12,19 @@ type hello struct {
 }
 type Game struct {
 	mcp.Game
+	*foo
 }
 
 func (this *Game) MainEntry() {
 	this.Server("protos")
 }
 func (this *Game) Main() {
-	mcp.Gopt_Game_Main(this, nil, []mcp.ToolProto{new(hello)}, []mcp.PromptProto{new(foo)})
+	_gop_obj0 := &hello{Game: this}
+	_gop_lst1 := []mcp.ToolProto{_gop_obj0}
+	_gop_obj1 := &foo{Game: this}
+	this.foo = _gop_obj1
+	_gop_lst2 := []mcp.PromptProto{_gop_obj1}
+	mcp.Gopt_Game_Main(this, nil, _gop_lst1, _gop_lst2)
 }
 func (this *foo) Main(_gop_arg0 *mcp.Tool) string {
 	this.Prompt.Main(_gop_arg0)
diff --git a/cl/_testspx/newobj/Kai_spx.gox b/cl/_testspx/newobj/Kai_spx.gox
new file mode 100644
index 00000000..e69de29b
diff --git a/cl/_testspx/newobj/main_spx.gox b/cl/_testspx/newobj/main_spx.gox
new file mode 100644
index 00000000..11211982
--- /dev/null
+++ b/cl/_testspx/newobj/main_spx.gox
@@ -0,0 +1,4 @@
+a := new
+a.run
+b := new(Sprite)
+echo b.name
diff --git a/cl/_testspx/newobj/out.go b/cl/_testspx/newobj/out.go
new file mode 100644
index 00000000..877f203c
--- /dev/null
+++ b/cl/_testspx/newobj/out.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx3"
+)
+
+type Kai struct {
+	spx3.Sprite
+	*Game
+}
+type Game struct {
+	spx3.Game
+}
+
+func (this *Game) MainEntry() {
+	a := spx3.New()
+	a.Run()
+	b := new(spx3.Sprite)
+	fmt.Println(b.Name())
+}
+func (this *Game) Main() {
+	_gop_obj0 := &Kai{Game: this}
+	spx3.Gopt_Game_Main(this, _gop_obj0)
+}
+func (this *Kai) Main(_gop_arg0 string) {
+	this.Sprite.Main(_gop_arg0)
+}
+func (this *Kai) Classfname() string {
+	return "Kai"
+}
+func (this *Kai) Classclone() spx3.Handler {
+	_gop_ret := *this
+	return &_gop_ret
+}
+func main() {
+	new(Game).Main()
+}
diff --git a/cl/_testspx/singlework/out.go b/cl/_testspx/singlework/out.go
index 5cd9b950..158ca9db 100644
--- a/cl/_testspx/singlework/out.go
+++ b/cl/_testspx/singlework/out.go
@@ -19,7 +19,8 @@ func (this *Game) MainEntry() {
 	this.Run()
 }
 func (this *Game) Main() {
-	spx3.Gopt_Game_Main(this, new(Kai))
+	_gop_obj0 := &Kai{Game: this}
+	spx3.Gopt_Game_Main(this, _gop_obj0)
 }
 func (this *Kai) Main(_gop_arg0 string) {
 	this.Sprite.Main(_gop_arg0)
diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go
index c03c5634..d8bc5a0c 100644
--- a/cl/cltest/cltest.go
+++ b/cl/cltest/cltest.go
@@ -100,7 +100,7 @@ func LookupClass(ext string) (c *modfile.Project, ok bool) {
 			Ext: "_mcp.gox", Class: "Game",
 			Works: []*modfile.Class{
 				{Ext: "_tool.gox", Class: "Tool", Proto: "ToolProto"},
-				{Ext: "_prompt.gox", Class: "Prompt", Proto: "PromptProto"},
+				{Ext: "_prompt.gox", Class: "Prompt", Proto: "PromptProto", Embedded: true},
 				{Ext: "_res.gox", Class: "Resource", Proto: "ResourceProto"},
 			},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/mcp"}}, true
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 1e45651d..fffc0c88 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -391,52 +391,6 @@ func main() {
 `, "index.tgmx", "Kai.tspx")
 }
 
-func TestSpxNewObj(t *testing.T) {
-	gopSpxTestEx(t, `
-a := new
-a.run
-b := new(Sprite)
-echo b.name
-`, ``, `package main
-
-import (
-	"fmt"
-	"github.com/goplus/gop/cl/internal/spx3"
-)
-
-type Kai struct {
-	spx3.Sprite
-	*Game
-}
-type Game struct {
-	spx3.Game
-}
-
-func (this *Game) MainEntry() {
-	a := spx3.New()
-	a.Run()
-	b := new(spx3.Sprite)
-	fmt.Println(b.Name())
-}
-func (this *Game) Main() {
-	spx3.Gopt_Game_Main(this, new(Kai))
-}
-func (this *Kai) Main(_gop_arg0 string) {
-	this.Sprite.Main(_gop_arg0)
-}
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
-func (this *Kai) Classclone() spx3.Handler {
-	_gop_ret := *this
-	return &_gop_ret
-}
-func main() {
-	new(Game).Main()
-}
-`, "main_spx.gox", "Kai_spx.gox")
-}
-
 func TestSpx2(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi")