Repository: goplus/gop
Pull request #1407 (2023-08-30 02:48:49)

add build context for build go+ to bytes


Patch:
diff --git a/build/build.go b/build/build.go
new file mode 100644
index 00000000..0d6424b8
--- /dev/null
+++ b/build/build.go
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2023 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 build
+
+import (
+	"bytes"
+	"fmt"
+	goast "go/ast"
+	"go/types"
+	"path"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+	"github.com/goplus/gox/packages"
+)
+
+type Class = cl.Class
+
+var (
+	projects = make(map[string]*cl.Project)
+)
+
+func RegisterClassFileType(ext string, class string, works []*Class, pkgPaths ...string) {
+	cls := &cl.Project{
+		Ext:      ext,
+		Class:    class,
+		Works:    works,
+		PkgPaths: pkgPaths,
+	}
+	if ext != "" {
+		projects[ext] = cls
+	}
+	for _, w := range works {
+		projects[w.Ext] = cls
+	}
+}
+
+func init() {
+	RegisterClassFileType(".gmx", "Game", []*Class{{Ext: ".spx", Class: "Sprite"}}, "github.com/goplus/spx", "math")
+}
+
+type Package struct {
+	Fset *token.FileSet
+	Pkg  *gox.Package
+}
+
+func (p *Package) ToSource() ([]byte, error) {
+	var buf bytes.Buffer
+	if err := gox.WriteTo(&buf, p.Pkg); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+func (p *Package) ToAst() *goast.File {
+	return gox.ASTFile(p.Pkg)
+}
+
+func ClassKind(fname string) (isProj, ok bool) {
+	ext := path.Ext(fname)
+	switch ext {
+	case ".gmx":
+		return true, true
+	case ".spx":
+		return ext == "main.spx", true
+	default:
+		if c, ok := projects[ext]; ok {
+			for _, w := range c.Works {
+				if w.Ext == ext {
+					if ext != c.Ext || fname != "main"+ext {
+						return false, true
+					}
+					break
+				}
+			}
+			return true, true
+		}
+	}
+	return
+}
+
+type Context struct {
+	impl       types.Importer
+	fset       *token.FileSet
+	LoadConfig func(*cl.Config)
+}
+
+func Default() *Context {
+	fset := token.NewFileSet()
+	impl := packages.NewImporter(fset)
+	return NewContext(impl, fset)
+}
+
+func NewContext(impl types.Importer, fset *token.FileSet) *Context {
+	if fset == nil {
+		fset = token.NewFileSet()
+	}
+	return &Context{impl: impl, fset: fset}
+}
+
+func (c *Context) Import(path string) (*types.Package, error) {
+	return c.impl.Import(path)
+}
+
+func (c *Context) ParseDir(dir string) (*Package, error) {
+	pkgs, err := parser.ParseDirEx(c.fset, dir, parser.Config{
+		ClassKind: ClassKind,
+	})
+	if err != nil {
+		return nil, err
+	}
+	return c.loadPackage(dir, pkgs)
+}
+
+func (c *Context) ParseFSDir(fs parser.FileSystem, dir string) (*Package, error) {
+	pkgs, err := parser.ParseFSDir(c.fset, fs, dir, parser.Config{
+		ClassKind: ClassKind,
+	})
+	if err != nil {
+		return nil, err
+	}
+	return c.loadPackage(dir, pkgs)
+}
+
+func (c *Context) ParseFile(fname string, src interface{}) (*Package, error) {
+	fs, err := newFileFS(fname, src)
+	if err != nil {
+		return nil, err
+	}
+	return c.ParseFSDir(fs, fs.dir)
+}
+
+func (c *Context) loadPackage(srcDir string, pkgs map[string]*ast.Package) (*Package, error) {
+	mainPkg, ok := pkgs["main"]
+	if !ok {
+		for _, v := range pkgs {
+			mainPkg = v
+			break
+		}
+	}
+	conf := &cl.Config{
+		WorkingDir: srcDir, TargetDir: srcDir, Fset: c.fset}
+	conf.Importer = c
+	conf.LookupClass = func(ext string) (c *cl.Project, ok bool) {
+		c, ok = projects[ext]
+		return
+	}
+	if c.LoadConfig != nil {
+		c.LoadConfig(conf)
+	}
+	out, err := cl.NewPackage("", mainPkg, conf)
+	if err != nil {
+		return nil, err
+	}
+	return &Package{c.fset, out}, nil
+}
+
+func (ctx *Context) BuildFile(filename string, src interface{}) (data []byte, err error) {
+	defer func() {
+		r := recover()
+		if r != nil {
+			err = fmt.Errorf("compile %v failed. %v", filename, r)
+		}
+	}()
+	pkg, err := ctx.ParseFile(filename, src)
+	if err != nil {
+		return nil, err
+	}
+	return pkg.ToSource()
+}
+
+func (ctx *Context) BuildFSDir(fs parser.FileSystem, dir string) (data []byte, err error) {
+	defer func() {
+		r := recover()
+		if r != nil {
+			err = fmt.Errorf("compile %v failed. %v", dir, err)
+		}
+	}()
+	pkg, err := ctx.ParseFSDir(fs, dir)
+	if err != nil {
+		return nil, err
+	}
+	return pkg.ToSource()
+}
+
+func (ctx *Context) BuildDir(dir string) (data []byte, err error) {
+	defer func() {
+		r := recover()
+		if r != nil {
+			err = fmt.Errorf("compile %v failed. %v", dir, err)
+		}
+	}()
+	pkg, err := ctx.ParseDir(dir)
+	if err != nil {
+		return nil, err
+	}
+	return pkg.ToSource()
+}
diff --git a/build/fs.go b/build/fs.go
new file mode 100644
index 00000000..29f6c465
--- /dev/null
+++ b/build/fs.go
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2023 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 build
+
+import (
+	"bytes"
+	"io"
+	"io/fs"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+type memFileInfo struct {
+	name string
+}
+
+func (p *memFileInfo) Name() string {
+	return p.name
+}
+
+func (p *memFileInfo) Size() int64 {
+	return 0
+}
+
+func (p *memFileInfo) Mode() os.FileMode {
+	return 0
+}
+
+func (p *memFileInfo) ModTime() (t time.Time) {
+	return
+}
+
+func (p *memFileInfo) IsDir() bool {
+	return false
+}
+
+func (p *memFileInfo) Sys() interface{} {
+	return nil
+}
+
+type fileFS struct {
+	dir      string
+	filename string
+	data     []byte
+	info     fs.FileInfo
+}
+
+func newFileFS(filename string, src interface{}) (f *fileFS, err error) {
+	dir, name := filepath.Split(filename)
+	var data []byte
+	var info fs.FileInfo
+	if src != nil {
+		data, err = readSource(src)
+		if err != nil {
+			return
+		}
+		info = &memFileInfo{name: name}
+	} else {
+		info, err = os.Stat(filename)
+		if err != nil {
+			return
+		}
+		data, err = os.ReadFile(filename)
+		if err != nil {
+			return
+		}
+	}
+	return &fileFS{dir: dir, filename: filename, data: data, info: info}, nil
+}
+
+func (p fileFS) ReadDir(dirname string) ([]fs.FileInfo, error) {
+	return []fs.FileInfo{p.info}, nil
+}
+
+func (p fileFS) ReadFile(filename string) ([]byte, error) {
+	return p.data, nil
+}
+
+func (p fileFS) Join(elem ...string) string {
+	return filepath.Join(elem...)
+}
+
+func readSource(src interface{}) ([]byte, error) {
+	switch s := src.(type) {
+	case string:
+		return []byte(s), nil
+	case []byte:
+		return s, nil
+	case *bytes.Buffer:
+		// is io.Reader, but src is already available in []byte form
+		if s != nil {
+			return s.Bytes(), nil
+		}
+	case io.Reader:
+		return ioutil.ReadAll(s)
+	}
+	return nil, os.ErrInvalid
+}


Test patch:
diff --git a/build/build_test.go b/build/build_test.go
new file mode 100644
index 00000000..c743f2d3
--- /dev/null
+++ b/build/build_test.go
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2022 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 build_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/goplus/gop/build"
+)
+
+var (
+	ctx = build.Default()
+)
+
+func gopClTest(t *testing.T, gopcode, expected string) {
+	gopClTestEx(t, "main.gop", gopcode, expected)
+}
+
+func gopClTestEx(t *testing.T, filename string, gopcode, expected string) {
+	data, err := ctx.BuildFile(filename, gopcode)
+	if err != nil {
+		t.Fatalf("build gop error: %v", err)
+	}
+	if string(data) != expected {
+		fmt.Println("build gop error:")
+		fmt.Println(string(data))
+		t.Fail()
+	}
+}
+
+func TestGop(t *testing.T) {
+	gopClTest(t, `
+println "Go+"
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+//line main.gop:2
+	fmt.Println("Go+")
+}
+`)
+}
+
+func TestGox(t *testing.T) {
+	gopClTestEx(t, "Rect.gox", `
+println "Go+"
+`, `package main
+
+import fmt "fmt"
+
+type Rect struct {
+}
+
+func (this *Rect) Main() {
+//line Rect.gox:2
+	fmt.Println("Go+")
+}
+func main() {
+}
+`)
+	gopClTestEx(t, "Rect.gox", `
+var (
+	Buffer
+	v int
+)
+type Buffer struct {
+	buf []byte
+}
+println "Go+"
+`, `package main
+
+import fmt "fmt"
+
+type Buffer struct {
+	buf []byte
+}
+type Rect struct {
+	Buffer
+	v int
+}
+
+func (this *Rect) Main() {
+//line Rect.gox:9
+	fmt.Println("Go+")
+}
+func main() {
+}
+`)
+	gopClTestEx(t, "Rect.gox", `
+var (
+	*Buffer
+	v int
+)
+type Buffer struct {
+	buf []byte
+}
+println "Go+"
+`, `package main
+
+import fmt "fmt"
+
+type Buffer struct {
+	buf []byte
+}
+type Rect struct {
+	*Buffer
+	v int
+}
+
+func (this *Rect) Main() {
+//line Rect.gox:9
+	fmt.Println("Go+")
+}
+func main() {
+}
+`)
+	gopClTestEx(t, "Rect.gox", `
+import "bytes"
+var (
+	*bytes.Buffer
+	v int
+)
+println "Go+"
+`, `package main
+
+import (
+	fmt "fmt"
+	bytes "bytes"
+)
+
+type Rect struct {
+	*bytes.Buffer
+	v int
+}
+
+func (this *Rect) Main() {
+//line Rect.gox:7
+	fmt.Println("Go+")
+}
+func main() {
+}
+`)
+	gopClTestEx(t, "Rect.gox", `
+import "bytes"
+var (
+	bytes.Buffer
+	v int
+)
+println "Go+"
+`, `package main
+
+import (
+	fmt "fmt"
+	bytes "bytes"
+)
+
+type Rect struct {
+	bytes.Buffer
+	v int
+}
+
+func (this *Rect) Main() {
+//line Rect.gox:7
+	fmt.Println("Go+")
+}
+func main() {
+}
+`)
+}
+
+func TestBig(t *testing.T) {
+	gopClTest(t, `
+a := 1/2r
+println a+1/2r
+`, `package main
+
+import (
+	fmt "fmt"
+	ng "github.com/goplus/gop/builtin/ng"
+	big "math/big"
+)
+
+func main() {
+//line main.gop:2
+	a := ng.Bigrat_Init__2(big.NewRat(1, 2))
+//line main.gop:3
+	fmt.Println(a.Gop_Add(ng.Bigrat_Init__2(big.NewRat(1, 2))))
+}
+`)
+}
+
+func TestIoxLines(t *testing.T) {
+	gopClTest(t, `
+import "io"
+
+var r io.Reader
+
+for line <- lines(r) {
+	println line
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	iox "github.com/goplus/gop/builtin/iox"
+	io "io"
+)
+
+var r io.Reader
+
+func main() {
+//line main.gop:6
+	for _gop_it := iox.Lines(r).Gop_Enum(); ; {
+		var _gop_ok bool
+		line, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+//line main.gop:7
+		fmt.Println(line)
+	}
+}
+`)
+}
+
+func TestErrorWrap(t *testing.T) {
+	gopClTest(t, `
+import (
+    "strconv"
+)
+
+func add(x, y string) (int, error) {
+    return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
+}
+
+func addSafe(x, y string) int {
+    return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
+}
+
+println add("100", "23")!
+
+sum, err := add("10", "abc")
+println sum, err
+
+println addSafe("10", "abc")
+`, `package main
+
+import (
+	fmt "fmt"
+	strconv "strconv"
+	errors "github.com/qiniu/x/errors"
+)
+
+func add(x string, y string) (int, error) {
+//line main.gop:7
+	var _autoGo_1 int
+//line main.gop:7
+	{
+//line main.gop:7
+		var _gop_err error
+//line main.gop:7
+		_autoGo_1, _gop_err = strconv.Atoi(x)
+//line main.gop:7
+		if _gop_err != nil {
+//line main.gop:7
+			_gop_err = errors.NewFrame(_gop_err, "strconv.Atoi(x)", "main.gop", 7, "main.add")
+//line main.gop:7
+			return 0, _gop_err
+		}
+//line main.gop:7
+		goto _autoGo_2
+	_autoGo_2:
+//line main.gop:7
+	}
+//line main.gop:7
+	var _autoGo_3 int
+//line main.gop:7
+	{
+//line main.gop:7
+		var _gop_err error
+//line main.gop:7
+		_autoGo_3, _gop_err = strconv.Atoi(y)
+//line main.gop:7
+		if _gop_err != nil {
+//line main.gop:7
+			_gop_err = errors.NewFrame(_gop_err, "strconv.Atoi(y)", "main.gop", 7, "main.add")
+//line main.gop:7
+			return 0, _gop_err
+		}
+//line main.gop:7
+		goto _autoGo_4
+	_autoGo_4:
+//line main.gop:7
+	}
+//line main.gop:7
+	return _autoGo_1 + _autoGo_3, nil
+}
+func addSafe(x string, y string) int {
+//line main.gop:11
+	return func() (_gop_ret int) {
+//line main.gop:11
+		var _gop_err error
+//line main.gop:11
+		_gop_ret, _gop_err = strconv.Atoi(x)
+//line main.gop:11
+		if _gop_err != nil {
+//line main.gop:11
+			return 0
+		}
+//line main.gop:11
+		return
+	}() + func() (_gop_ret int) {
+//line main.gop:11
+		var _gop_err error
+//line main.gop:11
+		_gop_ret, _gop_err = strconv.Atoi(y)
+//line main.gop:11
+		if _gop_err != nil {
+//line main.gop:11
+			return 0
+		}
+//line main.gop:11
+		return
+	}()
+}
+func main() {
+//line main.gop:14
+	fmt.Println(func() (_gop_ret int) {
+//line main.gop:14
+		var _gop_err error
+//line main.gop:14
+		_gop_ret, _gop_err = add("100", "23")
+//line main.gop:14
+		if _gop_err != nil {
+//line main.gop:14
+			_gop_err = errors.NewFrame(_gop_err, "add(\"100\", \"23\")", "main.gop", 14, "main.main")
+//line main.gop:14
+			panic(_gop_err)
+		}
+//line main.gop:14
+		return
+	}())
+//line main.gop:16
+	sum, err := add("10", "abc")
+//line main.gop:17
+	fmt.Println(sum, err)
+//line main.gop:19
+	fmt.Println(addSafe("10", "abc"))
+}
+`)
+}
+
+func init() {
+	build.RegisterClassFileType(".tspx", "MyGame", []*build.Class{
+		{Ext: ".tspx", Class: "Sprite"},
+	}, "github.com/goplus/gop/cl/internal/spx")
+}
+
+func TestSpx(t *testing.T) {
+	gopClTestEx(t, "main.tspx", `println "hi"`, `package main
+
+import (
+	fmt "fmt"
+	spx "github.com/goplus/gop/cl/internal/spx"
+)
+
+type MyGame struct {
+	spx.MyGame
+}
+
+func (this *MyGame) MainEntry() {
+//line main.tspx:1
+	fmt.Println("hi")
+}
+func main() {
+	spx.Gopt_MyGame_Main(new(MyGame))
+}
+`)
+	gopClTestEx(t, "Cat.tspx", `println "hi"`, `package main
+
+import (
+	fmt "fmt"
+	spx "github.com/goplus/gop/cl/internal/spx"
+)
+
+type Cat struct {
+	spx.Sprite
+}
+
+func (this *Cat) Main() {
+//line Cat.tspx:1
+	fmt.Println("hi")
+}
+func main() {
+}
+`)
+}