github.com/goplus/igop@v0.25.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 //go:generate go run ../cmd/qexp -outdir ../pkg github.com/qiniu/x/gsh 24 25 import ( 26 "bytes" 27 "fmt" 28 goast "go/ast" 29 "go/types" 30 "path/filepath" 31 32 "github.com/goplus/gogen" 33 "github.com/goplus/gop/ast" 34 "github.com/goplus/gop/cl" 35 "github.com/goplus/gop/parser" 36 "github.com/goplus/gop/token" 37 "github.com/goplus/igop" 38 "github.com/goplus/mod/modfile" 39 40 _ "github.com/goplus/igop/pkg/bufio" 41 _ "github.com/goplus/igop/pkg/context" 42 _ "github.com/goplus/igop/pkg/errors" 43 _ "github.com/goplus/igop/pkg/fmt" 44 _ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin" 45 _ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin/iox" 46 _ "github.com/goplus/igop/pkg/github.com/goplus/gop/builtin/ng" 47 _ "github.com/goplus/igop/pkg/github.com/qiniu/x/errors" 48 _ "github.com/goplus/igop/pkg/github.com/qiniu/x/gsh" 49 _ "github.com/goplus/igop/pkg/io" 50 _ "github.com/goplus/igop/pkg/log" 51 _ "github.com/goplus/igop/pkg/math" 52 _ "github.com/goplus/igop/pkg/math/big" 53 _ "github.com/goplus/igop/pkg/math/bits" 54 _ "github.com/goplus/igop/pkg/os" 55 _ "github.com/goplus/igop/pkg/os/exec" 56 _ "github.com/goplus/igop/pkg/path/filepath" 57 _ "github.com/goplus/igop/pkg/runtime" 58 _ "github.com/goplus/igop/pkg/strconv" 59 _ "github.com/goplus/igop/pkg/strings" 60 ) 61 62 type Class = cl.Class 63 64 var ( 65 projects = make(map[string]*cl.Project) 66 ) 67 68 func RegisterClassFileType(ext string, class string, works []*Class, pkgPaths ...string) { 69 cls := &cl.Project{ 70 Ext: ext, 71 Class: class, 72 Works: works, 73 PkgPaths: pkgPaths, 74 } 75 if ext != "" { 76 projects[ext] = cls 77 } 78 for _, w := range works { 79 projects[w.Ext] = cls 80 } 81 } 82 83 func init() { 84 cl.SetDebug(cl.FlagNoMarkAutogen) 85 igop.RegisterFileProcess(".gop", BuildFile) 86 igop.RegisterFileProcess(".gox", BuildFile) 87 igop.RegisterFileProcess(".gsh", BuildFile) 88 RegisterClassFileType(".gmx", "Game", []*Class{{Ext: ".spx", Class: "Sprite"}}, "github.com/goplus/spx", "math") 89 RegisterClassFileType(".gsh", "App", nil, "github.com/qiniu/x/gsh", "math") 90 } 91 92 func BuildFile(ctx *igop.Context, filename string, src interface{}) (data []byte, err error) { 93 defer func() { 94 r := recover() 95 if r != nil { 96 err = fmt.Errorf("compile %v failed. %v", filename, r) 97 } 98 }() 99 c := NewContext(ctx) 100 pkg, err := c.ParseFile(filename, src) 101 if err != nil { 102 return nil, err 103 } 104 return pkg.ToSource() 105 } 106 107 func BuildFSDir(ctx *igop.Context, fs parser.FileSystem, dir string) (data []byte, err error) { 108 defer func() { 109 r := recover() 110 if r != nil { 111 err = fmt.Errorf("compile %v failed. %v", dir, err) 112 } 113 }() 114 c := NewContext(ctx) 115 pkg, err := c.ParseFSDir(fs, dir) 116 if err != nil { 117 return nil, err 118 } 119 return pkg.ToSource() 120 } 121 122 func BuildDir(ctx *igop.Context, dir string) (data []byte, err error) { 123 defer func() { 124 r := recover() 125 if r != nil { 126 err = fmt.Errorf("compile %v failed. %v", dir, err) 127 } 128 }() 129 c := NewContext(ctx) 130 pkg, err := c.ParseDir(dir) 131 if err != nil { 132 return nil, err 133 } 134 return pkg.ToSource() 135 } 136 137 type Package struct { 138 Fset *token.FileSet 139 Pkg *gogen.Package 140 } 141 142 func (p *Package) ToSource() ([]byte, error) { 143 var buf bytes.Buffer 144 if err := p.Pkg.WriteTo(&buf); err != nil { 145 return nil, err 146 } 147 return buf.Bytes(), nil 148 } 149 150 func (p *Package) ToAst() *goast.File { 151 return p.Pkg.ASTFile() 152 } 153 154 type Context struct { 155 ctx *igop.Context 156 fset *token.FileSet 157 imp *igop.Importer 158 gop igop.Loader 159 } 160 161 func ClassKind(fname string) (isProj, ok bool) { 162 ext := modfile.ClassExt(fname) 163 switch ext { 164 case ".gmx", ".gsh": 165 return true, true 166 case ".spx": 167 return fname == "main.spx", true 168 default: 169 if c, ok := projects[ext]; ok { 170 for _, w := range c.Works { 171 if w.Ext == ext { 172 if ext != c.Ext || fname != "main"+ext { 173 return false, true 174 } 175 break 176 } 177 } 178 return true, true 179 } 180 } 181 return 182 } 183 184 func NewContext(ctx *igop.Context) *Context { 185 if ctx.IsEvalMode() { 186 ctx = igop.NewContext(0) 187 } 188 ctx.Mode |= igop.CheckGopOverloadFunc 189 return &Context{ctx: ctx, imp: igop.NewImporter(ctx), fset: token.NewFileSet(), gop: igop.NewTypesLoader(ctx, 0)} 190 } 191 192 func isGopPackage(path string) bool { 193 if pkg, ok := igop.LookupPackage(path); ok { 194 if _, ok := pkg.UntypedConsts["GopPackage"]; ok { 195 return true 196 } 197 } 198 return false 199 } 200 201 func (c *Context) Import(path string) (*types.Package, error) { 202 if isGopPackage(path) { 203 return c.gop.Import(path) 204 } 205 return c.imp.Import(path) 206 } 207 208 func (c *Context) ParseDir(dir string) (*Package, error) { 209 pkgs, err := parser.ParseDirEx(c.fset, dir, parser.Config{ 210 ClassKind: ClassKind, 211 }) 212 if err != nil { 213 return nil, err 214 } 215 return c.loadPackage(dir, pkgs) 216 } 217 218 func (c *Context) ParseFSDir(fs parser.FileSystem, dir string) (*Package, error) { 219 pkgs, err := parser.ParseFSDir(c.fset, fs, dir, parser.Config{ 220 ClassKind: ClassKind, 221 }) 222 if err != nil { 223 return nil, err 224 } 225 return c.loadPackage(dir, pkgs) 226 } 227 228 func (c *Context) ParseFile(fname string, src interface{}) (*Package, error) { 229 srcDir, _ := filepath.Split(fname) 230 fnameRmGox := fname 231 ext := filepath.Ext(fname) 232 var err error 233 var isProj, isClass, isNormalGox, rmGox bool 234 switch ext { 235 case ".go", ".gop": 236 case ".gox": 237 isClass = true 238 t := fname[:len(fname)-4] 239 if c := filepath.Ext(t); c != "" { 240 ext, fnameRmGox, rmGox = c, t, true 241 } else { 242 isNormalGox = true 243 } 244 fallthrough 245 default: 246 if !isNormalGox { 247 if isProj, isClass = ClassKind(fnameRmGox); !isClass { 248 if rmGox { 249 return nil, fmt.Errorf("not found Go+ class by ext %q for %q", ext, fname) 250 } 251 return nil, nil 252 } 253 } 254 } 255 if err != nil { 256 return nil, err 257 } 258 mode := parser.ParseComments 259 if isClass { 260 mode |= parser.ParseGoPlusClass 261 } 262 f, err := parser.ParseFile(c.fset, fname, src, mode) 263 if err != nil { 264 return nil, err 265 } 266 f.IsProj, f.IsClass, f.IsNormalGox = isProj, isClass, isNormalGox 267 name := f.Name.Name 268 pkgs := map[string]*ast.Package{ 269 name: &ast.Package{ 270 Name: name, 271 Files: map[string]*ast.File{ 272 fname: f, 273 }, 274 }, 275 } 276 return c.loadPackage(srcDir, pkgs) 277 } 278 279 func (c *Context) loadPackage(srcDir string, pkgs map[string]*ast.Package) (*Package, error) { 280 mainPkg, ok := pkgs["main"] 281 if !ok { 282 for _, v := range pkgs { 283 mainPkg = v 284 break 285 } 286 } 287 if c.ctx.Mode&igop.DisableCustomBuiltin == 0 { 288 if f, err := igop.ParseBuiltin(c.fset, mainPkg.Name); err == nil { 289 mainPkg.GoFiles = map[string]*goast.File{"_igop_builtin.go": f} 290 } 291 } 292 conf := &cl.Config{Fset: c.fset} 293 conf.Importer = c 294 conf.LookupClass = func(ext string) (c *cl.Project, ok bool) { 295 c, ok = projects[ext] 296 return 297 } 298 if c.ctx.IsEvalMode() { 299 conf.NoSkipConstant = true 300 } 301 out, err := cl.NewPackage("", mainPkg, conf) 302 if err != nil { 303 return nil, err 304 } 305 return &Package{c.fset, out}, nil 306 }