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

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