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