mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-26 18:59:08 -06:00
367 lines
16 KiB
Go
367 lines
16 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, enums etc 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"
|
|
"strings"
|
|
|
|
"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) 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
|
|
}
|
|
|
|
// 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)
|
|
k := rt.Kind()
|
|
switch k {
|
|
case types.BlobKind:
|
|
return fmt.Sprintf("%sBlob", gen.TypesPackage)
|
|
case types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
return strings.ToLower(kindToString(k))
|
|
case types.EnumKind:
|
|
return gen.UserName(t)
|
|
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)
|
|
k := rt.Kind()
|
|
switch k {
|
|
case types.BlobKind:
|
|
return fmt.Sprintf("%sBlob", gen.TypesPackage)
|
|
case types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
return strings.ToLower(kindToString(k))
|
|
case types.EnumKind, 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")
|
|
}
|
|
|
|
// 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)
|
|
switch rt.Kind() {
|
|
case types.BlobKind, types.EnumKind, types.PackageKind, types.ValueKind, types.TypeKind:
|
|
return val // No special Def representation
|
|
case types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
return gen.NativeToValue(val, rt)
|
|
case types.ListKind, types.MapKind, types.SetKind, types.StructKind:
|
|
return fmt.Sprintf("%s.New(cs)", 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)
|
|
switch rt.Kind() {
|
|
case types.BlobKind, types.BoolKind, types.EnumKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.PackageKind, types.StringKind, types.TypeKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind, 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)
|
|
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.EnumKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
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)
|
|
switch rt.Kind() {
|
|
case types.BlobKind, types.PackageKind, types.TypeKind:
|
|
return gen.ValueToUser(val, rt) // No special Def representation
|
|
case types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
return gen.ValueToNative(val, rt)
|
|
case types.EnumKind:
|
|
return fmt.Sprintf("%s.(%s)", val, gen.UserName(t))
|
|
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)
|
|
switch rt.Kind() {
|
|
case types.BlobKind, types.EnumKind, types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.PackageKind, types.StringKind, types.TypeKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind, 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)
|
|
k := t.Kind()
|
|
switch k {
|
|
case types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
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.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
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)
|
|
k := t.Kind()
|
|
switch k {
|
|
case types.BlobKind, types.EnumKind, types.ListKind, types.MapKind, types.PackageKind, types.RefKind, types.SetKind, types.StructKind, types.TypeKind, types.ValueKind:
|
|
return val
|
|
case types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
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)
|
|
k := rt.Kind()
|
|
switch k {
|
|
case types.BlobKind:
|
|
return fmt.Sprintf("%s.(%sBlob)", val, gen.TypesPackage)
|
|
case types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
return gen.ValueToNative(val, rt)
|
|
case types.EnumKind, 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(val string, t types.Type) string {
|
|
rt := gen.R.Resolve(t)
|
|
k := rt.Kind()
|
|
switch k {
|
|
case types.BlobKind:
|
|
return fmt.Sprintf("%sNewEmptyBlob()", gen.TypesPackage)
|
|
case types.BoolKind:
|
|
return "false"
|
|
case types.EnumKind:
|
|
return fmt.Sprintf("New%s()", gen.UserName(rt))
|
|
case types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
return fmt.Sprintf("%s(0)", strings.ToLower(kindToString(k)))
|
|
case types.ListKind, types.MapKind, types.SetKind, types.StructKind:
|
|
return fmt.Sprintf("New%s(%s)", gen.UserName(rt), val)
|
|
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)
|
|
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.EnumKind:
|
|
return gen.UserZero("", t)
|
|
case types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind:
|
|
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)
|
|
k := rt.Kind()
|
|
switch k {
|
|
case types.BlobKind, types.BoolKind, types.Float32Kind, types.Float64Kind, types.Int16Kind, types.Int32Kind, types.Int64Kind, types.Int8Kind, types.PackageKind, types.StringKind, types.Uint16Kind, types.Uint32Kind, types.Uint64Kind, types.Uint8Kind, types.ValueKind, types.TypeKind:
|
|
return kindToString(k)
|
|
case types.EnumKind:
|
|
return rt.Name()
|
|
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) refToId(t types.Type) string {
|
|
if !t.IsUnresolved() || !t.HasPackageRef() {
|
|
return gen.UserName(t)
|
|
}
|
|
return gen.UserName(gen.R.Resolve(t))
|
|
}
|
|
|
|
// ToType returns a string containing Go code that instantiates a types.Type instance equivalent to t.
|
|
func (gen Generator) ToType(t types.Type, fileID, packageName string) string {
|
|
if t.HasPackageRef() {
|
|
return fmt.Sprintf(`%sMakeType(ref.Parse("%s"), %d)`, gen.TypesPackage, t.PackageRef().String(), t.Ordinal())
|
|
}
|
|
if t.IsUnresolved() && fileID != "" {
|
|
return fmt.Sprintf(`%sMakeType(__%sPackageInFile_%s_CachedRef, %d)`, gen.TypesPackage, packageName, fileID, t.Ordinal())
|
|
}
|
|
if t.IsUnresolved() {
|
|
d.Chk.True(t.HasOrdinal(), "%s does not have an ordinal set", t.Name())
|
|
return fmt.Sprintf(`%sMakeType(ref.Ref{}, %d)`, gen.TypesPackage, 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.ToType(t, fileID, packageName)
|
|
}
|
|
return fmt.Sprintf(`%sMakeCompoundType(%s%sKind, %s)`, gen.TypesPackage, gen.TypesPackage, kindToString(t.Kind()), strings.Join(types, ", "))
|
|
case types.EnumDesc:
|
|
return fmt.Sprintf(`%sMakeEnumType("%s", "%s")`, gen.TypesPackage, t.Name(), strings.Join(desc.IDs, `", "`))
|
|
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.ToType(field.T, fileID, packageName), 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")
|
|
}
|
|
|
|
// 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
|
|
}
|