1package compiler
2
3import (
4	"bytes"
5	"encoding/binary"
6	"fmt"
7	"go/ast"
8	"go/constant"
9	"go/token"
10	"go/types"
11	"net/url"
12	"sort"
13	"strconv"
14	"strings"
15	"text/template"
16	"unicode"
17
18	"github.com/gopherjs/gopherjs/compiler/analysis"
19	"github.com/gopherjs/gopherjs/compiler/typesutil"
20)
21
22func (c *funcContext) Write(b []byte) (int, error) {
23	c.writePos()
24	c.output = append(c.output, b...)
25	return len(b), nil
26}
27
28func (c *funcContext) Printf(format string, values ...interface{}) {
29	c.Write([]byte(strings.Repeat("\t", c.p.indentation)))
30	fmt.Fprintf(c, format, values...)
31	c.Write([]byte{'\n'})
32	c.Write(c.delayedOutput)
33	c.delayedOutput = nil
34}
35
36func (c *funcContext) PrintCond(cond bool, onTrue, onFalse string) {
37	if !cond {
38		c.Printf("/* %s */ %s", strings.Replace(onTrue, "*/", "<star>/", -1), onFalse)
39		return
40	}
41	c.Printf("%s", onTrue)
42}
43
44func (c *funcContext) SetPos(pos token.Pos) {
45	c.posAvailable = true
46	c.pos = pos
47}
48
49func (c *funcContext) writePos() {
50	if c.posAvailable {
51		c.posAvailable = false
52		c.Write([]byte{'\b'})
53		binary.Write(c, binary.BigEndian, uint32(c.pos))
54	}
55}
56
57func (c *funcContext) Indent(f func()) {
58	c.p.indentation++
59	f()
60	c.p.indentation--
61}
62
63func (c *funcContext) CatchOutput(indent int, f func()) []byte {
64	origoutput := c.output
65	c.output = nil
66	c.p.indentation += indent
67	f()
68	c.writePos()
69	catched := c.output
70	c.output = origoutput
71	c.p.indentation -= indent
72	return catched
73}
74
75func (c *funcContext) Delayed(f func()) {
76	c.delayedOutput = c.CatchOutput(0, f)
77}
78
79func (c *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, ellipsis bool) []string {
80	if len(argExprs) == 1 {
81		if tuple, isTuple := c.p.TypeOf(argExprs[0]).(*types.Tuple); isTuple {
82			tupleVar := c.newVariable("_tuple")
83			c.Printf("%s = %s;", tupleVar, c.translateExpr(argExprs[0]))
84			argExprs = make([]ast.Expr, tuple.Len())
85			for i := range argExprs {
86				argExprs[i] = c.newIdent(c.formatExpr("%s[%d]", tupleVar, i).String(), tuple.At(i).Type())
87			}
88		}
89	}
90
91	paramsLen := sig.Params().Len()
92
93	var varargType *types.Slice
94	if sig.Variadic() && !ellipsis {
95		varargType = sig.Params().At(paramsLen - 1).Type().(*types.Slice)
96	}
97
98	preserveOrder := false
99	for i := 1; i < len(argExprs); i++ {
100		preserveOrder = preserveOrder || c.Blocking[argExprs[i]]
101	}
102
103	args := make([]string, len(argExprs))
104	for i, argExpr := range argExprs {
105		var argType types.Type
106		switch {
107		case varargType != nil && i >= paramsLen-1:
108			argType = varargType.Elem()
109		default:
110			argType = sig.Params().At(i).Type()
111		}
112
113		arg := c.translateImplicitConversionWithCloning(argExpr, argType).String()
114
115		if preserveOrder && c.p.Types[argExpr].Value == nil {
116			argVar := c.newVariable("_arg")
117			c.Printf("%s = %s;", argVar, arg)
118			arg = argVar
119		}
120
121		args[i] = arg
122	}
123
124	if varargType != nil {
125		return append(args[:paramsLen-1], fmt.Sprintf("new %s([%s])", c.typeName(varargType), strings.Join(args[paramsLen-1:], ", ")))
126	}
127	return args
128}
129
130func (c *funcContext) translateSelection(sel selection, pos token.Pos) ([]string, string) {
131	var fields []string
132	t := sel.Recv()
133	for _, index := range sel.Index() {
134		if ptr, isPtr := t.(*types.Pointer); isPtr {
135			t = ptr.Elem()
136		}
137		s := t.Underlying().(*types.Struct)
138		if jsTag := getJsTag(s.Tag(index)); jsTag != "" {
139			jsFieldName := s.Field(index).Name()
140			for {
141				fields = append(fields, fieldName(s, 0))
142				ft := s.Field(0).Type()
143				if typesutil.IsJsObject(ft) {
144					return fields, jsTag
145				}
146				ft = ft.Underlying()
147				if ptr, ok := ft.(*types.Pointer); ok {
148					ft = ptr.Elem().Underlying()
149				}
150				var ok bool
151				s, ok = ft.(*types.Struct)
152				if !ok || s.NumFields() == 0 {
153					c.p.errList = append(c.p.errList, types.Error{Fset: c.p.fileSet, Pos: pos, Msg: fmt.Sprintf("could not find field with type *js.Object for 'js' tag of field '%s'", jsFieldName), Soft: true})
154					return nil, ""
155				}
156			}
157		}
158		fields = append(fields, fieldName(s, index))
159		t = s.Field(index).Type()
160	}
161	return fields, ""
162}
163
164var nilObj = types.Universe.Lookup("nil")
165
166func (c *funcContext) zeroValue(ty types.Type) ast.Expr {
167	switch t := ty.Underlying().(type) {
168	case *types.Basic:
169		switch {
170		case isBoolean(t):
171			return c.newConst(ty, constant.MakeBool(false))
172		case isNumeric(t):
173			return c.newConst(ty, constant.MakeInt64(0))
174		case isString(t):
175			return c.newConst(ty, constant.MakeString(""))
176		case t.Kind() == types.UnsafePointer:
177			// fall through to "nil"
178		case t.Kind() == types.UntypedNil:
179			panic("Zero value for untyped nil.")
180		default:
181			panic(fmt.Sprintf("Unhandled basic type: %v\n", t))
182		}
183	case *types.Array, *types.Struct:
184		return c.setType(&ast.CompositeLit{}, ty)
185	case *types.Chan, *types.Interface, *types.Map, *types.Signature, *types.Slice, *types.Pointer:
186		// fall through to "nil"
187	default:
188		panic(fmt.Sprintf("Unhandled type: %T\n", t))
189	}
190	id := c.newIdent("nil", ty)
191	c.p.Uses[id] = nilObj
192	return id
193}
194
195func (c *funcContext) newConst(t types.Type, value constant.Value) ast.Expr {
196	id := &ast.Ident{}
197	c.p.Types[id] = types.TypeAndValue{Type: t, Value: value}
198	return id
199}
200
201func (c *funcContext) newVariable(name string) string {
202	return c.newVariableWithLevel(name, false)
203}
204
205func (c *funcContext) newVariableWithLevel(name string, pkgLevel bool) string {
206	if name == "" {
207		panic("newVariable: empty name")
208	}
209	name = encodeIdent(name)
210	if c.p.minify {
211		i := 0
212		for {
213			offset := int('a')
214			if pkgLevel {
215				offset = int('A')
216			}
217			j := i
218			name = ""
219			for {
220				name = string(offset+(j%26)) + name
221				j = j/26 - 1
222				if j == -1 {
223					break
224				}
225			}
226			if c.allVars[name] == 0 {
227				break
228			}
229			i++
230		}
231	}
232	n := c.allVars[name]
233	c.allVars[name] = n + 1
234	varName := name
235	if n > 0 {
236		varName = fmt.Sprintf("%s$%d", name, n)
237	}
238
239	if pkgLevel {
240		for c2 := c.parent; c2 != nil; c2 = c2.parent {
241			c2.allVars[name] = n + 1
242		}
243		return varName
244	}
245
246	c.localVars = append(c.localVars, varName)
247	return varName
248}
249
250func (c *funcContext) newIdent(name string, t types.Type) *ast.Ident {
251	ident := ast.NewIdent(name)
252	c.setType(ident, t)
253	obj := types.NewVar(0, c.p.Pkg, name, t)
254	c.p.Uses[ident] = obj
255	c.p.objectNames[obj] = name
256	return ident
257}
258
259func (c *funcContext) setType(e ast.Expr, t types.Type) ast.Expr {
260	c.p.Types[e] = types.TypeAndValue{Type: t}
261	return e
262}
263
264func (c *funcContext) pkgVar(pkg *types.Package) string {
265	if pkg == c.p.Pkg {
266		return "$pkg"
267	}
268
269	pkgVar, found := c.p.pkgVars[pkg.Path()]
270	if !found {
271		pkgVar = fmt.Sprintf(`$packages["%s"]`, pkg.Path())
272	}
273	return pkgVar
274}
275
276func isVarOrConst(o types.Object) bool {
277	switch o.(type) {
278	case *types.Var, *types.Const:
279		return true
280	}
281	return false
282}
283
284func isPkgLevel(o types.Object) bool {
285	return o.Parent() != nil && o.Parent().Parent() == types.Universe
286}
287
288func (c *funcContext) objectName(o types.Object) string {
289	if isPkgLevel(o) {
290		c.p.dependencies[o] = true
291
292		if o.Pkg() != c.p.Pkg || (isVarOrConst(o) && o.Exported()) {
293			return c.pkgVar(o.Pkg()) + "." + o.Name()
294		}
295	}
296
297	name, ok := c.p.objectNames[o]
298	if !ok {
299		name = c.newVariableWithLevel(o.Name(), isPkgLevel(o))
300		c.p.objectNames[o] = name
301	}
302
303	if v, ok := o.(*types.Var); ok && c.p.escapingVars[v] {
304		return name + "[0]"
305	}
306	return name
307}
308
309func (c *funcContext) varPtrName(o *types.Var) string {
310	if isPkgLevel(o) && o.Exported() {
311		return c.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr"
312	}
313
314	name, ok := c.p.varPtrNames[o]
315	if !ok {
316		name = c.newVariableWithLevel(o.Name()+"$ptr", isPkgLevel(o))
317		c.p.varPtrNames[o] = name
318	}
319	return name
320}
321
322func (c *funcContext) typeName(ty types.Type) string {
323	switch t := ty.(type) {
324	case *types.Basic:
325		return "$" + toJavaScriptType(t)
326	case *types.Named:
327		if t.Obj().Name() == "error" {
328			return "$error"
329		}
330		return c.objectName(t.Obj())
331	case *types.Interface:
332		if t.Empty() {
333			return "$emptyInterface"
334		}
335	}
336
337	anonType, ok := c.p.anonTypeMap.At(ty).(*types.TypeName)
338	if !ok {
339		c.initArgs(ty) // cause all embedded types to be registered
340		varName := c.newVariableWithLevel(strings.ToLower(typeKind(ty)[5:])+"Type", true)
341		anonType = types.NewTypeName(token.NoPos, c.p.Pkg, varName, ty) // fake types.TypeName
342		c.p.anonTypes = append(c.p.anonTypes, anonType)
343		c.p.anonTypeMap.Set(ty, anonType)
344	}
345	c.p.dependencies[anonType] = true
346	return anonType.Name()
347}
348
349func (c *funcContext) externalize(s string, t types.Type) string {
350	if typesutil.IsJsObject(t) {
351		return s
352	}
353	switch u := t.Underlying().(type) {
354	case *types.Basic:
355		if isNumeric(u) && !is64Bit(u) && !isComplex(u) {
356			return s
357		}
358		if u.Kind() == types.UntypedNil {
359			return "null"
360		}
361	}
362	return fmt.Sprintf("$externalize(%s, %s)", s, c.typeName(t))
363}
364
365func (c *funcContext) handleEscapingVars(n ast.Node) {
366	newEscapingVars := make(map[*types.Var]bool)
367	for escaping := range c.p.escapingVars {
368		newEscapingVars[escaping] = true
369	}
370	c.p.escapingVars = newEscapingVars
371
372	var names []string
373	objs := analysis.EscapingObjects(n, c.p.Info.Info)
374	sort.Slice(objs, func(i, j int) bool {
375		if objs[i].Name() == objs[j].Name() {
376			return objs[i].Pos() < objs[j].Pos()
377		}
378		return objs[i].Name() < objs[j].Name()
379	})
380	for _, obj := range objs {
381		names = append(names, c.objectName(obj))
382		c.p.escapingVars[obj] = true
383	}
384	sort.Strings(names)
385	for _, name := range names {
386		c.Printf("%s = [%s];", name, name)
387	}
388}
389
390func fieldName(t *types.Struct, i int) string {
391	name := t.Field(i).Name()
392	if name == "_" || reservedKeywords[name] {
393		return fmt.Sprintf("%s$%d", name, i)
394	}
395	return name
396}
397
398func typeKind(ty types.Type) string {
399	switch t := ty.Underlying().(type) {
400	case *types.Basic:
401		return "$kind" + toJavaScriptType(t)
402	case *types.Array:
403		return "$kindArray"
404	case *types.Chan:
405		return "$kindChan"
406	case *types.Interface:
407		return "$kindInterface"
408	case *types.Map:
409		return "$kindMap"
410	case *types.Signature:
411		return "$kindFunc"
412	case *types.Slice:
413		return "$kindSlice"
414	case *types.Struct:
415		return "$kindStruct"
416	case *types.Pointer:
417		return "$kindPtr"
418	default:
419		panic(fmt.Sprintf("Unhandled type: %T\n", t))
420	}
421}
422
423func toJavaScriptType(t *types.Basic) string {
424	switch t.Kind() {
425	case types.UntypedInt:
426		return "Int"
427	case types.Byte:
428		return "Uint8"
429	case types.Rune:
430		return "Int32"
431	case types.UnsafePointer:
432		return "UnsafePointer"
433	default:
434		name := t.String()
435		return strings.ToUpper(name[:1]) + name[1:]
436	}
437}
438
439func is64Bit(t *types.Basic) bool {
440	return t.Kind() == types.Int64 || t.Kind() == types.Uint64
441}
442
443func isBoolean(t *types.Basic) bool {
444	return t.Info()&types.IsBoolean != 0
445}
446
447func isComplex(t *types.Basic) bool {
448	return t.Info()&types.IsComplex != 0
449}
450
451func isFloat(t *types.Basic) bool {
452	return t.Info()&types.IsFloat != 0
453}
454
455func isInteger(t *types.Basic) bool {
456	return t.Info()&types.IsInteger != 0
457}
458
459func isNumeric(t *types.Basic) bool {
460	return t.Info()&types.IsNumeric != 0
461}
462
463func isString(t *types.Basic) bool {
464	return t.Info()&types.IsString != 0
465}
466
467func isUnsigned(t *types.Basic) bool {
468	return t.Info()&types.IsUnsigned != 0
469}
470
471func isBlank(expr ast.Expr) bool {
472	if expr == nil {
473		return true
474	}
475	if id, isIdent := expr.(*ast.Ident); isIdent {
476		return id.Name == "_"
477	}
478	return false
479}
480
481func isWrapped(ty types.Type) bool {
482	switch t := ty.Underlying().(type) {
483	case *types.Basic:
484		return !is64Bit(t) && !isComplex(t) && t.Kind() != types.UntypedNil
485	case *types.Array, *types.Chan, *types.Map, *types.Signature:
486		return true
487	case *types.Pointer:
488		_, isArray := t.Elem().Underlying().(*types.Array)
489		return isArray
490	}
491	return false
492}
493
494func encodeString(s string) string {
495	buffer := bytes.NewBuffer(nil)
496	for _, r := range []byte(s) {
497		switch r {
498		case '\b':
499			buffer.WriteString(`\b`)
500		case '\f':
501			buffer.WriteString(`\f`)
502		case '\n':
503			buffer.WriteString(`\n`)
504		case '\r':
505			buffer.WriteString(`\r`)
506		case '\t':
507			buffer.WriteString(`\t`)
508		case '\v':
509			buffer.WriteString(`\v`)
510		case '"':
511			buffer.WriteString(`\"`)
512		case '\\':
513			buffer.WriteString(`\\`)
514		default:
515			if r < 0x20 || r > 0x7E {
516				fmt.Fprintf(buffer, `\x%02X`, r)
517				continue
518			}
519			buffer.WriteByte(r)
520		}
521	}
522	return `"` + buffer.String() + `"`
523}
524
525func getJsTag(tag string) string {
526	for tag != "" {
527		// skip leading space
528		i := 0
529		for i < len(tag) && tag[i] == ' ' {
530			i++
531		}
532		tag = tag[i:]
533		if tag == "" {
534			break
535		}
536
537		// scan to colon.
538		// a space or a quote is a syntax error
539		i = 0
540		for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' {
541			i++
542		}
543		if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
544			break
545		}
546		name := string(tag[:i])
547		tag = tag[i+1:]
548
549		// scan quoted string to find value
550		i = 1
551		for i < len(tag) && tag[i] != '"' {
552			if tag[i] == '\\' {
553				i++
554			}
555			i++
556		}
557		if i >= len(tag) {
558			break
559		}
560		qvalue := string(tag[:i+1])
561		tag = tag[i+1:]
562
563		if name == "js" {
564			value, _ := strconv.Unquote(qvalue)
565			return value
566		}
567	}
568	return ""
569}
570
571func needsSpace(c byte) bool {
572	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$'
573}
574
575func removeWhitespace(b []byte, minify bool) []byte {
576	if !minify {
577		return b
578	}
579
580	var out []byte
581	var previous byte
582	for len(b) > 0 {
583		switch b[0] {
584		case '\b':
585			out = append(out, b[:5]...)
586			b = b[5:]
587			continue
588		case ' ', '\t', '\n':
589			if (!needsSpace(previous) || !needsSpace(b[1])) && !(previous == '-' && b[1] == '-') {
590				b = b[1:]
591				continue
592			}
593		case '"':
594			out = append(out, '"')
595			b = b[1:]
596			for {
597				i := bytes.IndexAny(b, "\"\\")
598				out = append(out, b[:i]...)
599				b = b[i:]
600				if b[0] == '"' {
601					break
602				}
603				// backslash
604				out = append(out, b[:2]...)
605				b = b[2:]
606			}
607		case '/':
608			if b[1] == '*' {
609				i := bytes.Index(b[2:], []byte("*/"))
610				b = b[i+4:]
611				continue
612			}
613		}
614		out = append(out, b[0])
615		previous = b[0]
616		b = b[1:]
617	}
618	return out
619}
620
621func rangeCheck(pattern string, constantIndex, array bool) string {
622	if constantIndex && array {
623		return pattern
624	}
625	lengthProp := "$length"
626	if array {
627		lengthProp = "length"
628	}
629	check := "%2f >= %1e." + lengthProp
630	if !constantIndex {
631		check = "(%2f < 0 || " + check + ")"
632	}
633	return "(" + check + ` ? ($throwRuntimeError("index out of range"), undefined) : ` + pattern + ")"
634}
635
636func endsWithReturn(stmts []ast.Stmt) bool {
637	if len(stmts) > 0 {
638		if _, ok := stmts[len(stmts)-1].(*ast.ReturnStmt); ok {
639			return true
640		}
641	}
642	return false
643}
644
645func encodeIdent(name string) string {
646	return strings.Replace(url.QueryEscape(name), "%", "$", -1)
647}
648
649// formatJSStructTagVal returns JavaScript code for accessing an object's property
650// identified by jsTag. It prefers the dot notation over the bracket notation when
651// possible, since the dot notation produces slightly smaller output.
652//
653// For example:
654//
655// 	"my_name" -> ".my_name"
656// 	"my name" -> `["my name"]`
657//
658// For more information about JavaScript property accessors and identifiers, see
659// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors and
660// https://developer.mozilla.org/en-US/docs/Glossary/Identifier.
661//
662func formatJSStructTagVal(jsTag string) string {
663	for i, r := range jsTag {
664		ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_'
665		if !ok {
666			// Saw an invalid JavaScript identifier character,
667			// so use bracket notation.
668			return `["` + template.JSEscapeString(jsTag) + `"]`
669		}
670	}
671	// Safe to use dot notation without any escaping.
672	return "." + jsTag
673}
674