github.com/goplus/igop@v0.17.0/gopbuild/build.go (about)

     1  /*
     2   * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package gopbuild
    18  
    19  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/goplus/gop/builtin
    20  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/goplus/gop/builtin/ng
    21  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/goplus/gop/builtin/iox
    22  //go:generate go run ../cmd/qexp -outdir ../pkg github.com/qiniu/x/errors
    23  
    24  import (
    25  	"bytes"
    26  	"fmt"
    27  	goast "go/ast"
    28  	"go/types"
    29  	"path/filepath"
    30  
    31  	"github.com/goplus/gop/ast"
    32  	"github.com/goplus/gop/cl"
    33  	"github.com/goplus/gop/parser"
    34  	"github.com/goplus/gop/token"
    35  	"github.com/goplus/gox"
    36  	"github.com/goplus/igop"
    37  	"github.com/goplus/mod/gopmod"
    38  
    39  	_ "github.com/goplus/igop/pkg/bufio"
    40  	_ "github.com/goplus/igop/pkg/fmt"
    41  	_ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin"
    42  	_ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin/iox"
    43  	_ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin/ng"
    44  	_ "github.com/goplus/igop/pkg/github.com/qiniu/x/errors"
    45  	_ "github.com/goplus/igop/pkg/io"
    46  	_ "github.com/goplus/igop/pkg/log"
    47  	_ "github.com/goplus/igop/pkg/math/big"
    48  	_ "github.com/goplus/igop/pkg/math/bits"
    49  	_ "github.com/goplus/igop/pkg/os"
    50  	_ "github.com/goplus/igop/pkg/strconv"
    51  	_ "github.com/goplus/igop/pkg/strings"
    52  )
    53  
    54  type Class = gopmod.Class
    55  type Project = gopmod.Project
    56  
    57  var (
    58  	classfile = make(map[string]*cl.Project)
    59  )
    60  
    61  func RegisterClassFileType(ext string, class string, works []*Class, pkgPaths ...string) {
    62  	cls := &cl.Project{
    63  		Ext:      ext,
    64  		Class:    class,
    65  		Works:    works,
    66  		PkgPaths: pkgPaths,
    67  	}
    68  	if ext != "" {
    69  		classfile[ext] = cls
    70  	}
    71  	for _, w := range works {
    72  		classfile[w.Ext] = cls
    73  	}
    74  }
    75  
    76  func init() {
    77  	igop.RegisterFileProcess(".gop", BuildFile)
    78  	igop.RegisterFileProcess(".gopx", BuildFile)
    79  	RegisterClassFileType(".gmx", "Game", []*Class{{Ext: ".spx", Class: "Sprite"}}, "github.com/goplus/spx", "math")
    80  }
    81  
    82  func BuildFile(ctx *igop.Context, filename string, src interface{}) (data []byte, err error) {
    83  	defer func() {
    84  		r := recover()
    85  		if r != nil {
    86  			err = fmt.Errorf("compile %v failed. %v", filename, r)
    87  		}
    88  	}()
    89  	c := NewContext(ctx)
    90  	pkg, err := c.ParseFile(filename, src)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	return pkg.ToSource()
    95  }
    96  
    97  func BuildFSDir(ctx *igop.Context, fs parser.FileSystem, dir string) (data []byte, err error) {
    98  	defer func() {
    99  		r := recover()
   100  		if r != nil {
   101  			err = fmt.Errorf("compile %v failed. %v", dir, err)
   102  		}
   103  	}()
   104  	c := NewContext(ctx)
   105  	pkg, err := c.ParseFSDir(fs, dir)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	return pkg.ToSource()
   110  }
   111  
   112  func BuildDir(ctx *igop.Context, dir string) (data []byte, err error) {
   113  	defer func() {
   114  		r := recover()
   115  		if r != nil {
   116  			err = fmt.Errorf("compile %v failed. %v", dir, err)
   117  		}
   118  	}()
   119  	c := NewContext(ctx)
   120  	pkg, err := c.ParseDir(dir)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  	return pkg.ToSource()
   125  }
   126  
   127  type Package struct {
   128  	Fset *token.FileSet
   129  	Pkg  *gox.Package
   130  }
   131  
   132  func (p *Package) ToSource() ([]byte, error) {
   133  	var buf bytes.Buffer
   134  	if err := gox.WriteTo(&buf, p.Pkg); err != nil {
   135  		return nil, err
   136  	}
   137  	return buf.Bytes(), nil
   138  }
   139  
   140  func (p *Package) ToAst() *goast.File {
   141  	return gox.ASTFile(p.Pkg)
   142  }
   143  
   144  type Context struct {
   145  	ctx  *igop.Context
   146  	fset *token.FileSet
   147  	gop  igop.Loader
   148  }
   149  
   150  func IsClass(ext string) (isProj bool, ok bool) {
   151  	if cls, ok := classfile[ext]; ok {
   152  		return ext == cls.Ext, true
   153  	}
   154  	if ext == ".gopx" {
   155  		ok = true
   156  	}
   157  	return
   158  }
   159  
   160  func NewContext(ctx *igop.Context) *Context {
   161  	if ctx.IsEvalMode() {
   162  		ctx = igop.NewContext(0)
   163  	}
   164  	return &Context{ctx: ctx, fset: token.NewFileSet(), gop: igop.NewTypesLoader(ctx, 0)}
   165  }
   166  
   167  func isGopPackage(path string) bool {
   168  	if pkg, ok := igop.LookupPackage(path); ok {
   169  		if _, ok := pkg.UntypedConsts["GopPackage"]; ok {
   170  			return true
   171  		}
   172  	}
   173  	return false
   174  }
   175  
   176  func (c *Context) Import(path string) (*types.Package, error) {
   177  	if isGopPackage(path) {
   178  		return c.gop.Import(path)
   179  	}
   180  	return c.ctx.Loader.Import(path)
   181  }
   182  
   183  func (c *Context) ParseDir(dir string) (*Package, error) {
   184  	pkgs, err := parser.ParseDirEx(c.fset, dir, parser.Config{
   185  		IsClass: IsClass,
   186  	})
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  	return c.loadPackage(dir, pkgs)
   191  }
   192  
   193  func (c *Context) ParseFSDir(fs parser.FileSystem, dir string) (*Package, error) {
   194  	pkgs, err := parser.ParseFSDir(c.fset, fs, dir, parser.Config{
   195  		IsClass: IsClass,
   196  	})
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	return c.loadPackage(dir, pkgs)
   201  }
   202  
   203  func (c *Context) ParseFile(filename string, src interface{}) (*Package, error) {
   204  	srcDir, _ := filepath.Split(filename)
   205  	isProj, isClass := IsClass(filepath.Ext(filename))
   206  	mode := parser.ParseComments
   207  	if isClass {
   208  		mode |= parser.ParseGoPlusClass
   209  	}
   210  	f, err := parser.ParseFile(c.fset, filename, src, mode)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	f.IsProj, f.IsClass = isProj, isClass
   215  	name := f.Name.Name
   216  	pkgs := map[string]*ast.Package{
   217  		name: &ast.Package{
   218  			Name: name,
   219  			Files: map[string]*ast.File{
   220  				filename: f,
   221  			},
   222  		},
   223  	}
   224  	return c.loadPackage(srcDir, pkgs)
   225  }
   226  
   227  func (c *Context) loadPackage(srcDir string, pkgs map[string]*ast.Package) (*Package, error) {
   228  	mainPkg, ok := pkgs["main"]
   229  	if !ok {
   230  		for _, v := range pkgs {
   231  			mainPkg = v
   232  			break
   233  		}
   234  	}
   235  	if c.ctx.Mode&igop.DisableCustomBuiltin == 0 {
   236  		if f, err := igop.ParseBuiltin(c.fset, mainPkg.Name); err == nil {
   237  			mainPkg.GoFiles = map[string]*goast.File{"_igop_builtin.go": f}
   238  		}
   239  	}
   240  	conf := &cl.Config{
   241  		WorkingDir: srcDir, TargetDir: srcDir, Fset: c.fset}
   242  	conf.Importer = c
   243  	conf.LookupClass = func(ext string) (c *cl.Project, ok bool) {
   244  		c, ok = classfile[ext]
   245  		return
   246  	}
   247  	if c.ctx.IsEvalMode() {
   248  		conf.NoSkipConstant = true
   249  	}
   250  	out, err := cl.NewPackage("", mainPkg, conf)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	return &Package{c.fset, out}, nil
   255  }