// Package code provides Generator, which has methods for generating code snippets from a *types.Type. // Conceptually there are few type spaces here: // // - Def - MyStructDef, ListOfBoolDef; convenient Go types for working with data from a given Noms Value. // - Native - such as string, uint32 // - Value - the generic types.Value // - Nom - types.String, types.Uint32, MyStruct, ListOfBool // - User - User defined structs as well as native primitves. This uses Native when possible or Nom if not. These are to be used in APIs for generated types -- Getters and setters for maps and structs, etc. package code import ( "fmt" "reflect" "strings" "unicode" "github.com/attic-labs/noms/d" "github.com/attic-labs/noms/ref" "github.com/attic-labs/noms/types" ) // Resolver provides a single method for resolving an unresolved types.Type. type Resolver interface { Resolve(t *types.Type, pkg *types.Package) *types.Type } // Generator provides methods for generating code snippets from both resolved and unresolved types.Types. In the latter case, it uses R to resolve the types.Type before generating code. type Generator struct { R Resolver TypesPackage string ImportedJS map[string]bool ImportedJSTypes map[string]bool AliasNames map[ref.Ref]string Package *types.Package } // DefType returns a string containing the Go type that should be used as the 'Def' for the Noms type described by t. func (gen *Generator) DefType(t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) k := rt.Kind() switch k { case types.BlobKind: return fmt.Sprintf("%sBlob", gen.TypesPackage) case types.BoolKind, types.NumberKind, types.StringKind: return strings.ToLower(kindToString(k)) case types.ListKind, types.MapKind, types.SetKind, types.StructKind: return gen.UserName(t) + "Def" case types.PackageKind: return fmt.Sprintf("%sPackage", gen.TypesPackage) case types.RefKind: return "ref.Ref" case types.ValueKind: return fmt.Sprintf("%sValue", gen.TypesPackage) case types.TypeKind: return fmt.Sprintf("%sType", gen.TypesPackage) } panic("unreachable") } // UserType returns a string containing the Go type that should be used when the Noms type described by t needs to be returned by a generated getter or taken as a parameter to a generated setter. func (gen *Generator) UserType(t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) k := rt.Kind() switch k { case types.BlobKind: return fmt.Sprintf("%sBlob", gen.TypesPackage) case types.BoolKind, types.NumberKind, types.StringKind: return strings.ToLower(kindToString(k)) case types.ListKind, types.MapKind, types.RefKind, types.SetKind, types.StructKind: return gen.UserName(t) case types.PackageKind: return fmt.Sprintf("%sPackage", gen.TypesPackage) case types.ValueKind: return fmt.Sprintf("%sValue", gen.TypesPackage) case types.TypeKind: return fmt.Sprintf("%sType", gen.TypesPackage) } panic("unreachable") } // UserTypeJS returns a string containing the JS type that should be used when the Noms type described by t needs to be returned by a generated getter or taken as a parameter to a generated setter. func (gen *Generator) UserTypeJS(t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) k := rt.Kind() switch k { case types.BlobKind: return gen.ImportJSType("Blob") case types.BoolKind: return "boolean" case types.StringKind: return "string" case types.NumberKind: return gen.ImportJSType(strings.ToLower(kindToString(k))) case types.StructKind: if t.HasPackageRef() && gen.Package.Ref() != t.PackageRef() { return gen.importedUserNameJS(t) } return gen.UserName(t) case types.ListKind: return fmt.Sprintf("%s<%s>", gen.ImportJSType("NomsList"), gen.UserTypeJS(t.Desc.(types.CompoundDesc).ElemTypes[0])) case types.SetKind: return fmt.Sprintf("%s<%s>", gen.ImportJSType("NomsSet"), gen.UserTypeJS(t.Desc.(types.CompoundDesc).ElemTypes[0])) case types.RefKind: return fmt.Sprintf("%s<%s>", gen.ImportJSType("RefValue"), gen.UserTypeJS(t.Desc.(types.CompoundDesc).ElemTypes[0])) case types.MapKind: elemTypes := t.Desc.(types.CompoundDesc).ElemTypes return fmt.Sprintf("%s<%s, %s>", gen.ImportJSType("NomsMap"), gen.UserTypeJS(elemTypes[0]), gen.UserTypeJS(elemTypes[1])) case types.PackageKind: return gen.ImportJSType("Package") case types.ValueKind: return gen.ImportJSType("Value") case types.TypeKind: return gen.ImportJSType("Type") } panic("unreachable") } // DefToValue returns a string containing Go code to convert an instance of a Def type (named val) to a Noms types.Value of the type described by t. func (gen *Generator) DefToValue(val string, t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) switch rt.Kind() { case types.BlobKind, types.PackageKind, types.ValueKind, types.TypeKind: return val // No special Def representation case types.BoolKind, types.NumberKind, types.StringKind: return gen.NativeToValue(val, rt) case types.ListKind, types.MapKind, types.SetKind, types.StructKind: return fmt.Sprintf("%s.New()", val) case types.RefKind: return fmt.Sprintf("New%s(%s)", gen.UserName(rt), val) } panic("unreachable") } // DefToUser returns a string containing Go code to convert an instance of a Def type (named val) to a User type described by t. func (gen *Generator) DefToUser(val string, t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) switch rt.Kind() { case types.BlobKind, types.BoolKind, types.NumberKind, types.PackageKind, types.StringKind, types.TypeKind, types.ValueKind: return val case types.ListKind, types.MapKind, types.RefKind, types.SetKind, types.StructKind: return gen.DefToValue(val, rt) } panic("unreachable") } // MayHaveChunks returns whether the type (t) may contain more chunks. func (gen *Generator) MayHaveChunks(t *types.Type) bool { rt := gen.R.Resolve(t, gen.Package) switch rt.Kind() { case types.BlobKind, types.ListKind, types.MapKind, types.PackageKind, types.RefKind, types.SetKind, types.StructKind, types.TypeKind, types.ValueKind: return true case types.BoolKind, types.NumberKind, types.StringKind: return false } panic("unreachable") } // ValueToDef returns a string containing Go code to convert an instance of a types.Value (val) into the Def type appropriate for t. func (gen *Generator) ValueToDef(val string, t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) switch rt.Kind() { case types.BlobKind, types.PackageKind, types.TypeKind: return gen.ValueToUser(val, rt) // No special Def representation case types.BoolKind, types.NumberKind, types.StringKind: return gen.ValueToNative(val, rt) case types.ListKind, types.MapKind, types.SetKind, types.StructKind: return fmt.Sprintf("%s.Def()", gen.ValueToUser(val, t)) case types.RefKind: return fmt.Sprintf("%s.TargetRef()", gen.ValueToUser(val, t)) case types.ValueKind: return val // Value is already a Value } panic("unreachable") } // UserToDef returns a string containing Go code to convert an User value (val) into the Def type appropriate for t. func (gen *Generator) UserToDef(val string, t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) switch rt.Kind() { case types.BlobKind, types.BoolKind, types.NumberKind, types.PackageKind, types.StringKind, types.TypeKind, types.ValueKind: return val case types.ListKind, types.MapKind, types.SetKind, types.StructKind: return fmt.Sprintf("%s.Def()", val) case types.RefKind: return fmt.Sprintf("%s.TargetRef()", val) } panic("unreachable") } // NativeToValue returns a string containing Go code to convert an instance of a native type (named val) to a Noms types.Value of the type described by t. func (gen *Generator) NativeToValue(val string, t *types.Type) string { t = gen.R.Resolve(t, gen.Package) k := t.Kind() switch k { case types.BoolKind, types.NumberKind: return fmt.Sprintf("%s%s(%s)", gen.TypesPackage, kindToString(k), val) case types.StringKind: return fmt.Sprintf("%sNewString(%s)", gen.TypesPackage, val) } panic("unreachable") } // ValueToNative returns a string containing Go code to convert an instance of a types.Value (val) into the native type appropriate for t. func (gen *Generator) ValueToNative(val string, t *types.Type) string { k := t.Kind() switch k { case types.BoolKind, types.NumberKind: n := kindToString(k) return fmt.Sprintf("%s(%s.(%s%s))", strings.ToLower(n), val, gen.TypesPackage, n) case types.StringKind: return fmt.Sprintf("%s.(%sString).String()", val, gen.TypesPackage) } panic("unreachable") } // UserToValue returns a string containing Go code to convert an instance of a User type (named val) to a Noms types.Value of the type described by t. For Go primitive types, this will use NativeToValue(). For other types, their UserType is a Noms types.Value (or a wrapper around one), so this is more-or-less a pass-through. func (gen *Generator) UserToValue(val string, t *types.Type) string { t = gen.R.Resolve(t, gen.Package) k := t.Kind() switch k { case types.BlobKind, types.ListKind, types.MapKind, types.PackageKind, types.RefKind, types.SetKind, types.StructKind, types.TypeKind, types.ValueKind: return val case types.BoolKind, types.NumberKind, types.StringKind: return gen.NativeToValue(val, t) } panic("unreachable") } // ValueToUser returns a string containing Go code to convert an instance of a types.Value (val) into the User type appropriate for t. For Go primitives, this will use ValueToNative(). func (gen *Generator) ValueToUser(val string, t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) k := rt.Kind() switch k { case types.BlobKind: return fmt.Sprintf("%s.(%sBlob)", val, gen.TypesPackage) case types.BoolKind, types.NumberKind, types.StringKind: return gen.ValueToNative(val, rt) case types.ListKind, types.MapKind, types.RefKind, types.SetKind, types.StructKind: return fmt.Sprintf("%s.(%s)", val, gen.UserName(t)) case types.PackageKind: return fmt.Sprintf("%s.(%sPackage)", val, gen.TypesPackage) case types.ValueKind: return val case types.TypeKind: return fmt.Sprintf("%s.(%sType)", val, gen.TypesPackage) } panic("unreachable") } // UserZero returns a string containing Go code to create an uninitialized instance of the User type appropriate for t. func (gen *Generator) UserZero(t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) k := rt.Kind() switch k { case types.BlobKind: return fmt.Sprintf("%sNewEmptyBlob()", gen.TypesPackage) case types.BoolKind: return "false" case types.NumberKind: return fmt.Sprintf("%s(0)", strings.ToLower(kindToString(k))) case types.ListKind, types.MapKind, types.SetKind, types.StructKind: return fmt.Sprintf("New%s()", gen.UserName(rt)) case types.PackageKind: return fmt.Sprintf("New%s()", gen.UserName(rt)) case types.RefKind: return fmt.Sprintf("New%s(ref.Ref{})", gen.UserName(rt)) case types.StringKind: return `""` case types.ValueKind: // TODO: This is where a null Value would have been useful. return fmt.Sprintf("%sBool(false)", gen.TypesPackage) case types.TypeKind: return fmt.Sprintf("%sType{R: ref.Ref{}}", gen.TypesPackage) } panic("unreachable") } // ValueZero returns a string containing Go code to create an uninitialized instance of the Noms types.Value appropriate for t. func (gen *Generator) ValueZero(t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) k := rt.Kind() switch k { case types.BlobKind: return fmt.Sprintf("%sNewEmptyBlob()", gen.TypesPackage) case types.BoolKind: return fmt.Sprintf("%sBool(false)", gen.TypesPackage) case types.NumberKind: return fmt.Sprintf("%s%s(0)", gen.TypesPackage, kindToString(k)) case types.ListKind, types.MapKind, types.RefKind, types.SetKind: return gen.UserZero(t) case types.PackageKind: return fmt.Sprintf("%sNewPackage()", gen.TypesPackage) case types.StringKind: return fmt.Sprintf(`%sNewString("")`, gen.TypesPackage) case types.StructKind: return fmt.Sprintf("New%s()", gen.UserName(rt)) case types.ValueKind: // TODO: Use nil here return fmt.Sprintf("%sBool(false)", gen.TypesPackage) case types.TypeKind: return fmt.Sprintf("%sType{R: ref.Ref{}}", gen.TypesPackage) } panic("unreachable") } // UserName returns the name of the User type appropriate for t, taking into account Noms types imported from other packages. func (gen *Generator) UserName(t *types.Type) string { rt := gen.R.Resolve(t, gen.Package) k := rt.Kind() switch k { case types.BlobKind, types.BoolKind, types.NumberKind, types.PackageKind, types.StringKind, types.ValueKind, types.TypeKind: return kindToString(k) case types.ListKind: return fmt.Sprintf("ListOf%s", gen.refToID(rt.Desc.(types.CompoundDesc).ElemTypes[0])) case types.MapKind: elemTypes := rt.Desc.(types.CompoundDesc).ElemTypes return fmt.Sprintf("MapOf%sTo%s", gen.refToID(elemTypes[0]), gen.refToID(elemTypes[1])) case types.RefKind: return fmt.Sprintf("RefOf%s", gen.refToID(rt.Desc.(types.CompoundDesc).ElemTypes[0])) case types.SetKind: return fmt.Sprintf("SetOf%s", gen.refToID(rt.Desc.(types.CompoundDesc).ElemTypes[0])) case types.StructKind: // We get an empty name when we have a struct that is used as union if rt.Name() == "" { choices := rt.Desc.(types.StructDesc).Union s := "__unionOf" for i, f := range choices { if i > 0 { s += "And" } s += strings.Title(f.Name) + "Of" + gen.refToID(f.T) } return s } return rt.Name() } panic("unreachable") } func (gen Generator) importedUserNameJS(t *types.Type) string { d.Chk.True(t.HasPackageRef()) return fmt.Sprintf("%s.%s", gen.RefToAliasName(t.PackageRef()), gen.UserName(t)) } func (gen *Generator) refToID(t *types.Type) string { if !t.IsUnresolved() || !t.HasPackageRef() { return gen.UserName(t) } return gen.UserName(gen.R.Resolve(t, gen.Package)) } // RefToJSIdentfierName generates an identifier name representing a Ref. ie. `sha1_abc1234`. func (gen *Generator) RefToJSIdentfierName(r ref.Ref) string { return strings.Replace(r.String(), "-", "_", 1)[0:12] } // RefToAliasName is used to map the ref of an import to the alias name used in the noms file func (gen *Generator) RefToAliasName(r ref.Ref) string { // When we generate code from a Package stored in a DataStore we do not have the alias names. if n, ok := gen.AliasNames[r]; ok { return n } return fmt.Sprintf("_%s", gen.RefToJSIdentfierName(r)) } // ToTypesType returns a string containing Go code that instantiates a *types.Type instance equivalent to t. func (gen *Generator) ToTypesType(t *types.Type, inPackageDef bool) string { if t.IsUnresolved() { d.Chk.True(t.HasPackageRef()) d.Chk.True(t.HasOrdinal(), "%s does not have an ordinal set", t.Name()) if t.PackageRef() == gen.Package.Ref() && inPackageDef { return fmt.Sprintf(`%sMakeType(ref.Ref{}, %d)`, gen.TypesPackage, t.Ordinal()) } return fmt.Sprintf(`%sMakeType(ref.Parse("%s"), %d)`, gen.TypesPackage, t.PackageRef().String(), t.Ordinal()) } if types.IsPrimitiveKind(t.Kind()) { return fmt.Sprintf("%sMakePrimitiveType(%s%sKind)", gen.TypesPackage, gen.TypesPackage, kindToString(t.Kind())) } switch desc := t.Desc.(type) { case types.CompoundDesc: types := make([]string, len(desc.ElemTypes)) for i, t := range desc.ElemTypes { types[i] = gen.ToTypesType(t, inPackageDef) } return fmt.Sprintf(`%sMakeCompoundType(%s%sKind, %s)`, gen.TypesPackage, gen.TypesPackage, kindToString(t.Kind()), strings.Join(types, ", ")) case types.StructDesc: flatten := func(f []types.Field) string { out := make([]string, 0, len(f)) for _, field := range f { out = append(out, fmt.Sprintf(`%sField{"%s", %s, %t},`, gen.TypesPackage, field.Name, gen.ToTypesType(field.T, inPackageDef), field.Optional)) } return strings.Join(out, "\n") } fields := fmt.Sprintf("[]%sField{\n%s\n}", gen.TypesPackage, flatten(desc.Fields)) choices := fmt.Sprintf("%sChoices{\n%s\n}", gen.TypesPackage, flatten(desc.Union)) return fmt.Sprintf("%sMakeStructType(\"%s\",\n%s,\n%s,\n)", gen.TypesPackage, t.Name(), fields, choices) default: d.Chk.Fail("Unknown TypeDesc.", "%#v (%T)", desc, desc) } panic("Unreachable") } func ind(i int) string { return strings.Repeat(" ", i) } func firstToLower(s string) string { b := []rune(s) b[0] = unicode.ToLower(b[0]) return string(b) } // ToTypeValueJS returns a string containing JS code that instantiates a Type instance equivalent to t for JavaScript. func (gen *Generator) ToTypeValueJS(t *types.Type, inPackageDef bool, indent int) string { if t.IsUnresolved() { d.Chk.True(t.HasPackageRef()) d.Chk.True(t.HasOrdinal(), "%s does not have an ordinal set", t.Name()) if t.PackageRef() == gen.Package.Ref() { if inPackageDef { return fmt.Sprintf(`%s(%s, %d)`, gen.ImportJS("makeType"), gen.ImportJS("emptyRef"), t.Ordinal()) } else { return fmt.Sprintf(`%s(_pkg.ref, %d)`, gen.ImportJS("makeType"), t.Ordinal()) } } return fmt.Sprintf(`%s(%s.parse('%s'), %d)`, gen.ImportJS("makeType"), gen.ImportJS("Ref"), t.PackageRef().String(), t.Ordinal()) } if types.IsPrimitiveKind(t.Kind()) { return gen.ImportJS(firstToLower(kindToString(t.Kind())) + "Type") } switch desc := t.Desc.(type) { case types.CompoundDesc: types := make([]string, len(desc.ElemTypes)) for i, t := range desc.ElemTypes { types[i] = gen.ToTypeValueJS(t, inPackageDef, 0) } return fmt.Sprintf(`%s(%s.%s, %s)`, gen.ImportJS("makeCompoundType"), gen.ImportJS("Kind"), kindToString(t.Kind()), strings.Join(types, ", ")) case types.StructDesc: flatten := func(f []types.Field) string { out := make([]string, 0, len(f)) for _, field := range f { out = append(out, fmt.Sprintf(`%snew %s('%s', %s, %t),`, ind(indent+1), gen.ImportJS("Field"), field.Name, gen.ToTypeValueJS(field.T, inPackageDef, 0), field.Optional)) } return strings.Join(out, "\n") } fields := fmt.Sprintf("%s[\n%s\n%s]", ind(indent), flatten(desc.Fields), ind(indent)) choices := fmt.Sprintf("%s[\n%s\n%s]", ind(indent), flatten(desc.Union), ind(indent)) return fmt.Sprintf("%s('%s',\n%s,\n%s\n%s)", gen.ImportJS("makeStructType"), t.Name(), fields, choices, ind(indent-1)) default: d.Chk.Fail("Unknown TypeDesc.", "%#v (%T)", desc, desc) } panic("Unreachable") } // IsLast determines if |index| is the last index in |seq|. func (gen *Generator) IsLast(index int, seq interface{}) bool { return reflect.ValueOf(seq).Len() == index+1 } // ToTag replaces "-" characters in s with "_", so it can be used in a Go identifier. // TODO: replace other illegal chars as well? func ToTag(r ref.Ref) string { return strings.Replace(r.String()[0:12], "-", "_", -1) } func kindToString(k types.NomsKind) (out string) { out = types.KindToString[k] d.Chk.NotEmpty(out, "Unknown NomsKind %d", k) return } // ImportJS returns the name of the imported binding as well as registers the binding as imported so that we can later generate the right import declaration. func (gen *Generator) ImportJS(name string) string { if gen.ImportedJS == nil { gen.ImportedJS = map[string]bool{} } gen.ImportedJS[name] = true return fmt.Sprintf("_%s", name) } // ImportJSType returns the name of the imported type as well as registers the type as imported so that we can later generate the right import type declaration. func (gen *Generator) ImportJSType(name string) string { if gen.ImportedJSTypes == nil { gen.ImportedJSTypes = map[string]bool{} } gen.ImportedJSTypes[name] = true return fmt.Sprintf("_%s", name) }