1package compiler
2
3import (
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/gopherjs/gopherjs/compiler/prelude"
15	"golang.org/x/tools/go/gcexportdata"
16)
17
18var sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8}
19var reservedKeywords = make(map[string]bool)
20var _ = ___GOPHERJS_REQUIRES_GO_VERSION_1_11___ // Compile error on other Go versions, because they're not supported.
21
22func init() {
23	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"} {
24		reservedKeywords[keyword] = true
25	}
26}
27
28type ErrorList []error
29
30func (err ErrorList) Error() string {
31	return err[0].Error()
32}
33
34type Archive struct {
35	ImportPath   string
36	Name         string
37	Imports      []string
38	ExportData   []byte
39	Declarations []*Decl
40	IncJSCode    []byte
41	FileSet      []byte
42	Minified     bool
43}
44
45type 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
58type Dependency struct {
59	Pkg    string
60	Type   string
61	Method string
62}
63
64func 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
99type dceInfo struct {
100	decl         *Decl
101	objectFilter string
102	methodFilter string
103}
104
105func 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
182func 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
239func 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
254func WriteArchive(a *Archive, w io.Writer) error {
255	return gob.NewEncoder(w).Encode(a)
256}
257
258type 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
266func (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}
298