/
compiler.go
149 lines (123 loc) · 3.03 KB
/
compiler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package compiler
import (
"bytes"
"go/ast"
"go/parser"
"go/token"
"go/types"
"honnef.co/go/js/xhr"
"github.com/gopherjs/gopherjs/compiler"
"github.com/gopherjs/gopherjs/js"
)
type Context struct {
GoCode string
JsCode string
Err error
done bool
waitChan chan bool
packages map[string]*compiler.Archive
pkgsToLoad map[string]struct{}
importContext *compiler.ImportContext
fileSet *token.FileSet
}
func NewContext(code string) *Context {
ctx := &Context{
GoCode: code,
done: false,
waitChan: make(chan bool),
packages: make(map[string]*compiler.Archive),
pkgsToLoad: make(map[string]struct{}),
fileSet: token.NewFileSet(),
}
ctx.importContext = &compiler.ImportContext{
Packages: make(map[string]*types.Package),
Import: func(path string) (*compiler.Archive, error) {
if pkg, found := ctx.packages[path]; found {
return pkg, nil
}
ctx.pkgsToLoad[path] = struct{}{}
return &compiler.Archive{}, nil
},
}
return ctx
}
func (ctx *Context) Wait() {
if !ctx.done {
<-ctx.waitChan
}
return
}
func (ctx *Context) Done() {
ctx.done = true
close(ctx.waitChan)
}
func (ctx *Context) SetError(err error) {
ctx.Err = err
ctx.Done()
}
type Compiler struct{}
func NewCompiler() *Compiler {
return &Compiler{}
}
func (cmp *Compiler) Compile(code string) (string, error) {
ctx := NewContext(code)
cmp.LowCompile(ctx)
ctx.Wait()
return ctx.JsCode, ctx.Err
}
func (cmp *Compiler) LowCompile(ctx *Context) {
file, err := parser.ParseFile(ctx.fileSet, "prog.go", []byte(ctx.GoCode), parser.ParseComments)
if err != nil {
ctx.SetError(err)
return
}
buf := bytes.NewBuffer(nil)
var compile func()
compile = func() {
ctx.pkgsToLoad = make(map[string]struct{})
mainPkg, err := compiler.Compile("main", []*ast.File{file}, ctx.fileSet, ctx.importContext, false)
ctx.packages["main"] = mainPkg
if err != nil && len(ctx.pkgsToLoad) == 0 {
ctx.SetError(err)
return
}
var allPkgs []*compiler.Archive
if len(ctx.pkgsToLoad) == 0 {
allPkgs, _ = compiler.ImportDependencies(mainPkg, ctx.importContext.Import)
}
if len(ctx.pkgsToLoad) != 0 {
pkgsReceived := 0
for path := range ctx.pkgsToLoad {
req := xhr.NewRequest("GET", "/agent/v1/pkg/"+path+".a.js")
req.ResponseType = xhr.ArrayBuffer
go func(path string) {
err := req.Send(nil)
if err != nil || req.Status != 200 {
ctx.SetError(err)
return
}
data := js.Global.Get("Uint8Array").New(req.Response).Interface().([]byte)
ctx.packages[path], err = compiler.ReadArchive(path+".a", path, bytes.NewReader(data), ctx.importContext.Packages)
if err != nil {
ctx.SetError(err)
return
}
pkgsReceived++
if pkgsReceived == len(ctx.pkgsToLoad) {
compile()
}
}(path)
}
return
}
compiler.WriteProgramCode(allPkgs, &compiler.SourceMapFilter{Writer: buf})
ctx.JsCode = buf.String()
ctx.Done()
}
go func() { compile() }()
return
}
func Compile(code string) (string, error) {
cmp := NewCompiler()
return cmp.Compile(code)
}