generate.go 7.66 KB
Newer Older
Eric Myhre's avatar
Eric Myhre committed
1 2 3
package gengo

import (
4
	"fmt"
Eric Myhre's avatar
Eric Myhre committed
5 6 7
	"io"
	"os"
	"path/filepath"
8
	"sort"
Eric Myhre's avatar
Eric Myhre committed
9 10 11 12

	"github.com/ipld/go-ipld-prime/schema"
)

13 14 15 16
// Generate takes a typesystem and the adjunct config for codegen,
// and emits generated code in the given path with the given package name.
//
// All of the files produced will match the pattern "ipldsch.*.gen.go".
Eric Myhre's avatar
Eric Myhre committed
17
func Generate(pth string, pkgName string, ts schema.TypeSystem, adjCfg *AdjunctCfg) {
18
	// Emit fixed bits.
Eric Myhre's avatar
Eric Myhre committed
19
	withFile(filepath.Join(pth, "ipldsch_minima.go"), func(f io.Writer) {
20 21 22 23 24 25
		EmitInternalEnums(pkgName, f)
	})

	// Local helper function for applying generation logic to each type.
	//  We will end up doing this more than once because in this layout, more than one file contains part of the story for each type.
	applyToEachType := func(fn func(tg TypeGenerator, w io.Writer), f io.Writer) {
26 27 28 29 30 31 32 33 34 35
		// Sort the type names so we have a determinisic order; this affects output consistency.
		//  Any stable order would do, but we don't presently have one, so a sort is necessary.
		types := ts.GetTypes()
		keys := make(sortableTypeNames, 0, len(types))
		for tn := range types {
			keys = append(keys, tn)
		}
		sort.Sort(keys)
		for _, tn := range keys {
			switch t2 := types[tn].(type) {
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
			case *schema.TypeBool:
				fn(NewBoolReprBoolGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeInt:
				fn(NewIntReprIntGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeFloat:
				fn(NewFloatReprFloatGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeString:
				fn(NewStringReprStringGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeBytes:
				fn(NewBytesReprBytesGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeLink:
				fn(NewLinkReprLinkGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeStruct:
				switch t2.RepresentationStrategy().(type) {
				case schema.StructRepresentation_Map:
					fn(NewStructReprMapGenerator(pkgName, t2, adjCfg), f)
				case schema.StructRepresentation_Tuple:
					fn(NewStructReprTupleGenerator(pkgName, t2, adjCfg), f)
				case schema.StructRepresentation_Stringjoin:
					fn(NewStructReprStringjoinGenerator(pkgName, t2, adjCfg), f)
				default:
					panic("unrecognized struct representation strategy")
				}
			case *schema.TypeMap:
				fn(NewMapReprMapGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeList:
				fn(NewListReprListGenerator(pkgName, t2, adjCfg), f)
			case *schema.TypeUnion:
				switch t2.RepresentationStrategy().(type) {
				case schema.UnionRepresentation_Keyed:
					fn(NewUnionReprKeyedGenerator(pkgName, t2, adjCfg), f)
				case schema.UnionRepresentation_Kinded:
					fn(NewUnionReprKindedGenerator(pkgName, t2, adjCfg), f)
				default:
					panic("unrecognized union representation strategy")
				}
			default:
				panic("add more type switches here :)")
			}
		}
	}

	// Emit a file with the type table, and the golang type defns for each type.
Eric Myhre's avatar
Eric Myhre committed
79
	withFile(filepath.Join(pth, "ipldsch_types.go"), func(f io.Writer) {
80 81 82
		// Emit headers, import statements, etc.
		fmt.Fprintf(f, "package %s\n\n", pkgName)
		fmt.Fprintf(f, doNotEditComment+"\n\n")
83 84 85 86
		fmt.Fprintf(f, "import (\n")
		fmt.Fprintf(f, "\tipld \"github.com/ipld/go-ipld-prime\"\n") // referenced for links
		fmt.Fprintf(f, ")\n")
		fmt.Fprintf(f, "var _ ipld.Node = nil // suppress errors when this dependency is not referenced\n")
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

		// Emit the type table.
		EmitTypeTable(pkgName, ts, adjCfg, f)

		// Emit the type defns matching the schema types.
		fmt.Fprintf(f, "\n// --- type definitions follow ---\n\n")
		applyToEachType(func(tg TypeGenerator, w io.Writer) {
			tg.EmitNativeType(w)
			fmt.Fprintf(f, "\n")
		}, f)

	})

	// Emit a file with all the Node/NodeBuilder/NodeAssembler boilerplate.
	//  Also includes typedefs for representation-level data.
	//  Also includes the MaybeT boilerplate.
Eric Myhre's avatar
Eric Myhre committed
103
	withFile(filepath.Join(pth, "ipldsch_satisfaction.go"), func(f io.Writer) {
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
		// Emit headers, import statements, etc.
		fmt.Fprintf(f, "package %s\n\n", pkgName)
		fmt.Fprintf(f, doNotEditComment+"\n\n")
		fmt.Fprintf(f, "import (\n")
		fmt.Fprintf(f, "\tipld \"github.com/ipld/go-ipld-prime\"\n")        // referenced everywhere.
		fmt.Fprintf(f, "\t\"github.com/ipld/go-ipld-prime/node/mixins\"\n") // referenced by node implementation guts.
		fmt.Fprintf(f, "\t\"github.com/ipld/go-ipld-prime/schema\"\n")      // referenced by maybes (and surprisingly little else).
		fmt.Fprintf(f, ")\n\n")

		// For each type, we'll emit... everything except the native type, really.
		applyToEachType(func(tg TypeGenerator, w io.Writer) {
			tg.EmitNativeAccessors(w)
			tg.EmitNativeBuilder(w)
			tg.EmitNativeMaybe(w)
			EmitNode(tg, w)
			tg.EmitTypedNodeMethodType(w)
			tg.EmitTypedNodeMethodRepresentation(w)

			nrg := tg.GetRepresentationNodeGen()
			EmitNode(nrg, w)

			fmt.Fprintf(f, "\n")
		}, f)
	})
}

// GenerateSplayed is like Generate, but emits a differnet pattern of files.
// GenerateSplayed emits many more individual files than Generate.
//
// This function should be considered deprecated and may be removed in the future.
func GenerateSplayed(pth string, pkgName string, ts schema.TypeSystem, adjCfg *AdjunctCfg) {
Eric Myhre's avatar
Eric Myhre committed
135 136 137 138 139 140 141 142 143 144
	// Emit fixed bits.
	withFile(filepath.Join(pth, "minima.go"), func(f io.Writer) {
		EmitInternalEnums(pkgName, f)
	})

	// Emit a file for each type.
	for _, typ := range ts.GetTypes() {
		withFile(filepath.Join(pth, "t"+typ.Name().String()+".go"), func(f io.Writer) {
			EmitFileHeader(pkgName, f)
			switch t2 := typ.(type) {
145
			case *schema.TypeBool:
Eric Myhre's avatar
Eric Myhre committed
146
				EmitEntireType(NewBoolReprBoolGenerator(pkgName, t2, adjCfg), f)
147
			case *schema.TypeInt:
Eric Myhre's avatar
Eric Myhre committed
148
				EmitEntireType(NewIntReprIntGenerator(pkgName, t2, adjCfg), f)
149
			case *schema.TypeFloat:
Eric Myhre's avatar
Eric Myhre committed
150
				EmitEntireType(NewFloatReprFloatGenerator(pkgName, t2, adjCfg), f)
151
			case *schema.TypeString:
Eric Myhre's avatar
Eric Myhre committed
152
				EmitEntireType(NewStringReprStringGenerator(pkgName, t2, adjCfg), f)
153
			case *schema.TypeBytes:
Eric Myhre's avatar
Eric Myhre committed
154
				EmitEntireType(NewBytesReprBytesGenerator(pkgName, t2, adjCfg), f)
155
			case *schema.TypeLink:
Eric Myhre's avatar
Eric Myhre committed
156
				EmitEntireType(NewLinkReprLinkGenerator(pkgName, t2, adjCfg), f)
157
			case *schema.TypeStruct:
Eric Myhre's avatar
Eric Myhre committed
158 159 160
				switch t2.RepresentationStrategy().(type) {
				case schema.StructRepresentation_Map:
					EmitEntireType(NewStructReprMapGenerator(pkgName, t2, adjCfg), f)
161 162
				case schema.StructRepresentation_Tuple:
					EmitEntireType(NewStructReprTupleGenerator(pkgName, t2, adjCfg), f)
163 164
				case schema.StructRepresentation_Stringjoin:
					EmitEntireType(NewStructReprStringjoinGenerator(pkgName, t2, adjCfg), f)
Eric Myhre's avatar
Eric Myhre committed
165 166 167
				default:
					panic("unrecognized struct representation strategy")
				}
168
			case *schema.TypeMap:
169
				EmitEntireType(NewMapReprMapGenerator(pkgName, t2, adjCfg), f)
170
			case *schema.TypeList:
Eric Myhre's avatar
Eric Myhre committed
171
				EmitEntireType(NewListReprListGenerator(pkgName, t2, adjCfg), f)
172
			case *schema.TypeUnion:
173 174 175
				switch t2.RepresentationStrategy().(type) {
				case schema.UnionRepresentation_Keyed:
					EmitEntireType(NewUnionReprKeyedGenerator(pkgName, t2, adjCfg), f)
176 177
				case schema.UnionRepresentation_Kinded:
					EmitEntireType(NewUnionReprKindedGenerator(pkgName, t2, adjCfg), f)
178 179 180
				default:
					panic("unrecognized union representation strategy")
				}
Eric Myhre's avatar
Eric Myhre committed
181 182 183 184 185
			default:
				panic("add more type switches here :)")
			}
		})
	}
186 187 188

	// Emit the unified type table.
	withFile(filepath.Join(pth, "typeTable.go"), func(f io.Writer) {
189 190
		fmt.Fprintf(f, "package %s\n\n", pkgName)
		fmt.Fprintf(f, doNotEditComment+"\n\n")
191 192
		EmitTypeTable(pkgName, ts, adjCfg, f)
	})
Eric Myhre's avatar
Eric Myhre committed
193 194 195 196 197 198 199 200 201 202
}

func withFile(filename string, fn func(io.Writer)) {
	f, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	fn(f)
}
203 204 205 206 207 208

type sortableTypeNames []schema.TypeName

func (a sortableTypeNames) Len() int           { return len(a) }
func (a sortableTypeNames) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a sortableTypeNames) Less(i, j int) bool { return a[i] < a[j] }