From c893ccb87de414da305628dfa8e0e76ebfd24ef5 Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 21 Nov 2024 19:59:43 +0800 Subject: [PATCH 1/6] x/format: XGoClassSource --- x/format/gopstyle.go | 112 +++++++++++++++++++++++++++++++++- x/format/stmt_expr_or_type.go | 32 +++++++++- 2 files changed, 140 insertions(+), 4 deletions(-) diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go index cbbd5eb4c..e5b1299ca 100644 --- a/x/format/gopstyle.go +++ b/x/format/gopstyle.go @@ -83,6 +83,28 @@ func Gopstyle(file *ast.File) { XGoStyle(file) } +func XGoClassSource(src []byte, pkg string, class string, entry string, filename ...string) (ret []byte, err error) { + var fname string + if filename != nil { + fname = filename[0] + } + fset := token.NewFileSet() + var f *ast.File + if f, err = parser.ParseFile(fset, fname, src, parser.ParseComments); err == nil { + XGoClass(f, pkg, class, entry) + var buf bytes.Buffer + if err = format.Node(&buf, fset, f); err == nil { + ret = buf.Bytes() + } + } + return +} + +// GopClass format ast.File to Go+ class +func XGoClass(file *ast.File, pkg string, class string, entry string) { + formatClass(file, pkg, class, entry) +} + func findFuncDecl(decls []ast.Decl, name string) (int, *ast.FuncDecl) { for i, decl := range decls { if fn, ok := decl.(*ast.FuncDecl); ok { @@ -156,8 +178,12 @@ type importCtx struct { } type formatCtx struct { - imports map[string]*importCtx - scope *types.Scope + imports map[string]*importCtx + scope *types.Scope + classMode bool //class mode + classPkg string //class pkg name + className string //this class + funcRecv string //this class func recv } func (ctx *formatCtx) insert(name string) { @@ -223,6 +249,60 @@ func formatFile(file *ast.File) { } } +func formatClass(file *ast.File, pkg string, class string, entry string) { + var funcs []*ast.FuncDecl + ctx := &formatCtx{ + imports: make(map[string]*importCtx), + scope: types.NewScope(nil, token.NoPos, token.NoPos, ""), + classMode: true, + classPkg: path.Base(pkg), + className: class, + } + for _, decl := range file.Decls { + switch v := decl.(type) { + case *ast.FuncDecl: + // delay the process, because package level vars need to be processed first. + funcs = append(funcs, v) + if isClassFunc(v, class) && v.Name.Name == entry { + file.ShadowEntry = v + } + case *ast.GenDecl: + switch v.Tok { + case token.IMPORT: + for _, item := range v.Specs { + var spec = item.(*ast.ImportSpec) + var pkgPath = toString(spec.Path) + var name string + if spec.Name == nil { + name = path.Base(pkgPath) // TODO: open pkgPath to get pkgName + } else { + name = spec.Name.Name + if name == "." || name == "_" { + continue + } + } + ctx.imports[name] = &importCtx{pkgPath: pkgPath, decl: v, spec: spec} + } + default: + formatGenDecl(ctx, v) + } + } + } + + for _, fn := range funcs { + formatFuncDecl(ctx, fn) + } + for _, imp := range ctx.imports { + if imp.pkgPath == "fmt" && !imp.isUsed { + if len(imp.decl.Specs) == 1 { + file.Decls = deleteDecl(file.Decls, imp.decl) + } else { + imp.decl.Specs = deleteSpec(imp.decl.Specs, imp.spec) + } + } + } +} + func formatGenDecl(ctx *formatCtx, v *ast.GenDecl) { switch v.Tok { case token.VAR, token.CONST: @@ -242,7 +322,35 @@ func formatGenDecl(ctx *formatCtx, v *ast.GenDecl) { } } +func isClassFunc(v *ast.FuncDecl, className string) bool { + if v.Recv != nil && len(v.Recv.List) == 1 { + typ := v.Recv.List[0].Type + if star, ok := typ.(*ast.StarExpr); ok { + typ = star.X + } + if ident, ok := typ.(*ast.Ident); ok && ident.Name == className { + return true + } + } + return false +} + +func funcRecv(v *ast.FuncDecl) *ast.Ident { + if v.Recv != nil && len(v.Recv.List) == 1 && len(v.Recv.List[0].Names) == 1 { + return v.Recv.List[0].Names[0] + } + return nil +} + func formatFuncDecl(ctx *formatCtx, v *ast.FuncDecl) { + if ctx.classMode && isClassFunc(v, ctx.className) { + if recv := funcRecv(v); recv != nil { + ctx.funcRecv = recv.Name + defer func() { + ctx.funcRecv = "" + }() + } + } formatFuncType(ctx, v.Type) formatBlockStmt(ctx, v.Body) } diff --git a/x/format/stmt_expr_or_type.go b/x/format/stmt_expr_or_type.go index eccebacf1..1dc38bf80 100644 --- a/x/format/stmt_expr_or_type.go +++ b/x/format/stmt_expr_or_type.go @@ -166,13 +166,13 @@ func formatSliceExpr(ctx *formatCtx, v *ast.SliceExpr) { } func formatCallExpr(ctx *formatCtx, v *ast.CallExpr) { - formatExpr(ctx, v.Fun, &v.Fun) fncallStartingLowerCase(v) for i, arg := range v.Args { if fn, ok := arg.(*ast.FuncLit); ok { funcLitToLambdaExpr(fn, &v.Args[i]) } } + formatExpr(ctx, v.Fun, &v.Fun) formatExprs(ctx, v.Args) } @@ -182,6 +182,10 @@ func formatSelectorExpr(ctx *formatCtx, v *ast.SelectorExpr, ref *ast.Expr) { if _, o := ctx.scope.LookupParent(x.Name, token.NoPos); o != nil { break } + if ctx.classMode && (x.Name == ctx.funcRecv || x.Name == ctx.classPkg) { + *ref = v.Sel + break + } if imp, ok := ctx.imports[x.Name]; ok { if !fmtToBuiltin(imp, v.Sel, ref) { imp.isUsed = true @@ -202,8 +206,25 @@ func formatBlockStmt(ctx *formatCtx, stmt *ast.BlockStmt) { } } +func isClassSched(ctx *formatCtx, stmt ast.Stmt) bool { + if expr, ok := stmt.(*ast.ExprStmt); ok { + if v, ok := expr.X.(*ast.CallExpr); ok { + if sel, ok := v.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "Sched" { + if ident, ok := sel.X.(*ast.Ident); ok && ident.Name == ctx.classPkg { + return true + } + } + } + } + return false +} + func formatStmts(ctx *formatCtx, stmts []ast.Stmt) { - for _, stmt := range stmts { + for i, stmt := range stmts { + if ctx.classMode && isClassSched(ctx, stmt) { + stmts[i] = &ast.EmptyStmt{} + continue + } formatStmt(ctx, stmt) } } @@ -262,6 +283,13 @@ func formatStmt(ctx *formatCtx, stmt ast.Stmt) { func formatExprStmt(ctx *formatCtx, v *ast.ExprStmt) { switch x := v.X.(type) { case *ast.CallExpr: + if ctx.classMode { + if sel, ok := x.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "Sched" { + if ident, ok := sel.X.(*ast.Ident); ok && ident.Name == ctx.classPkg { + return + } + } + } commandStyleFirst(x) } formatExpr(ctx, v.X, &v.X) From d4ac34da40648bf8dd579febebb1d28caf63d5a2 Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 21 Nov 2024 21:03:53 +0800 Subject: [PATCH 2/6] x/format: TestClassSpx --- x/format/gopclass_test.go | 70 +++++++++++++++++++++++++++++++++++++++ x/format/gopstyle.go | 49 +++++++++++++++++++++++++-- 2 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 x/format/gopclass_test.go diff --git a/x/format/gopclass_test.go b/x/format/gopclass_test.go new file mode 100644 index 000000000..b5ca7a89d --- /dev/null +++ b/x/format/gopclass_test.go @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package format_test + +import ( + "testing" + + "github.com/goplus/gop/x/format" +) + +func testClass(t *testing.T, name string, pkg string, class string, entry string, src, expect string) { + t.Run(name, func(t *testing.T) { + result, err := format.GopClassSource([]byte(src), pkg, class, entry, name) + if err != nil { + t.Fatal("format.GopClassSource failed:", err) + } + if ret := string(result); ret != expect { + t.Fatalf("%s => Expect:\n%s\n=> Got:\n%s\n", name, expect, ret) + } + }) +} + +func TestClassSpx(t *testing.T) { + testClass(t, "spx class", "github.com/goplus/spx", "Calf", "Main", `package main + +type Calf struct { + spx.Sprite + *Game + index int + info string +} +func (this *Calf) Update() { + this.index++ +} +func (this *Calf) Main() { + this.OnStart(func() { + this.Say("Hello Go+") + }) +} +func (this *Calf) Classfname() string { + return "Calf" +} +`, `var ( + index int + info string +) + +func Update() { + index++ +} + +onStart func() { + say "Hello Go+" +} +`) +} diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go index e5b1299ca..834e48eb3 100644 --- a/x/format/gopstyle.go +++ b/x/format/gopstyle.go @@ -258,14 +258,56 @@ func formatClass(file *ast.File, pkg string, class string, entry string) { classPkg: path.Base(pkg), className: class, } + if file.Name.Name == "main" { + file.NoPkgDecl = true + } + var fnEntry *ast.FuncDecl + var decls []ast.Decl + var varSpecs []ast.Spec + for _, decl := range file.Decls { + switch v := decl.(type) { + case *ast.FuncDecl: + if isClassFunc(v, class) { + v.IsClass = true + switch v.Name.Name { + case entry: + fnEntry = v + file.ShadowEntry = v + continue + case "Classfname": + v.Shadow = true + } + } + case *ast.GenDecl: + if v.Tok == token.TYPE { + if spec, ok := v.Specs[0].(*ast.TypeSpec); ok && spec.Name.Name == class { + if st, ok := spec.Type.(*ast.StructType); ok { + for _, fs := range st.Fields.List { + if len(fs.Names) == 0 { + continue + } + varSpecs = append(varSpecs, &ast.ValueSpec{Names: fs.Names, Type: fs.Type}) + } + continue + } + } + } + } + decls = append(decls, decl) + } + if len(varSpecs) != 0 { + decls = append([]ast.Decl{&ast.GenDecl{Tok: token.VAR, Specs: varSpecs}}, decls...) + } + if fnEntry != nil { + decls = append(decls, fnEntry) + } + file.Decls = decls + for _, decl := range file.Decls { switch v := decl.(type) { case *ast.FuncDecl: // delay the process, because package level vars need to be processed first. funcs = append(funcs, v) - if isClassFunc(v, class) && v.Name.Name == entry { - file.ShadowEntry = v - } case *ast.GenDecl: switch v.Tok { case token.IMPORT: @@ -344,6 +386,7 @@ func funcRecv(v *ast.FuncDecl) *ast.Ident { func formatFuncDecl(ctx *formatCtx, v *ast.FuncDecl) { if ctx.classMode && isClassFunc(v, ctx.className) { + v.IsClass = true if recv := funcRecv(v); recv != nil { ctx.funcRecv = recv.Name defer func() { From 467c51e6efb8153f833664485a872a8703ab2b67 Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 21 Nov 2024 21:36:49 +0800 Subject: [PATCH 3/6] x/format: GopClass update imports --- x/format/gopclass_test.go | 20 +++++++++++++++++++- x/format/gopstyle.go | 17 ++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/x/format/gopclass_test.go b/x/format/gopclass_test.go index b5ca7a89d..e5bc4bc25 100644 --- a/x/format/gopclass_test.go +++ b/x/format/gopclass_test.go @@ -37,6 +37,12 @@ func testClass(t *testing.T, name string, pkg string, class string, entry string func TestClassSpx(t *testing.T) { testClass(t, "spx class", "github.com/goplus/spx", "Calf", "Main", `package main +import ( + "github.com/goplus/spx" + "fmt" + "log" +) + type Calf struct { spx.Sprite *Game @@ -46,6 +52,9 @@ type Calf struct { func (this *Calf) Update() { this.index++ } +func (this *Calf) Dump() { + log.Println(this.info) +} func (this *Calf) Main() { this.OnStart(func() { this.Say("Hello Go+") @@ -54,7 +63,11 @@ func (this *Calf) Main() { func (this *Calf) Classfname() string { return "Calf" } -`, `var ( +`, `import ( + "log" +) + +var ( index int info string ) @@ -63,8 +76,13 @@ func Update() { index++ } +func Dump() { + log.println info +} + onStart func() { say "Hello Go+" } `) + } diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go index 834e48eb3..cbec1cce8 100644 --- a/x/format/gopstyle.go +++ b/x/format/gopstyle.go @@ -263,6 +263,7 @@ func formatClass(file *ast.File, pkg string, class string, entry string) { } var fnEntry *ast.FuncDecl var decls []ast.Decl + var imports []ast.Decl var varSpecs []ast.Spec for _, decl := range file.Decls { switch v := decl.(type) { @@ -279,7 +280,11 @@ func formatClass(file *ast.File, pkg string, class string, entry string) { } } case *ast.GenDecl: - if v.Tok == token.TYPE { + switch v.Tok { + case token.IMPORT: + imports = append(imports, v) + continue + case token.TYPE: if spec, ok := v.Specs[0].(*ast.TypeSpec); ok && spec.Name.Name == class { if st, ok := spec.Type.(*ast.StructType); ok { for _, fs := range st.Fields.List { @@ -295,13 +300,15 @@ func formatClass(file *ast.File, pkg string, class string, entry string) { } decls = append(decls, decl) } + + file.Decls = imports if len(varSpecs) != 0 { - decls = append([]ast.Decl{&ast.GenDecl{Tok: token.VAR, Specs: varSpecs}}, decls...) + file.Decls = append(file.Decls, &ast.GenDecl{Tok: token.VAR, Specs: varSpecs}) } + file.Decls = append(file.Decls, decls...) if fnEntry != nil { - decls = append(decls, fnEntry) + file.Decls = append(file.Decls, fnEntry) } - file.Decls = decls for _, decl := range file.Decls { switch v := decl.(type) { @@ -335,7 +342,7 @@ func formatClass(file *ast.File, pkg string, class string, entry string) { formatFuncDecl(ctx, fn) } for _, imp := range ctx.imports { - if imp.pkgPath == "fmt" && !imp.isUsed { + if !imp.isUsed { if len(imp.decl.Specs) == 1 { file.Decls = deleteDecl(file.Decls, imp.decl) } else { From ec027d85a9b4c2b47184098fe59bd46789e71c1a Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 21 Nov 2024 21:57:43 +0800 Subject: [PATCH 4/6] x/format: GopClass check project --- x/format/gopclass_test.go | 121 ++++++++++++++++++++++++++++++++++++-- x/format/gopstyle.go | 33 +++++++---- 2 files changed, 138 insertions(+), 16 deletions(-) diff --git a/x/format/gopclass_test.go b/x/format/gopclass_test.go index e5bc4bc25..1dabcc992 100644 --- a/x/format/gopclass_test.go +++ b/x/format/gopclass_test.go @@ -19,14 +19,14 @@ package format_test import ( "testing" - "github.com/goplus/gop/x/format" + "github.com/goplus/xgo/x/format" ) -func testClass(t *testing.T, name string, pkg string, class string, entry string, src, expect string) { +func testClass(t *testing.T, name string, pkg string, class string, proj bool, src, expect string) { t.Run(name, func(t *testing.T) { - result, err := format.GopClassSource([]byte(src), pkg, class, entry, name) + result, err := format.XGoClassSource([]byte(src), pkg, class, proj, name) if err != nil { - t.Fatal("format.GopClassSource failed:", err) + t.Fatal("format.XGoClassSource failed:", err) } if ret := string(result); ret != expect { t.Fatalf("%s => Expect:\n%s\n=> Got:\n%s\n", name, expect, ret) @@ -35,7 +35,7 @@ func testClass(t *testing.T, name string, pkg string, class string, entry string } func TestClassSpx(t *testing.T) { - testClass(t, "spx class", "github.com/goplus/spx", "Calf", "Main", `package main + testClass(t, "spx class", "github.com/goplus/spx", "Calf", false, `package main import ( "github.com/goplus/spx" @@ -59,6 +59,35 @@ func (this *Calf) Main() { this.OnStart(func() { this.Say("Hello Go+") }) +//line Calf.spx:38:1 + this.OnMsg__1("tap", func() { +//line Calf.spx:39:1 + for calfPlay { + spx.Sched() +//line Calf.spx:40:1 + for !(this.KeyPressed(spx.KeySpace) || this.MousePressed()) { + spx.Sched() +//line Calf.spx:41:1 + this.Wait(0.01) + } +//line Calf.spx:43:1 + calfGravity = 0.8 +//line Calf.spx:44:1 + for +//line Calf.spx:44:1 + i := 0; i < 10; +//line Calf.spx:44:1 + i++ { + spx.Sched() +//line Calf.spx:45:1 + this.ChangeYpos(3.5) +//line Calf.spx:46:1 + this.Wait(0.03) + } +//line Calf.spx:48:1 + this.Wait(0.03) + } + }) } func (this *Calf) Classfname() string { return "Calf" @@ -83,6 +112,88 @@ func Dump() { onStart func() { say "Hello Go+" } + +onMsg__1 "tap", func() { + + for calfPlay { + + for !(keyPressed(KeySpace) || mousePressed()) { + + wait 0.01 + } + + calfGravity = 0.8 + + for i := 0; i < 10; i++ { + + changeYpos 3.5 + + wait 0.03 + } + + wait 0.03 + } +} `) +} + +func TestClassProj(t *testing.T) { + testClass(t, "spx project", "github.com/goplus/spx", "Game", true, `package main +import "github.com/goplus/spx" +import "log" + +type Game struct { + spx.Game + MyAircraft MyAircraft + Bullet Bullet +} + +var calfPlay = false +var calfDie = false +var calfGravity = 0.0 +//line main.spx:30:1 +func (this *Game) reset() { +//line main.spx:31:1 + this.userScore = 0 +//line main.spx:32:1 + calfPlay = false +//line main.spx:33:1 + calfDie = false +//line main.spx:34:1 + calfGravity = 0.0 +} +func (this *Game) MainEntry() { + log.Println("MainEntry") +} +func (this *Game) Main() { + spx.Gopt_Game_Main(this, new(Bullet), new(MyAircraft)) +} +func main() { + new(Game).Main() +} +`, `import "log" + +var ( + MyAircraft MyAircraft + Bullet Bullet +) + +var calfPlay = false +var calfDie = false +var calfGravity = 0.0 + +func reset() { + + userScore = 0 + + calfPlay = false + + calfDie = false + + calfGravity = 0.0 +} + +log.println "MainEntry" +`) } diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go index cbec1cce8..c97bb05fc 100644 --- a/x/format/gopstyle.go +++ b/x/format/gopstyle.go @@ -83,7 +83,7 @@ func Gopstyle(file *ast.File) { XGoStyle(file) } -func XGoClassSource(src []byte, pkg string, class string, entry string, filename ...string) (ret []byte, err error) { +func XGoClassSource(src []byte, pkg string, class string, prog bool, filename ...string) (ret []byte, err error) { var fname string if filename != nil { fname = filename[0] @@ -91,7 +91,7 @@ func XGoClassSource(src []byte, pkg string, class string, entry string, filename fset := token.NewFileSet() var f *ast.File if f, err = parser.ParseFile(fset, fname, src, parser.ParseComments); err == nil { - XGoClass(f, pkg, class, entry) + XGoClass(f, pkg, class, prog) var buf bytes.Buffer if err = format.Node(&buf, fset, f); err == nil { ret = buf.Bytes() @@ -100,9 +100,9 @@ func XGoClassSource(src []byte, pkg string, class string, entry string, filename return } -// GopClass format ast.File to Go+ class -func XGoClass(file *ast.File, pkg string, class string, entry string) { - formatClass(file, pkg, class, entry) +// XGoClass format ast.File to Go+ class +func XGoClass(file *ast.File, pkg string, class string, prog bool) { + formatClass(file, pkg, class, prog) } func findFuncDecl(decls []ast.Decl, name string) (int, *ast.FuncDecl) { @@ -249,7 +249,7 @@ func formatFile(file *ast.File) { } } -func formatClass(file *ast.File, pkg string, class string, entry string) { +func formatClass(file *ast.File, pkg string, class string, proj bool) { var funcs []*ast.FuncDecl ctx := &formatCtx{ imports: make(map[string]*importCtx), @@ -271,13 +271,24 @@ func formatClass(file *ast.File, pkg string, class string, entry string) { if isClassFunc(v, class) { v.IsClass = true switch v.Name.Name { - case entry: - fnEntry = v - file.ShadowEntry = v - continue + case "MainEntry": + if proj { + fnEntry = v + file.ShadowEntry = v + continue + } + case "Main": + if !proj { + fnEntry = v + file.ShadowEntry = v + continue + } + v.Shadow = true case "Classfname": v.Shadow = true } + } else if v.Name.Name == "main" && proj { + v.Shadow = true } case *ast.GenDecl: switch v.Tok { @@ -323,7 +334,7 @@ func formatClass(file *ast.File, pkg string, class string, entry string) { var pkgPath = toString(spec.Path) var name string if spec.Name == nil { - name = path.Base(pkgPath) // TODO: open pkgPath to get pkgName + name = path.Base(pkgPath) } else { name = spec.Name.Name if name == "." || name == "_" { From 06ab4b793265f9e293172c645376e857fd7e26b6 Mon Sep 17 00:00:00 2001 From: visualfc Date: Fri, 22 Nov 2024 09:44:20 +0800 Subject: [PATCH 5/6] x/format: GopClass support normal .gox --- x/format/gopclass_test.go | 40 +++++++++++++++++++++++++++++++++++++++ x/format/gopstyle.go | 6 +++--- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/x/format/gopclass_test.go b/x/format/gopclass_test.go index 1dabcc992..96ca0e35d 100644 --- a/x/format/gopclass_test.go +++ b/x/format/gopclass_test.go @@ -197,3 +197,43 @@ func reset() { log.println "MainEntry" `) } + +func TestClassGox(t *testing.T) { + testClass(t, "gox class", "", "Rect", false, `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 +} +`, `var ( + BaseClass + Width float64 + Height float64 + *AggClass +) + +type BaseClass struct { + x int + y int +} + +type AggClass struct { +} + +func Area() float64 { + return Width * Height +} +`) +} diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go index c97bb05fc..0f131405b 100644 --- a/x/format/gopstyle.go +++ b/x/format/gopstyle.go @@ -83,7 +83,7 @@ func Gopstyle(file *ast.File) { XGoStyle(file) } -func XGoClassSource(src []byte, pkg string, class string, prog bool, filename ...string) (ret []byte, err error) { +func XGoClassSource(src []byte, pkg string, class string, proj bool, filename ...string) (ret []byte, err error) { var fname string if filename != nil { fname = filename[0] @@ -91,7 +91,7 @@ func XGoClassSource(src []byte, pkg string, class string, prog bool, filename .. fset := token.NewFileSet() var f *ast.File if f, err = parser.ParseFile(fset, fname, src, parser.ParseComments); err == nil { - XGoClass(f, pkg, class, prog) + XGoClass(f, pkg, class, proj) var buf bytes.Buffer if err = format.Node(&buf, fset, f); err == nil { ret = buf.Bytes() @@ -299,7 +299,7 @@ func formatClass(file *ast.File, pkg string, class string, proj bool) { if spec, ok := v.Specs[0].(*ast.TypeSpec); ok && spec.Name.Name == class { if st, ok := spec.Type.(*ast.StructType); ok { for _, fs := range st.Fields.List { - if len(fs.Names) == 0 { + if len(fs.Names) == 0 && pkg != "" { continue } varSpecs = append(varSpecs, &ast.ValueSpec{Names: fs.Names, Type: fs.Type}) From 9bce99106a968b3da02d14912fd0bbfc9d792a45 Mon Sep 17 00:00:00 2001 From: visualfc Date: Fri, 22 Nov 2024 11:17:54 +0800 Subject: [PATCH 6/6] x/format: ClassConfig support gopt/overload --- x/format/gopclass_test.go | 136 +++++++++++++++++++++++++++++----- x/format/gopstyle.go | 59 +++++++++------ x/format/stmt_expr_or_type.go | 22 ++++-- 3 files changed, 170 insertions(+), 47 deletions(-) diff --git a/x/format/gopclass_test.go b/x/format/gopclass_test.go index 96ca0e35d..143461ebe 100644 --- a/x/format/gopclass_test.go +++ b/x/format/gopclass_test.go @@ -22,20 +22,24 @@ import ( "github.com/goplus/xgo/x/format" ) -func testClass(t *testing.T, name string, pkg string, class string, proj bool, src, expect string) { +func testClass(t *testing.T, name string, cfg *format.ClassConfig, src, expect string) { t.Run(name, func(t *testing.T) { - result, err := format.XGoClassSource([]byte(src), pkg, class, proj, name) + result, err := format.XGoClassSource([]byte(src), cfg, name) if err != nil { t.Fatal("format.XGoClassSource failed:", err) } if ret := string(result); ret != expect { - t.Fatalf("%s => Expect:\n%s\n=> Got:\n%s\n", name, expect, ret) + t.Fatalf("%s => Expect:\n%s\n=> Got:\n%s\n== end", name, expect, ret) } }) } func TestClassSpx(t *testing.T) { - testClass(t, "spx class", "github.com/goplus/spx", "Calf", false, `package main + testClass(t, "spx class", &format.ClassConfig{ + PkgPath: "github.com/goplus/spx", + ClassName: "Calf", + Overload: map[string]string{"OnMsg__1": "OnMsg"}, + }, `package main import ( "github.com/goplus/spx" @@ -109,11 +113,11 @@ func Dump() { log.println info } -onStart func() { +onStart => { say "Hello Go+" } -onMsg__1 "tap", func() { +onMsg "tap", => { for calfPlay { @@ -138,7 +142,11 @@ onMsg__1 "tap", func() { } func TestClassProj(t *testing.T) { - testClass(t, "spx project", "github.com/goplus/spx", "Game", true, `package main + testClass(t, "spx project", &format.ClassConfig{ + PkgPath: "github.com/goplus/spx", + ClassName: "Game", + Project: true, + }, `package main import "github.com/goplus/spx" import "log" @@ -152,15 +160,10 @@ type Game struct { var calfPlay = false var calfDie = false var calfGravity = 0.0 -//line main.spx:30:1 func (this *Game) reset() { -//line main.spx:31:1 this.userScore = 0 -//line main.spx:32:1 calfPlay = false -//line main.spx:33:1 calfDie = false -//line main.spx:34:1 calfGravity = 0.0 } func (this *Game) MainEntry() { @@ -184,13 +187,9 @@ var calfDie = false var calfGravity = 0.0 func reset() { - userScore = 0 - calfPlay = false - calfDie = false - calfGravity = 0.0 } @@ -199,7 +198,10 @@ log.println "MainEntry" } func TestClassGox(t *testing.T) { - testClass(t, "gox class", "", "Rect", false, `package main + testClass(t, "gox class", &format.ClassConfig{ + ClassName: "Rect", + Comments: true, + }, `package main type BaseClass struct { x int @@ -214,6 +216,7 @@ type Rect struct { *AggClass } +// Area is call rect area func (this *Rect) Area() float64 { return this.Width * this.Height } @@ -232,8 +235,107 @@ type BaseClass struct { type AggClass struct { } +// Area is call rect area func Area() float64 { return Width * Height } `) } + +func TestClassGopt(t *testing.T) { + testClass(t, "test class", &format.ClassConfig{ + PkgPath: "github.com/goplus/gop/cl/internal/spx", + ClassName: "Game", + Project: true, + Gopt: map[string]string{ + "Gopt_Sprite_Clone__0": "Clone", + "Gopt_Sprite_Clone__1": "Clone", + }, + Overload: map[string]string{"Broadcast__0": "Broadcast"}, + }, `package main + +import "github.com/goplus/gop/cl/internal/spx" + +type Game struct { + *spx.MyGame + Kai Kai +} +func (this *Game) onInit() { + spx.Gopt_Sprite_Clone__0(this.Kai) + this.Broadcast__0("msg1") +} +func (this *Game) MainEntry() { +} +func (this *Game) Main() { + spx.Gopt_MyGame_Main(this) +} +func main() { + new(Game).Main() +} +`, `var Kai Kai + +func onInit() { + Kai.clone + broadcast "msg1" +} + + +`) + testClass(t, "test class", &format.ClassConfig{ + PkgPath: "github.com/goplus/gop/cl/internal/spx", + ClassName: "Kai", + Gopt: map[string]string{ + "Gopt_Sprite_Clone__0": "Clone", + "Gopt_Sprite_Clone__1": "Clone", + }, + Overload: map[string]string{"Broadcast__0": "Broadcast"}, + }, `package main + +import "github.com/goplus/gop/cl/internal/spx" + +type info struct { + x int + y int +} + +type Kai struct { + spx.Sprite + *Game + a int +} + +func (this *Kai) onInit() { + this.a = 1 + spx.Gopt_Sprite_Clone__0(this) + spx.Gopt_Sprite_Clone__1(this, info{1, 2}) + spx.Gopt_Sprite_Clone__1(this, &info{1, 2}) +} +func (this *Kai) onCloned() { + this.Say("Hi") +} +func (this *Kai) Classfname() string { + return "Kai" +} +func (this *Kai) Main() { +} +`, `var a int + +type info struct { + x int + y int +} + +func onInit() { + a = 1 + clone + clone info{1, 2} + clone &info{1, 2} +} + +func onCloned() { + say "Hi" +} + + +`) +} diff --git a/x/format/gopstyle.go b/x/format/gopstyle.go index 0f131405b..f8a91f077 100644 --- a/x/format/gopstyle.go +++ b/x/format/gopstyle.go @@ -83,15 +83,28 @@ func Gopstyle(file *ast.File) { XGoStyle(file) } -func XGoClassSource(src []byte, pkg string, class string, proj bool, filename ...string) (ret []byte, err error) { +type ClassConfig struct { + PkgPath string // Go+ class project pkgpath, empty if normal .gox class. (optional) + ClassName string // project or class name. + Project bool // true means ClassName is project. + Comments bool // true means parse comments. + Gopt map[string]string // Gopt_ function name mapping. (optional) + Overload map[string]string // Overload function name mapping. (optional) +} + +func XGoClassSource(src []byte, cfg *ClassConfig, filename ...string) (ret []byte, err error) { var fname string if filename != nil { fname = filename[0] } fset := token.NewFileSet() + mode := parser.AllErrors + if cfg.Comments { + mode |= parser.ParseComments + } var f *ast.File - if f, err = parser.ParseFile(fset, fname, src, parser.ParseComments); err == nil { - XGoClass(f, pkg, class, proj) + if f, err = parser.ParseFile(fset, fname, src, mode); err == nil { + XGoClass(f, cfg) var buf bytes.Buffer if err = format.Node(&buf, fset, f); err == nil { ret = buf.Bytes() @@ -101,8 +114,8 @@ func XGoClassSource(src []byte, pkg string, class string, proj bool, filename .. } // XGoClass format ast.File to Go+ class -func XGoClass(file *ast.File, pkg string, class string, prog bool) { - formatClass(file, pkg, class, prog) +func XGoClass(file *ast.File, cfg *ClassConfig) { + formatClass(file, cfg) } func findFuncDecl(decls []ast.Decl, name string) (int, *ast.FuncDecl) { @@ -178,12 +191,11 @@ type importCtx struct { } type formatCtx struct { - imports map[string]*importCtx - scope *types.Scope - classMode bool //class mode - classPkg string //class pkg name - className string //this class - funcRecv string //this class func recv + imports map[string]*importCtx + scope *types.Scope + classCfg *ClassConfig + classPkg string //this class pkg name + funcRecv string //this class func recv } func (ctx *formatCtx) insert(name string) { @@ -249,14 +261,13 @@ func formatFile(file *ast.File) { } } -func formatClass(file *ast.File, pkg string, class string, proj bool) { +func formatClass(file *ast.File, cfg *ClassConfig) { var funcs []*ast.FuncDecl ctx := &formatCtx{ - imports: make(map[string]*importCtx), - scope: types.NewScope(nil, token.NoPos, token.NoPos, ""), - classMode: true, - classPkg: path.Base(pkg), - className: class, + imports: make(map[string]*importCtx), + scope: types.NewScope(nil, token.NoPos, token.NoPos, ""), + classCfg: cfg, + classPkg: path.Base(cfg.PkgPath), } if file.Name.Name == "main" { file.NoPkgDecl = true @@ -268,17 +279,17 @@ func formatClass(file *ast.File, pkg string, class string, proj bool) { for _, decl := range file.Decls { switch v := decl.(type) { case *ast.FuncDecl: - if isClassFunc(v, class) { + if isClassFunc(v, cfg.ClassName) { v.IsClass = true switch v.Name.Name { case "MainEntry": - if proj { + if cfg.Project { fnEntry = v file.ShadowEntry = v continue } case "Main": - if !proj { + if !cfg.Project { fnEntry = v file.ShadowEntry = v continue @@ -287,7 +298,7 @@ func formatClass(file *ast.File, pkg string, class string, proj bool) { case "Classfname": v.Shadow = true } - } else if v.Name.Name == "main" && proj { + } else if v.Name.Name == "main" && cfg.Project { v.Shadow = true } case *ast.GenDecl: @@ -296,10 +307,10 @@ func formatClass(file *ast.File, pkg string, class string, proj bool) { imports = append(imports, v) continue case token.TYPE: - if spec, ok := v.Specs[0].(*ast.TypeSpec); ok && spec.Name.Name == class { + if spec, ok := v.Specs[0].(*ast.TypeSpec); ok && spec.Name.Name == cfg.ClassName { if st, ok := spec.Type.(*ast.StructType); ok { for _, fs := range st.Fields.List { - if len(fs.Names) == 0 && pkg != "" { + if len(fs.Names) == 0 && cfg.PkgPath != "" { continue } varSpecs = append(varSpecs, &ast.ValueSpec{Names: fs.Names, Type: fs.Type}) @@ -403,7 +414,7 @@ func funcRecv(v *ast.FuncDecl) *ast.Ident { } func formatFuncDecl(ctx *formatCtx, v *ast.FuncDecl) { - if ctx.classMode && isClassFunc(v, ctx.className) { + if ctx.classCfg != nil && isClassFunc(v, ctx.classCfg.ClassName) { v.IsClass = true if recv := funcRecv(v); recv != nil { ctx.funcRecv = recv.Name diff --git a/x/format/stmt_expr_or_type.go b/x/format/stmt_expr_or_type.go index 1dc38bf80..f0283aaac 100644 --- a/x/format/stmt_expr_or_type.go +++ b/x/format/stmt_expr_or_type.go @@ -182,7 +182,7 @@ func formatSelectorExpr(ctx *formatCtx, v *ast.SelectorExpr, ref *ast.Expr) { if _, o := ctx.scope.LookupParent(x.Name, token.NoPos); o != nil { break } - if ctx.classMode && (x.Name == ctx.funcRecv || x.Name == ctx.classPkg) { + if ctx.classCfg != nil && (x.Name == ctx.funcRecv || x.Name == ctx.classPkg) { *ref = v.Sel break } @@ -221,7 +221,7 @@ func isClassSched(ctx *formatCtx, stmt ast.Stmt) bool { func formatStmts(ctx *formatCtx, stmts []ast.Stmt) { for i, stmt := range stmts { - if ctx.classMode && isClassSched(ctx, stmt) { + if ctx.classCfg != nil && isClassSched(ctx, stmt) { stmts[i] = &ast.EmptyStmt{} continue } @@ -283,10 +283,20 @@ func formatStmt(ctx *formatCtx, stmt ast.Stmt) { func formatExprStmt(ctx *formatCtx, v *ast.ExprStmt) { switch x := v.X.(type) { case *ast.CallExpr: - if ctx.classMode { - if sel, ok := x.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "Sched" { - if ident, ok := sel.X.(*ast.Ident); ok && ident.Name == ctx.classPkg { - return + if ctx.classCfg != nil { + if sel, ok := x.Fun.(*ast.SelectorExpr); ok { + if name, ok := ctx.classCfg.Overload[sel.Sel.Name]; ok { + sel.Sel.Name = name + } else if ident, ok := sel.X.(*ast.Ident); ok && ident.Name == ctx.classPkg { + if name, ok := ctx.classCfg.Gopt[sel.Sel.Name]; ok { + if len(x.Args) > 0 { + x.Fun = &ast.SelectorExpr{ + X: x.Args[0], + Sel: ast.NewIdent(name), + } + x.Args = x.Args[1:] + } + } } } }