Files
dolt/nomdl/codegen/code/generate.go

492 lines
19 KiB
Go

// 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)
}