Skip to content

Commit b1788a8

Browse files
authored
Merge pull request #1318 from nevkontakte/gng6
Move function translation code into a separate file.
2 parents cfa0783 + 8595378 commit b1788a8

File tree

3 files changed

+321
-230
lines changed

3 files changed

+321
-230
lines changed

compiler/expressions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
201201
}
202202

203203
case *ast.FuncLit:
204-
_, fun := translateFunction(e.Type, nil, e.Body, fc, exprType.(*types.Signature), fc.pkgCtx.FuncLitInfos[e], "", typeparams.Instance{})
204+
fun := fc.nestedFunctionContext(fc.pkgCtx.FuncLitInfos[e], exprType.(*types.Signature), typeparams.Instance{}).translateFunctionBody(e.Type, nil, e.Body, "")
205205
if len(fc.pkgCtx.escapingVars) != 0 {
206206
names := make([]string, 0, len(fc.pkgCtx.escapingVars))
207207
for obj := range fc.pkgCtx.escapingVars {

compiler/functions.go

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
package compiler
2+
3+
// functions.go contains logic responsible for translating top-level functions
4+
// and function literals.
5+
6+
import (
7+
"bytes"
8+
"fmt"
9+
"go/ast"
10+
"go/types"
11+
"sort"
12+
"strings"
13+
14+
"github.com/gopherjs/gopherjs/compiler/analysis"
15+
"github.com/gopherjs/gopherjs/compiler/astutil"
16+
"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
17+
"github.com/gopherjs/gopherjs/compiler/typesutil"
18+
)
19+
20+
// newFunctionContext creates a new nested context for a function corresponding
21+
// to the provided info and instance.
22+
func (fc *funcContext) nestedFunctionContext(info *analysis.FuncInfo, sig *types.Signature, inst typeparams.Instance) *funcContext {
23+
if info == nil {
24+
panic(fmt.Errorf("missing *analysis.FuncInfo"))
25+
}
26+
if sig == nil {
27+
panic(fmt.Errorf("missing *types.Signature"))
28+
}
29+
30+
c := &funcContext{
31+
FuncInfo: info,
32+
pkgCtx: fc.pkgCtx,
33+
parent: fc,
34+
allVars: make(map[string]int, len(fc.allVars)),
35+
localVars: []string{},
36+
flowDatas: map[*types.Label]*flowData{nil: {}},
37+
caseCounter: 1,
38+
labelCases: make(map[*types.Label]int),
39+
typeResolver: fc.typeResolver,
40+
objectNames: map[types.Object]string{},
41+
sig: &typesutil.Signature{Sig: sig},
42+
}
43+
for k, v := range fc.allVars {
44+
c.allVars[k] = v
45+
}
46+
47+
if sig.TypeParams().Len() > 0 {
48+
c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, typeparams.ToSlice(sig.TypeParams()), inst.TArgs)
49+
} else if sig.RecvTypeParams().Len() > 0 {
50+
c.typeResolver = typeparams.NewResolver(c.pkgCtx.typesCtx, typeparams.ToSlice(sig.RecvTypeParams()), inst.TArgs)
51+
}
52+
if c.objectNames == nil {
53+
c.objectNames = map[types.Object]string{}
54+
}
55+
56+
return c
57+
}
58+
59+
// translateTopLevelFunction translates a top-level function declaration
60+
// (standalone function or method) into a corresponding JS function.
61+
//
62+
// Returns a string with a JavaScript statements that define the function or
63+
// method. For methods it returns declarations for both value- and
64+
// pointer-receiver (if appropriate).
65+
func (fc *funcContext) translateTopLevelFunction(fun *ast.FuncDecl, inst typeparams.Instance) []byte {
66+
if fun.Recv == nil {
67+
return fc.translateStandaloneFunction(fun, inst)
68+
}
69+
70+
o := inst.Object.(*types.Func)
71+
info := fc.pkgCtx.FuncDeclInfos[o]
72+
73+
sig := o.Type().(*types.Signature)
74+
// primaryFunction generates a JS function equivalent of the current Go function
75+
// and assigns it to the JS expression defined by lvalue.
76+
primaryFunction := func(lvalue string) []byte {
77+
if fun.Body == nil {
78+
return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName()))
79+
}
80+
81+
var recv *ast.Ident
82+
if fun.Recv != nil && fun.Recv.List[0].Names != nil {
83+
recv = fun.Recv.List[0].Names[0]
84+
}
85+
fun := fc.nestedFunctionContext(info, sig, inst).translateFunctionBody(fun.Type, recv, fun.Body, lvalue)
86+
return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun))
87+
}
88+
89+
funName := fun.Name.Name
90+
if reservedKeywords[funName] {
91+
funName += "$"
92+
}
93+
94+
// proxyFunction generates a JS function that forwards the call to the actual
95+
// method implementation for the alternate receiver (e.g. pointer vs
96+
// non-pointer).
97+
proxyFunction := func(lvalue, receiver string) []byte {
98+
fun := fmt.Sprintf("function(...$args) { return %s.%s(...$args); }", receiver, funName)
99+
return []byte(fmt.Sprintf("\t%s = %s;\n", lvalue, fun))
100+
}
101+
102+
recvInst := inst.Recv()
103+
recvInstName := fc.instName(recvInst)
104+
recvType := recvInst.Object.Type().(*types.Named)
105+
106+
// Objects the method should be assigned to for the plain and pointer type
107+
// of the receiver.
108+
prototypeVar := fmt.Sprintf("%s.prototype.%s", recvInstName, funName)
109+
ptrPrototypeVar := fmt.Sprintf("$ptrType(%s).prototype.%s", recvInstName, funName)
110+
111+
code := bytes.NewBuffer(nil)
112+
113+
if _, isStruct := recvType.Underlying().(*types.Struct); isStruct {
114+
// Structs are a special case: they are represented by JS objects and their
115+
// methods are the underlying object's methods. Due to reference semantics
116+
// of the JS variables, the actual backing object is considered to represent
117+
// the pointer-to-struct type, and methods are attacher to it first and
118+
// foremost.
119+
code.Write(primaryFunction(ptrPrototypeVar))
120+
code.Write(proxyFunction(prototypeVar, "this.$val"))
121+
return code.Bytes()
122+
}
123+
124+
if ptr, isPointer := sig.Recv().Type().(*types.Pointer); isPointer {
125+
if _, isArray := ptr.Elem().Underlying().(*types.Array); isArray {
126+
// Pointer-to-array is another special case.
127+
// TODO(nevkontakte) Find out and document why.
128+
code.Write(primaryFunction(prototypeVar))
129+
code.Write(proxyFunction(ptrPrototypeVar, fmt.Sprintf("(new %s(this.$get()))", recvInstName)))
130+
return code.Bytes()
131+
}
132+
133+
// Methods with pointer-receiver are only attached to the pointer-receiver
134+
// type.
135+
return primaryFunction(ptrPrototypeVar)
136+
}
137+
138+
// Methods defined for non-pointer receiver are attached to both pointer- and
139+
// non-pointer-receiver types.
140+
recvExpr := "this.$get()"
141+
if isWrapped(recvType) {
142+
recvExpr = fmt.Sprintf("new %s(%s)", recvInstName, recvExpr)
143+
}
144+
code.Write(primaryFunction(prototypeVar))
145+
code.Write(proxyFunction(ptrPrototypeVar, recvExpr))
146+
return code.Bytes()
147+
}
148+
149+
// translateStandaloneFunction translates a package-level function.
150+
//
151+
// It returns a JS statements which define the corresponding function in a
152+
// package context. Exported functions are also assigned to the `$pkg` object.
153+
func (fc *funcContext) translateStandaloneFunction(fun *ast.FuncDecl, inst typeparams.Instance) []byte {
154+
o := inst.Object.(*types.Func)
155+
info := fc.pkgCtx.FuncDeclInfos[o]
156+
sig := o.Type().(*types.Signature)
157+
158+
if fun.Recv != nil {
159+
panic(fmt.Errorf("expected standalone function, got method: %s", o))
160+
}
161+
162+
lvalue := fc.instName(inst)
163+
164+
if fun.Body == nil {
165+
return []byte(fmt.Sprintf("\t%s = function() {\n\t\t$throwRuntimeError(\"native function not implemented: %s\");\n\t};\n", lvalue, o.FullName()))
166+
}
167+
168+
body := fc.nestedFunctionContext(info, sig, inst).translateFunctionBody(fun.Type, nil, fun.Body, lvalue)
169+
code := bytes.NewBuffer(nil)
170+
fmt.Fprintf(code, "\t%s = %s;\n", lvalue, body)
171+
if fun.Name.IsExported() {
172+
fmt.Fprintf(code, "\t$pkg.%s = %s;\n", encodeIdent(fun.Name.Name), lvalue)
173+
}
174+
return code.Bytes()
175+
}
176+
177+
// translateFunctionBody translates body of a top-level or literal function.
178+
//
179+
// It returns a JS function expression that represents the given Go function.
180+
// Function receiver must have been created with nestedFunctionContext() to have
181+
// required metadata set up.
182+
func (fc *funcContext) translateFunctionBody(typ *ast.FuncType, recv *ast.Ident, body *ast.BlockStmt, funcRef string) string {
183+
prevEV := fc.pkgCtx.escapingVars
184+
185+
// Generate a list of function argument variables. Since Go allows nameless
186+
// arguments, we have to generate synthetic names for their JS counterparts.
187+
var args []string
188+
for _, param := range typ.Params.List {
189+
if len(param.Names) == 0 {
190+
args = append(args, fc.newLocalVariable("param"))
191+
continue
192+
}
193+
for _, ident := range param.Names {
194+
if isBlank(ident) {
195+
args = append(args, fc.newLocalVariable("param"))
196+
continue
197+
}
198+
args = append(args, fc.objectName(fc.pkgCtx.Defs[ident]))
199+
}
200+
}
201+
202+
bodyOutput := string(fc.CatchOutput(1, func() {
203+
if len(fc.Blocking) != 0 {
204+
fc.pkgCtx.Scopes[body] = fc.pkgCtx.Scopes[typ]
205+
fc.handleEscapingVars(body)
206+
}
207+
208+
if fc.sig != nil && fc.sig.HasNamedResults() {
209+
fc.resultNames = make([]ast.Expr, fc.sig.Sig.Results().Len())
210+
for i := 0; i < fc.sig.Sig.Results().Len(); i++ {
211+
result := fc.sig.Sig.Results().At(i)
212+
typ := fc.typeResolver.Substitute(result.Type())
213+
fc.Printf("%s = %s;", fc.objectName(result), fc.translateExpr(fc.zeroValue(typ)).String())
214+
id := ast.NewIdent("")
215+
fc.pkgCtx.Uses[id] = result
216+
fc.resultNames[i] = fc.setType(id, typ)
217+
}
218+
}
219+
220+
if recv != nil && !isBlank(recv) {
221+
this := "this"
222+
if isWrapped(fc.typeOf(recv)) {
223+
this = "this.$val" // Unwrap receiver value.
224+
}
225+
fc.Printf("%s = %s;", fc.translateExpr(recv), this)
226+
}
227+
228+
fc.translateStmtList(body.List)
229+
if len(fc.Flattened) != 0 && !astutil.EndsWithReturn(body.List) {
230+
fc.translateStmt(&ast.ReturnStmt{}, nil)
231+
}
232+
}))
233+
234+
sort.Strings(fc.localVars)
235+
236+
var prefix, suffix, functionName string
237+
238+
if len(fc.Flattened) != 0 {
239+
// $s contains an index of the switch case a blocking function reached
240+
// before getting blocked. When execution resumes, it will allow to continue
241+
// from where we left off.
242+
fc.localVars = append(fc.localVars, "$s")
243+
prefix = prefix + " $s = $s || 0;"
244+
}
245+
246+
if fc.HasDefer {
247+
fc.localVars = append(fc.localVars, "$deferred")
248+
suffix = " }" + suffix
249+
if len(fc.Blocking) != 0 {
250+
suffix = " }" + suffix
251+
}
252+
}
253+
254+
localVarDefs := "" // Function-local var declaration at the top.
255+
256+
if len(fc.Blocking) != 0 {
257+
if funcRef == "" {
258+
funcRef = "$b"
259+
functionName = " $b"
260+
}
261+
262+
localVars := append([]string{}, fc.localVars...)
263+
// There are several special variables involved in handling blocking functions:
264+
// $r is sometimes used as a temporary variable to store blocking call result.
265+
// $c indicates that a function is being resumed after a blocking call when set to true.
266+
// $f is an object used to save and restore function context for blocking calls.
267+
localVars = append(localVars, "$r")
268+
// If a blocking function is being resumed, initialize local variables from the saved context.
269+
localVarDefs = fmt.Sprintf("var {%s, $c} = $restore(this, {%s});\n", strings.Join(localVars, ", "), strings.Join(args, ", "))
270+
// If the function gets blocked, save local variables for future.
271+
saveContext := fmt.Sprintf("var $f = {$blk: "+funcRef+", $c: true, $r, %s};", strings.Join(fc.localVars, ", "))
272+
273+
suffix = " " + saveContext + "return $f;" + suffix
274+
} else if len(fc.localVars) > 0 {
275+
// Non-blocking functions simply declare local variables with no need for restore support.
276+
localVarDefs = fmt.Sprintf("var %s;\n", strings.Join(fc.localVars, ", "))
277+
}
278+
279+
if fc.HasDefer {
280+
prefix = prefix + " var $err = null; try {"
281+
deferSuffix := " } catch(err) { $err = err;"
282+
if len(fc.Blocking) != 0 {
283+
deferSuffix += " $s = -1;"
284+
}
285+
if fc.resultNames == nil && fc.sig.HasResults() {
286+
deferSuffix += fmt.Sprintf(" return%s;", fc.translateResults(nil))
287+
}
288+
deferSuffix += " } finally { $callDeferred($deferred, $err);"
289+
if fc.resultNames != nil {
290+
deferSuffix += fmt.Sprintf(" if (!$curGoroutine.asleep) { return %s; }", fc.translateResults(fc.resultNames))
291+
}
292+
if len(fc.Blocking) != 0 {
293+
deferSuffix += " if($curGoroutine.asleep) {"
294+
}
295+
suffix = deferSuffix + suffix
296+
}
297+
298+
if len(fc.Flattened) != 0 {
299+
prefix = prefix + " s: while (true) { switch ($s) { case 0:"
300+
suffix = " } return; }" + suffix
301+
}
302+
303+
if fc.HasDefer {
304+
prefix = prefix + " $deferred = []; $curGoroutine.deferStack.push($deferred);"
305+
}
306+
307+
if prefix != "" {
308+
bodyOutput = fc.Indentation(1) + "/* */" + prefix + "\n" + bodyOutput
309+
}
310+
if suffix != "" {
311+
bodyOutput = bodyOutput + fc.Indentation(1) + "/* */" + suffix + "\n"
312+
}
313+
if localVarDefs != "" {
314+
bodyOutput = fc.Indentation(1) + localVarDefs + bodyOutput
315+
}
316+
317+
fc.pkgCtx.escapingVars = prevEV
318+
319+
return fmt.Sprintf("function%s(%s) {\n%s%s}", functionName, strings.Join(args, ", "), bodyOutput, fc.Indentation(0))
320+
}

0 commit comments

Comments
 (0)