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 }