github.com/goplusjs/gopherjs@v1.2.6-0.20211206034512-f187917453b8/compiler/compiler.go (about)

     1  package compiler
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/gob"
     7  	"encoding/json"
     8  	"fmt"
     9  	"go/token"
    10  	"go/types"
    11  	"io"
    12  	"strings"
    13  
    14  	"github.com/goplusjs/gopherjs/compiler/prelude"
    15  	"golang.org/x/tools/go/gcexportdata"
    16  )
    17  
    18  var sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8}
    19  var reservedKeywords = make(map[string]bool)
    20  
    21  func init() {
    22  	for _, keyword := range []string{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "undefined", "var", "void", "volatile", "while", "with", "yield"} {
    23  		reservedKeywords[keyword] = true
    24  	}
    25  }
    26  
    27  type ErrorList []error
    28  
    29  func (err ErrorList) Error() string {
    30  	return err[0].Error()
    31  }
    32  
    33  type Archive struct {
    34  	ImportPath   string
    35  	Name         string
    36  	Version      string
    37  	Imports      []string
    38  	ExportData   []byte
    39  	Declarations []*Decl
    40  	IncJSCode    []byte
    41  	FileSet      []byte
    42  	Minified     bool
    43  }
    44  
    45  type Decl struct {
    46  	FullName        string
    47  	Vars            []string
    48  	DeclCode        []byte
    49  	MethodListCode  []byte
    50  	TypeInitCode    []byte
    51  	InitCode        []byte
    52  	DceObjectFilter string
    53  	DceMethodFilter string
    54  	DceDeps         []string
    55  	Blocking        bool
    56  }
    57  
    58  type Dependency struct {
    59  	Pkg    string
    60  	Type   string
    61  	Method string
    62  }
    63  
    64  func ImportDependencies(archive *Archive, importPkg func(string) (*Archive, error)) ([]*Archive, error) {
    65  	var deps []*Archive
    66  	paths := make(map[string]bool)
    67  	var collectDependencies func(path string) error
    68  	collectDependencies = func(path string) error {
    69  		if paths[path] {
    70  			return nil
    71  		}
    72  		dep, err := importPkg(path)
    73  		if err != nil {
    74  			return err
    75  		}
    76  		for _, imp := range dep.Imports {
    77  			if err := collectDependencies(imp); err != nil {
    78  				return err
    79  			}
    80  		}
    81  		deps = append(deps, dep)
    82  		paths[dep.ImportPath] = true
    83  		return nil
    84  	}
    85  
    86  	if err := collectDependencies("runtime"); err != nil {
    87  		return nil, err
    88  	}
    89  	for _, imp := range archive.Imports {
    90  		if err := collectDependencies(imp); err != nil {
    91  			return nil, err
    92  		}
    93  	}
    94  
    95  	deps = append(deps, archive)
    96  	return deps, nil
    97  }
    98  
    99  type dceInfo struct {
   100  	decl         *Decl
   101  	objectFilter string
   102  	methodFilter string
   103  }
   104  
   105  func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter) error {
   106  	mainPkg := pkgs[len(pkgs)-1]
   107  	minify := mainPkg.Minified
   108  
   109  	byFilter := make(map[string][]*dceInfo)
   110  	var pendingDecls []*Decl
   111  	for _, pkg := range pkgs {
   112  		for _, d := range pkg.Declarations {
   113  			if d.DceObjectFilter == "" && d.DceMethodFilter == "" {
   114  				pendingDecls = append(pendingDecls, d)
   115  				continue
   116  			}
   117  			info := &dceInfo{decl: d}
   118  			if d.DceObjectFilter != "" {
   119  				info.objectFilter = pkg.ImportPath + "." + d.DceObjectFilter
   120  				byFilter[info.objectFilter] = append(byFilter[info.objectFilter], info)
   121  			}
   122  			if d.DceMethodFilter != "" {
   123  				info.methodFilter = pkg.ImportPath + "." + d.DceMethodFilter
   124  				byFilter[info.methodFilter] = append(byFilter[info.methodFilter], info)
   125  			}
   126  		}
   127  	}
   128  
   129  	dceSelection := make(map[*Decl]struct{})
   130  	for len(pendingDecls) != 0 {
   131  		d := pendingDecls[len(pendingDecls)-1]
   132  		pendingDecls = pendingDecls[:len(pendingDecls)-1]
   133  
   134  		dceSelection[d] = struct{}{}
   135  
   136  		for _, dep := range d.DceDeps {
   137  			if infos, ok := byFilter[dep]; ok {
   138  				delete(byFilter, dep)
   139  				for _, info := range infos {
   140  					if info.objectFilter == dep {
   141  						info.objectFilter = ""
   142  					}
   143  					if info.methodFilter == dep {
   144  						info.methodFilter = ""
   145  					}
   146  					if info.objectFilter == "" && info.methodFilter == "" {
   147  						pendingDecls = append(pendingDecls, info.decl)
   148  					}
   149  				}
   150  			}
   151  		}
   152  	}
   153  
   154  	if _, err := w.Write([]byte("\"use strict\";\n(function() {\n\n")); err != nil {
   155  		return err
   156  	}
   157  	preludeJS := prelude.Prelude
   158  	if minify {
   159  		preludeJS = prelude.Minified
   160  	}
   161  	if _, err := io.WriteString(w, preludeJS); err != nil {
   162  		return err
   163  	}
   164  	if _, err := w.Write([]byte("\n")); err != nil {
   165  		return err
   166  	}
   167  
   168  	// write packages
   169  	for _, pkg := range pkgs {
   170  		if err := WritePkgCode(pkg, dceSelection, minify, w); err != nil {
   171  			return err
   172  		}
   173  	}
   174  
   175  	if _, err := w.Write([]byte("$synthesizeMethods();\nvar $mainPkg = $packages[\"" + string(mainPkg.ImportPath) + "\"];\n$packages[\"runtime\"].$init();\n$go($mainPkg.$init, []);\n$flushConsole();\n\n}).call(this);\n")); err != nil {
   176  		return err
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func WritePkgCode(pkg *Archive, dceSelection map[*Decl]struct{}, minify bool, w *SourceMapFilter) error {
   183  	if w.MappingCallback != nil && pkg.FileSet != nil {
   184  		w.fileSet = token.NewFileSet()
   185  		if err := w.fileSet.Read(json.NewDecoder(bytes.NewReader(pkg.FileSet)).Decode); err != nil {
   186  			panic(err)
   187  		}
   188  	}
   189  	if _, err := w.Write(pkg.IncJSCode); err != nil {
   190  		return err
   191  	}
   192  	if _, err := w.Write(removeWhitespace([]byte(fmt.Sprintf("$packages[\"%s\"] = (function() {\n", pkg.ImportPath)), minify)); err != nil {
   193  		return err
   194  	}
   195  	vars := []string{"$pkg = {}", "$init"}
   196  	var filteredDecls []*Decl
   197  	for _, d := range pkg.Declarations {
   198  		if _, ok := dceSelection[d]; ok {
   199  			vars = append(vars, d.Vars...)
   200  			filteredDecls = append(filteredDecls, d)
   201  		}
   202  	}
   203  	if _, err := w.Write(removeWhitespace([]byte(fmt.Sprintf("\tvar %s;\n", strings.Join(vars, ", "))), minify)); err != nil {
   204  		return err
   205  	}
   206  	for _, d := range filteredDecls {
   207  		if _, err := w.Write(d.DeclCode); err != nil {
   208  			return err
   209  		}
   210  	}
   211  	for _, d := range filteredDecls {
   212  		if _, err := w.Write(d.MethodListCode); err != nil {
   213  			return err
   214  		}
   215  	}
   216  	for _, d := range filteredDecls {
   217  		if _, err := w.Write(d.TypeInitCode); err != nil {
   218  			return err
   219  		}
   220  	}
   221  
   222  	if _, err := w.Write(removeWhitespace([]byte("\t$init = function() {\n\t\t$pkg.$init = function() {};\n\t\t/* */ var $f, $c = false, $s = 0, $r; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $s = $f.$s; $r = $f.$r; } s: while (true) { switch ($s) { case 0:\n"), minify)); err != nil {
   223  		return err
   224  	}
   225  	for _, d := range filteredDecls {
   226  		if _, err := w.Write(d.InitCode); err != nil {
   227  			return err
   228  		}
   229  	}
   230  	if _, err := w.Write(removeWhitespace([]byte("\t\t/* */ } return; } if ($f === undefined) { $f = { $blk: $init }; } $f.$s = $s; $f.$r = $r; return $f;\n\t};\n\t$pkg.$init = $init;\n\treturn $pkg;\n})();"), minify)); err != nil {
   231  		return err
   232  	}
   233  	if _, err := w.Write([]byte("\n")); err != nil { // keep this \n even when minified
   234  		return err
   235  	}
   236  	return nil
   237  }
   238  
   239  func ReadArchive(filename, path string, r io.Reader, packages map[string]*types.Package) (*Archive, error) {
   240  	var a Archive
   241  	if err := gob.NewDecoder(r).Decode(&a); err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	var err error
   246  	packages[path], err = gcexportdata.Read(bytes.NewReader(a.ExportData), token.NewFileSet(), packages, path)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	return &a, nil
   252  }
   253  
   254  func WriteArchive(a *Archive, w io.Writer) error {
   255  	return gob.NewEncoder(w).Encode(a)
   256  }
   257  
   258  type SourceMapFilter struct {
   259  	Writer          io.Writer
   260  	MappingCallback func(generatedLine, generatedColumn int, originalPos token.Position)
   261  	line            int
   262  	column          int
   263  	fileSet         *token.FileSet
   264  }
   265  
   266  func (f *SourceMapFilter) Write(p []byte) (n int, err error) {
   267  	var n2 int
   268  	for {
   269  		i := bytes.IndexByte(p, '\b')
   270  		w := p
   271  		if i != -1 {
   272  			w = p[:i]
   273  		}
   274  
   275  		n2, err = f.Writer.Write(w)
   276  		n += n2
   277  		for {
   278  			i := bytes.IndexByte(w, '\n')
   279  			if i == -1 {
   280  				f.column += len(w)
   281  				break
   282  			}
   283  			f.line++
   284  			f.column = 0
   285  			w = w[i+1:]
   286  		}
   287  
   288  		if err != nil || i == -1 {
   289  			return
   290  		}
   291  		if f.MappingCallback != nil {
   292  			f.MappingCallback(f.line+1, f.column, f.fileSet.Position(token.Pos(binary.BigEndian.Uint32(p[i+1:i+5]))))
   293  		}
   294  		p = p[i+5:]
   295  		n += 5
   296  	}
   297  }