blob: 7fec5b2237c693c599524d149d66351eb2f9d146 [file] [log] [blame]
package compiler
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"net/url"
"regexp"
"runtime/debug"
"sort"
"strconv"
"strings"
"text/template"
"unicode"
"github.com/gopherjs/gopherjs/compiler/analysis"
"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
"github.com/gopherjs/gopherjs/compiler/typesutil"
)
// root returns the topmost function context corresponding to the package scope.
func (fc *funcContext) root() *funcContext {
if fc.isRoot() {
return fc
}
return fc.parent.root()
}
// isRoot returns true for the package-level context.
func (fc *funcContext) isRoot() bool {
return fc.parent == nil
}
func (fc *funcContext) Write(b []byte) (int, error) {
fc.writePos()
fc.output = append(fc.output, b...)
return len(b), nil
}
func (fc *funcContext) Printf(format string, values ...interface{}) {
fc.Write([]byte(fc.Indentation(0)))
fmt.Fprintf(fc, format, values...)
fc.Write([]byte{'\n'})
fc.Write(fc.delayedOutput)
fc.delayedOutput = nil
}
func (fc *funcContext) PrintCond(cond bool, onTrue, onFalse string) {
if !cond {
fc.Printf("/* %s */ %s", strings.Replace(onTrue, "*/", "<star>/", -1), onFalse)
return
}
fc.Printf("%s", onTrue)
}
func (fc *funcContext) SetPos(pos token.Pos) {
fc.posAvailable = true
fc.pos = pos
}
func (fc *funcContext) writePos() {
if fc.posAvailable {
fc.posAvailable = false
fc.Write([]byte{'\b'})
binary.Write(fc, binary.BigEndian, uint32(fc.pos))
}
}
// Indented increases generated code indentation level by 1 for the code emitted
// from the callback f.
func (fc *funcContext) Indented(f func()) {
fc.pkgCtx.indentation++
f()
fc.pkgCtx.indentation--
}
// Indentation returns a sequence of "\t" characters appropriate to the current
// generated code indentation level. The `extra` parameter provides relative
// indentation adjustment.
func (fc *funcContext) Indentation(extra int) string {
return strings.Repeat("\t", fc.pkgCtx.indentation+extra)
}
func (fc *funcContext) CatchOutput(indent int, f func()) []byte {
origoutput := fc.output
fc.output = nil
fc.pkgCtx.indentation += indent
f()
fc.writePos()
caught := fc.output
fc.output = origoutput
fc.pkgCtx.indentation -= indent
return caught
}
func (fc *funcContext) Delayed(f func()) {
fc.delayedOutput = fc.CatchOutput(0, f)
}
// CollectDCEDeps captures a list of Go objects (types, functions, etc.)
// the code translated inside f() depends on. The returned list of identifiers
// can be used in dead-code elimination.
//
// Note that calling CollectDCEDeps() inside another CollectDCEDeps() call is
// not allowed.
func (fc *funcContext) CollectDCEDeps(f func()) []string {
if fc.pkgCtx.dependencies != nil {
panic(bailout(fmt.Errorf("called funcContext.CollectDependencies() inside another funcContext.CollectDependencies() call")))
}
fc.pkgCtx.dependencies = make(map[types.Object]bool)
defer func() { fc.pkgCtx.dependencies = nil }()
f()
var deps []string
for o := range fc.pkgCtx.dependencies {
qualifiedName := o.Pkg().Path() + "." + o.Name()
if typesutil.IsMethod(o) {
qualifiedName += "~"
}
deps = append(deps, qualifiedName)
}
sort.Strings(deps)
return deps
}
// DeclareDCEDep records that the code that is currently being transpiled
// depends on a given Go object.
func (fc *funcContext) DeclareDCEDep(o types.Object) {
if fc.pkgCtx.dependencies == nil {
return // Dependencies are not being collected.
}
fc.pkgCtx.dependencies[o] = true
}
// expandTupleArgs converts a function call which argument is a tuple returned
// by another function into a set of individual call arguments corresponding to
// tuple elements.
//
// For example, for functions defined as:
//
// func a() (int, string) {return 42, "foo"}
// func b(a1 int, a2 string) {}
//
// ...the following statement:
//
// b(a())
//
// ...will be transformed into:
//
// _tuple := a()
// b(_tuple[0], _tuple[1])
func (fc *funcContext) expandTupleArgs(argExprs []ast.Expr) []ast.Expr {
if len(argExprs) != 1 {
return argExprs
}
tuple, isTuple := fc.typeOf(argExprs[0]).(*types.Tuple)
if !isTuple {
return argExprs
}
tupleVar := fc.newLocalVariable("_tuple")
fc.Printf("%s = %s;", tupleVar, fc.translateExpr(argExprs[0]))
argExprs = make([]ast.Expr, tuple.Len())
for i := range argExprs {
argExprs[i] = fc.newIdent(fc.formatExpr("%s[%d]", tupleVar, i).String(), tuple.At(i).Type())
}
return argExprs
}
func (fc *funcContext) translateArgs(sig *types.Signature, argExprs []ast.Expr, ellipsis bool) []string {
argExprs = fc.expandTupleArgs(argExprs)
sigTypes := typesutil.Signature{Sig: sig}
if sig.Variadic() && len(argExprs) == 0 {
return []string{fmt.Sprintf("%s.nil", fc.typeName(sigTypes.VariadicType()))}
}
preserveOrder := false
for i := 1; i < len(argExprs); i++ {
preserveOrder = preserveOrder || fc.Blocking[argExprs[i]]
}
args := make([]string, len(argExprs))
for i, argExpr := range argExprs {
arg := fc.translateImplicitConversionWithCloning(argExpr, sigTypes.Param(i, ellipsis)).String()
if preserveOrder && fc.pkgCtx.Types[argExpr].Value == nil {
argVar := fc.newLocalVariable("_arg")
fc.Printf("%s = %s;", argVar, arg)
arg = argVar
}
args[i] = arg
}
// If variadic arguments were passed in as individual elements, regroup them
// into a slice and pass it as a single argument.
if sig.Variadic() && !ellipsis {
required := args[:sigTypes.RequiredParams()]
var variadic string
if len(args) == sigTypes.RequiredParams() {
// If no variadic parameters were passed, the slice value defaults to nil.
variadic = fmt.Sprintf("%s.nil", fc.typeName(sigTypes.VariadicType()))
} else {
variadic = fmt.Sprintf("new %s([%s])", fc.typeName(sigTypes.VariadicType()), strings.Join(args[sigTypes.RequiredParams():], ", "))
}
return append(required, variadic)
}
return args
}
func (fc *funcContext) translateSelection(sel typesutil.Selection, pos token.Pos) ([]string, string) {
var fields []string
t := sel.Recv()
for _, index := range sel.Index() {
if ptr, isPtr := t.Underlying().(*types.Pointer); isPtr {
t = ptr.Elem()
}
s := t.Underlying().(*types.Struct)
if jsTag := getJsTag(s.Tag(index)); jsTag != "" {
jsFieldName := s.Field(index).Name()
for {
fields = append(fields, fieldName(s, 0))
ft := s.Field(0).Type()
if typesutil.IsJsObject(ft) {
return fields, jsTag
}
ft = ft.Underlying()
if ptr, ok := ft.(*types.Pointer); ok {
ft = ptr.Elem().Underlying()
}
var ok bool
s, ok = ft.(*types.Struct)
if !ok || s.NumFields() == 0 {
fc.pkgCtx.errList = append(fc.pkgCtx.errList, types.Error{Fset: fc.pkgCtx.fileSet, Pos: pos, Msg: fmt.Sprintf("could not find field with type *js.Object for 'js' tag of field '%s'", jsFieldName), Soft: true})
return nil, ""
}
}
}
fields = append(fields, fieldName(s, index))
t = s.Field(index).Type()
}
return fields, ""
}
var nilObj = types.Universe.Lookup("nil")
func (fc *funcContext) zeroValue(ty types.Type) ast.Expr {
switch t := ty.Underlying().(type) {
case *types.Basic:
switch {
case isBoolean(t):
return fc.newConst(ty, constant.MakeBool(false))
case isNumeric(t):
return fc.newConst(ty, constant.MakeInt64(0))
case isString(t):
return fc.newConst(ty, constant.MakeString(""))
case t.Kind() == types.UnsafePointer:
// fall through to "nil"
case t.Kind() == types.UntypedNil:
panic("Zero value for untyped nil.")
default:
panic(fmt.Sprintf("Unhandled basic type: %v\n", t))
}
case *types.Array, *types.Struct:
return fc.setType(&ast.CompositeLit{}, ty)
case *types.Chan, *types.Interface, *types.Map, *types.Signature, *types.Slice, *types.Pointer:
// fall through to "nil"
default:
panic(fmt.Sprintf("Unhandled type: %T\n", t))
}
id := fc.newIdent("nil", ty)
fc.pkgCtx.Uses[id] = nilObj
return id
}
func (fc *funcContext) newConst(t types.Type, value constant.Value) ast.Expr {
id := &ast.Ident{}
fc.pkgCtx.Types[id] = types.TypeAndValue{Type: t, Value: value}
return id
}
// newLocalVariable assigns a new JavaScript variable name for the given Go
// local variable name. In this context "local" means "in scope of the current"
// functionContext.
func (fc *funcContext) newLocalVariable(name string) string {
return fc.newVariable(name, false)
}
// newVariable assigns a new JavaScript variable name for the given Go variable
// or type.
//
// If there is already a variable with the same name visible in the current
// function context (e.g. due to shadowing), the returned name will be suffixed
// with a number to prevent conflict. This is necessary because Go name
// resolution scopes differ from var declarations in JS.
//
// If pkgLevel is true, the variable is declared at the package level and added
// to this functionContext, as well as all parents, but not to the list of local
// variables. If false, it is added to this context only, as well as the list of
// local vars.
func (fc *funcContext) newVariable(name string, pkgLevel bool) string {
if name == "" {
panic("newVariable: empty name")
}
name = encodeIdent(name)
if fc.pkgCtx.minify {
i := 0
for {
offset := int('a')
if pkgLevel {
offset = int('A')
}
j := i
name = ""
for {
name = string(rune(offset+(j%26))) + name
j = j/26 - 1
if j == -1 {
break
}
}
if fc.allVars[name] == 0 {
break
}
i++
}
}
n := fc.allVars[name]
fc.allVars[name] = n + 1
varName := name
if n > 0 {
varName = fmt.Sprintf("%s$%d", name, n)
}
if pkgLevel {
for c2 := fc.parent; c2 != nil; c2 = c2.parent {
c2.allVars[name] = n + 1
}
return varName
}
fc.localVars = append(fc.localVars, varName)
return varName
}
// newIdent declares a new Go variable with the given name and type and returns
// an *ast.Ident referring to that object.
func (fc *funcContext) newIdent(name string, t types.Type) *ast.Ident {
obj := types.NewVar(0, fc.pkgCtx.Pkg, name, t)
fc.objectNames[obj] = name
return fc.newIdentFor(obj)
}
// newIdentFor creates a new *ast.Ident referring to the given Go object.
func (fc *funcContext) newIdentFor(obj types.Object) *ast.Ident {
ident := ast.NewIdent(obj.Name())
ident.NamePos = obj.Pos()
fc.pkgCtx.Uses[ident] = obj
fc.setType(ident, obj.Type())
return ident
}
func (fc *funcContext) newTypeIdent(name string, obj types.Object) *ast.Ident {
ident := ast.NewIdent(name)
fc.pkgCtx.Info.Uses[ident] = obj
return ident
}
func (fc *funcContext) setType(e ast.Expr, t types.Type) ast.Expr {
fc.pkgCtx.Types[e] = types.TypeAndValue{Type: t}
return e
}
func (fc *funcContext) pkgVar(pkg *types.Package) string {
if pkg == fc.pkgCtx.Pkg {
return "$pkg"
}
pkgVar, found := fc.pkgCtx.pkgVars[pkg.Path()]
if !found {
pkgVar = fmt.Sprintf(`$packages["%s"]`, pkg.Path())
}
return pkgVar
}
func isVarOrConst(o types.Object) bool {
switch o.(type) {
case *types.Var, *types.Const:
return true
}
return false
}
func isPkgLevel(o types.Object) bool {
// Note: named types are always assigned a variable at package level to be
// initialized with the rest of the package types, even the types declared
// in a statement inside a function.
_, isType := o.(*types.TypeName)
return (o.Parent() != nil && o.Parent().Parent() == types.Universe) || isType
}
// assignedObjectName checks if the object has been previously assigned a name
// in this or one of the parent contexts. If not, found will be false.
func (fc *funcContext) assignedObjectName(o types.Object) (name string, found bool) {
if fc == nil {
return "", false
}
if name, found := fc.parent.assignedObjectName(o); found {
return name, true
}
name, found = fc.objectNames[o]
return name, found
}
// objectName returns a JS expression that refers to the given object. If the
// object hasn't been previously assigned a JS variable name, it will be
// allocated as needed.
func (fc *funcContext) objectName(o types.Object) string {
if isPkgLevel(o) {
fc.DeclareDCEDep(o)
if o.Pkg() != fc.pkgCtx.Pkg || (isVarOrConst(o) && o.Exported()) {
return fc.pkgVar(o.Pkg()) + "." + o.Name()
}
}
name, ok := fc.assignedObjectName(o)
if !ok {
pkgLevel := isPkgLevel(o)
name = fc.newVariable(o.Name(), pkgLevel)
if pkgLevel {
fc.root().objectNames[o] = name
} else {
fc.objectNames[o] = name
}
}
if v, ok := o.(*types.Var); ok && fc.pkgCtx.escapingVars[v] {
return name + "[0]"
}
return name
}
// knownInstances returns a list of known instantiations of the object.
//
// For objects without type params always returns a single trivial instance.
func (fc *funcContext) knownInstances(o types.Object) []typeparams.Instance {
if !typeparams.HasTypeParams(o.Type()) {
return []typeparams.Instance{{Object: o}}
}
return fc.pkgCtx.instanceSet.Pkg(o.Pkg()).ByObj()[o]
}
// instName returns a JS expression that refers to the provided instance of a
// function or type. Non-generic objects may be represented as an instance with
// zero type arguments.
func (fc *funcContext) instName(inst typeparams.Instance) string {
objName := fc.objectName(inst.Object)
if inst.IsTrivial() {
return objName
}
return fmt.Sprintf("%s[%d /* %v */]", objName, fc.pkgCtx.instanceSet.ID(inst), inst.TArgs)
}
func (fc *funcContext) varPtrName(o *types.Var) string {
if isPkgLevel(o) && o.Exported() {
return fc.pkgVar(o.Pkg()) + "." + o.Name() + "$ptr"
}
name, ok := fc.pkgCtx.varPtrNames[o]
if !ok {
name = fc.newVariable(o.Name()+"$ptr", isPkgLevel(o))
fc.pkgCtx.varPtrNames[o] = name
}
return name
}
// typeName returns a JS identifier name for the given Go type.
//
// For the built-in types it returns identifiers declared in the prelude. For
// all user-defined or composite types it creates a unique JS identifier and
// will return it on all subsequent calls for the type.
func (fc *funcContext) typeName(ty types.Type) string {
switch t := ty.(type) {
case *types.Basic:
return "$" + toJavaScriptType(t)
case *types.Named:
if t.Obj().Name() == "error" {
return "$error"
}
inst := typeparams.Instance{Object: t.Obj()}
for i := 0; i < t.TypeArgs().Len(); i++ {
inst.TArgs = append(inst.TArgs, t.TypeArgs().At(i))
}
return fc.instName(inst)
case *types.Interface:
if t.Empty() {
return "$emptyInterface"
}
}
// For anonymous composite types, generate a synthetic package-level type
// declaration, which will be reused for all instances of this time. This
// improves performance, since runtime won't have to synthesize the same type
// repeatedly.
anonType, ok := fc.pkgCtx.anonTypeMap.At(ty).(*types.TypeName)
if !ok {
fc.initArgs(ty) // cause all embedded types to be registered
varName := fc.newVariable(strings.ToLower(typeKind(ty)[5:])+"Type", true)
anonType = types.NewTypeName(token.NoPos, fc.pkgCtx.Pkg, varName, ty) // fake types.TypeName
fc.pkgCtx.anonTypes = append(fc.pkgCtx.anonTypes, anonType)
fc.pkgCtx.anonTypeMap.Set(ty, anonType)
}
fc.DeclareDCEDep(anonType)
return anonType.Name()
}
// importedPkgVar returns a package-level variable name for accessing an imported
// package.
//
// Allocates a new variable if this is the first call, or returns the existing
// one. The variable is based on the package name (implicitly derived from the
// `package` declaration in the imported package, or explicitly assigned by the
// import decl in the importing source file).
//
// Returns the allocated variable name.
func (fc *funcContext) importedPkgVar(pkg *types.Package) string {
if pkgVar, ok := fc.pkgCtx.pkgVars[pkg.Path()]; ok {
return pkgVar // Already registered.
}
pkgVar := fc.newVariable(pkg.Name(), true)
fc.pkgCtx.pkgVars[pkg.Path()] = pkgVar
return pkgVar
}
// instanceOf constructs an instance description of the object the ident is
// referring to. For non-generic objects, it will return a trivial instance with
// no type arguments.
func (fc *funcContext) instanceOf(ident *ast.Ident) typeparams.Instance {
inst := typeparams.Instance{Object: fc.pkgCtx.ObjectOf(ident)}
if i, ok := fc.pkgCtx.Instances[ident]; ok {
inst.TArgs = fc.typeResolver.SubstituteAll(i.TypeArgs)
}
return inst
}
// typeOf returns a type associated with the given AST expression. For types
// defined in terms of type parameters, it will substitute type parameters with
// concrete types from the current set of type arguments.
func (fc *funcContext) typeOf(expr ast.Expr) types.Type {
typ := fc.pkgCtx.TypeOf(expr)
// If the expression is referring to an instance of a generic type or function,
// we want the instantiated type.
if ident, ok := expr.(*ast.Ident); ok {
if inst, ok := fc.pkgCtx.Instances[ident]; ok {
typ = inst.Type
}
}
return fc.typeResolver.Substitute(typ)
}
func (fc *funcContext) selectionOf(e *ast.SelectorExpr) (typesutil.Selection, bool) {
if sel, ok := fc.pkgCtx.Selections[e]; ok {
return fc.typeResolver.SubstituteSelection(sel), true
}
if sel, ok := fc.pkgCtx.additionalSelections[e]; ok {
return sel, true
}
return nil, false
}
func (fc *funcContext) externalize(s string, t types.Type) string {
if typesutil.IsJsObject(t) {
return s
}
switch u := t.Underlying().(type) {
case *types.Basic:
if isNumeric(u) && !is64Bit(u) && !isComplex(u) {
return s
}
if u.Kind() == types.UntypedNil {
return "null"
}
}
return fmt.Sprintf("$externalize(%s, %s)", s, fc.typeName(t))
}
func (fc *funcContext) handleEscapingVars(n ast.Node) {
newEscapingVars := make(map[*types.Var]bool)
for escaping := range fc.pkgCtx.escapingVars {
newEscapingVars[escaping] = true
}
fc.pkgCtx.escapingVars = newEscapingVars
var names []string
objs := analysis.EscapingObjects(n, fc.pkgCtx.Info.Info)
for _, obj := range objs {
names = append(names, fc.objectName(obj))
fc.pkgCtx.escapingVars[obj] = true
}
sort.Strings(names)
for _, name := range names {
fc.Printf("%s = [%s];", name, name)
}
}
func fieldName(t *types.Struct, i int) string {
name := t.Field(i).Name()
if name == "_" || reservedKeywords[name] {
return fmt.Sprintf("%s$%d", name, i)
}
return name
}
func typeKind(ty types.Type) string {
switch t := ty.Underlying().(type) {
case *types.Basic:
return "$kind" + toJavaScriptType(t)
case *types.Array:
return "$kindArray"
case *types.Chan:
return "$kindChan"
case *types.Interface:
return "$kindInterface"
case *types.Map:
return "$kindMap"
case *types.Signature:
return "$kindFunc"
case *types.Slice:
return "$kindSlice"
case *types.Struct:
return "$kindStruct"
case *types.Pointer:
return "$kindPtr"
default:
panic(fmt.Sprintf("Unhandled type: %T\n", t))
}
}
func toJavaScriptType(t *types.Basic) string {
switch t.Kind() {
case types.UntypedInt:
return "Int"
case types.Byte:
return "Uint8"
case types.Rune:
return "Int32"
case types.UnsafePointer:
return "UnsafePointer"
default:
name := t.String()
return strings.ToUpper(name[:1]) + name[1:]
}
}
func is64Bit(t *types.Basic) bool {
return t.Kind() == types.Int64 || t.Kind() == types.Uint64
}
func isBoolean(t *types.Basic) bool {
return t.Info()&types.IsBoolean != 0
}
func isComplex(t *types.Basic) bool {
return t.Info()&types.IsComplex != 0
}
func isFloat(t *types.Basic) bool {
return t.Info()&types.IsFloat != 0
}
func isInteger(t *types.Basic) bool {
return t.Info()&types.IsInteger != 0
}
func isNumeric(t *types.Basic) bool {
return t.Info()&types.IsNumeric != 0
}
func isString(t *types.Basic) bool {
return t.Info()&types.IsString != 0
}
func isUnsigned(t *types.Basic) bool {
return t.Info()&types.IsUnsigned != 0
}
func isBlank(expr ast.Expr) bool {
if expr == nil {
return true
}
if id, isIdent := expr.(*ast.Ident); isIdent {
return id.Name == "_"
}
return false
}
// isWrapped returns true for types that may need to be boxed to access full
// functionality of the Go type.
//
// For efficiency or interoperability reasons certain Go types can be represented
// by JavaScript values that weren't constructed by the corresponding Go type
// constructor.
//
// For example, consider a Go type:
//
// type SecretInt int
// func (_ SecretInt) String() string { return "<secret>" }
//
// func main() {
// var i SecretInt = 1
// println(i.String())
// }
//
// For this example the compiler will generate code similar to the snippet below:
//
// SecretInt = $pkg.SecretInt = $newType(4, $kindInt, "main.SecretInt", true, "main", true, null);
// SecretInt.prototype.String = function() {
// return "<secret>";
// };
// main = function() {
// var i = 1;
// console.log(new SecretInt(i).String());
// };
//
// Note that the generated code assigns a primitive "number" value into i, and
// only boxes it into an object when it's necessary to access its methods.
func isWrapped(ty types.Type) bool {
switch t := ty.Underlying().(type) {
case *types.Basic:
return !is64Bit(t) && !isComplex(t) && t.Kind() != types.UntypedNil
case *types.Array, *types.Chan, *types.Map, *types.Signature:
return true
case *types.Pointer:
_, isArray := t.Elem().Underlying().(*types.Array)
return isArray
}
return false
}
func encodeString(s string) string {
buffer := bytes.NewBuffer(nil)
for _, r := range []byte(s) {
switch r {
case '\b':
buffer.WriteString(`\b`)
case '\f':
buffer.WriteString(`\f`)
case '\n':
buffer.WriteString(`\n`)
case '\r':
buffer.WriteString(`\r`)
case '\t':
buffer.WriteString(`\t`)
case '\v':
buffer.WriteString(`\v`)
case '"':
buffer.WriteString(`\"`)
case '\\':
buffer.WriteString(`\\`)
default:
if r < 0x20 || r > 0x7E {
fmt.Fprintf(buffer, `\x%02X`, r)
continue
}
buffer.WriteByte(r)
}
}
return `"` + buffer.String() + `"`
}
func getJsTag(tag string) string {
for tag != "" {
// skip leading space
i := 0
for i < len(tag) && tag[i] == ' ' {
i++
}
tag = tag[i:]
if tag == "" {
break
}
// scan to colon.
// a space or a quote is a syntax error
i = 0
for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' {
i++
}
if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' {
break
}
name := string(tag[:i])
tag = tag[i+1:]
// scan quoted string to find value
i = 1
for i < len(tag) && tag[i] != '"' {
if tag[i] == '\\' {
i++
}
i++
}
if i >= len(tag) {
break
}
qvalue := string(tag[:i+1])
tag = tag[i+1:]
if name == "js" {
value, _ := strconv.Unquote(qvalue)
return value
}
}
return ""
}
func needsSpace(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$'
}
func removeWhitespace(b []byte, minify bool) []byte {
if !minify {
return b
}
var out []byte
var previous byte
for len(b) > 0 {
switch b[0] {
case '\b':
out = append(out, b[:5]...)
b = b[5:]
continue
case ' ', '\t', '\n':
if (!needsSpace(previous) || !needsSpace(b[1])) && !(previous == '-' && b[1] == '-') {
b = b[1:]
continue
}
case '"':
out = append(out, '"')
b = b[1:]
for {
i := bytes.IndexAny(b, "\"\\")
out = append(out, b[:i]...)
b = b[i:]
if b[0] == '"' {
break
}
// backslash
out = append(out, b[:2]...)
b = b[2:]
}
case '/':
if b[1] == '*' {
i := bytes.Index(b[2:], []byte("*/"))
b = b[i+4:]
continue
}
}
out = append(out, b[0])
previous = b[0]
b = b[1:]
}
return out
}
func rangeCheck(pattern string, constantIndex, array bool) string {
if constantIndex && array {
return pattern
}
lengthProp := "$length"
if array {
lengthProp = "length"
}
check := "%2f >= %1e." + lengthProp
if !constantIndex {
check = "(%2f < 0 || " + check + ")"
}
return "(" + check + ` ? ($throwRuntimeError("index out of range"), undefined) : ` + pattern + ")"
}
func encodeIdent(name string) string {
return strings.Replace(url.QueryEscape(name), "%", "$", -1)
}
// formatJSStructTagVal returns JavaScript code for accessing an object's property
// identified by jsTag. It prefers the dot notation over the bracket notation when
// possible, since the dot notation produces slightly smaller output.
//
// For example:
//
// "my_name" -> ".my_name"
// "my name" -> `["my name"]`
//
// For more information about JavaScript property accessors and identifiers, see
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors and
// https://developer.mozilla.org/en-US/docs/Glossary/Identifier.
func formatJSStructTagVal(jsTag string) string {
for i, r := range jsTag {
ok := unicode.IsLetter(r) || (i != 0 && unicode.IsNumber(r)) || r == '$' || r == '_'
if !ok {
// Saw an invalid JavaScript identifier character,
// so use bracket notation.
return `["` + template.JSEscapeString(jsTag) + `"]`
}
}
// Safe to use dot notation without any escaping.
return "." + jsTag
}
// ErrorAt annotates an error with a position in the source code.
func ErrorAt(err error, fset *token.FileSet, pos token.Pos) error {
return fmt.Errorf("%s: %w", fset.Position(pos), err)
}
// FatalError is an error compiler panics with when it encountered a fatal error.
//
// FatalError implements io.Writer, which can be used to record any free-form
// debugging details for human consumption. This information will be included
// into String() result along with the rest.
type FatalError struct {
cause interface{}
stack []byte
clues strings.Builder
}
func (b FatalError) Unwrap() error {
if b.cause == nil {
return nil
}
if err, ok := b.cause.(error); ok {
return err
}
if s, ok := b.cause.(string); ok {
return errors.New(s)
}
return fmt.Errorf("[%T]: %v", b.cause, b.cause)
}
// Write implements io.Writer and can be used to store free-form debugging clues.
func (b *FatalError) Write(p []byte) (n int, err error) { return b.clues.Write(p) }
func (b FatalError) Error() string {
buf := &strings.Builder{}
fmt.Fprintln(buf, "[compiler panic] ", strings.TrimSpace(b.Unwrap().Error()))
if b.clues.Len() > 0 {
fmt.Fprintln(buf, "\n"+b.clues.String())
}
if len(b.stack) > 0 {
// Shift stack track by 2 spaces for better readability.
stack := regexp.MustCompile("(?m)^").ReplaceAll(b.stack, []byte(" "))
fmt.Fprintln(buf, "\nOriginal stack trace:\n", string(stack))
}
return buf.String()
}
func bailout(cause interface{}) *FatalError {
b := &FatalError{
cause: cause,
stack: debug.Stack(),
}
return b
}
func bailingOut(err interface{}) (*FatalError, bool) {
fe, ok := err.(*FatalError)
return fe, ok
}