Commit 52033884 authored by Eric Myhre's avatar Eric Myhre

New take on schema codegen.

The previous code was lots of switches; I think it's safe enough to
say that wasn't going to scale or compose very easily.
This new take is based on some interfaces and a wee dusting of
polymorphism.

The abstraction *won't* hold: we'll drill through it in many places,
and there will still be type switches up the wazoo by the end -- see
the comments already scattered about regarding maps and enums and such.
But that's okay; a few interfaces will still help; at the very least
it makes things a bit more structurally self-documenting.

Strings (and one particular representation and node implementation of
them) are all that's included here, but so far so good.

The comment above, and the one in the code about "triple cross
product", probably waggle towards what's going to be the trickiest
part of this: codegen needs to take into account a *lot* of choices.
Making the code for this maintainable is going to be nontrivial!
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent 1ec2ec37
package typegen
import (
"fmt"
"io"
)
// you'll find a file in this package per kind
// (schema level kind, not data model level reprkind)...
// sparse cross-product with their representation strategy (more or less)
// (it's more... idunnoyet. hopefully we have implstrats and reprstrats,
// and those combine over an interface so it's not a triple cross product...
// and hopefully that interface is nodebuilder,
// because I dunno why it wouldn't be unless we goof on perf somehow).
// typeGenerator declares a standard names for a bunch of methods for generating
// code for our schema types. There's still numerous places where other casts
// to more specific interfaces will be required (so, technically, it's not a
// very powerful interface; it's not so much that the abstractions leak as that
// the floodgates are outright open), but this at least forces consistency onto
// the parts where we can.
//
// All Emit{foo} methods should emit one trailing and one leading linebreak, or,
// nothing (e.g. string kinds don't need to produce a dummy map iterator, so
// such a method can just emit nothing, and the extra spacing between sections
// shouldn't accumulate).
//
// None of these methods return error values because we panic in this package.
//
type typeGenerator interface {
// wip note: hopefully imports are a constant. if not, we'll have to curry something with the writer.
// -- the typed.Node.Type method and vars -->
// TODO
// (and last -- needs whole `typed/system` package)
// -- all node methods -->
EmitNodeType(io.Writer)
EmitNodeMethodReprKind(io.Writer)
EmitNodeMethodTraverseField(io.Writer)
EmitNodeMethodTraverseIndex(io.Writer)
EmitNodeMethodMapIterator(io.Writer)
EmitNodeMethodListIterator(io.Writer)
EmitNodeMethodLength(io.Writer)
EmitNodeMethodIsNull(io.Writer)
EmitNodeMethodAsBool(io.Writer)
EmitNodeMethodAsInt(io.Writer)
EmitNodeMethodAsFloat(io.Writer)
EmitNodeMethodAsString(io.Writer)
EmitNodeMethodAsBytes(io.Writer)
EmitNodeMethodAsLink(io.Writer)
EmitNodeMethodNodeBuilder(io.Writer)
// TODO also iterators (return blanks for non-{map,list,struct,enum})
// -- all nodebuilder methods -->
// TODO
}
func emitFileHeader(w io.Writer) {
fmt.Fprintf(w, "package whee\n\n")
fmt.Fprintf(w, "import (\n")
fmt.Fprintf(w, "\tipld \"github.com/ipld/go-ipld-prime\"\n")
fmt.Fprintf(w, ")\n")
}
// enums will have special methods
// maps will have special methods (namely, well typed getters
package typegen
import (
"io"
"text/template"
declaration "github.com/ipld/go-ipld-prime/typed/declaration"
wish "github.com/warpfork/go-wish"
)
type generateKindString struct {
Name declaration.TypeName
Type declaration.Type
}
func (gk generateKindString) EmitNodeMethodTraverseField(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) TraverseField(key string) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{ /* todo more content */ }
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodTraverseIndex(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) TraverseIndex(idx int) (ipld.Node, error) {
return nil, ipld.ErrWrongKind{ /* todo more content */ }
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodMapIterator(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) MapIterator() ipld.MapIterator {
return mapIteratorReject{ipld.ErrWrongKind{ /* todo more content */ }}
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodListIterator(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) ListIterator() ipld.ListIterator {
return listIteratorReject{ipld.ErrWrongKind{ /* todo more content */ }}
}
`))).Execute(w, gk) // REVIEW: maybe that rejection thunk should be in main package? don't really want to flash it at folks though. very impl detail.
}
func (gk generateKindString) EmitNodeMethodLength(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) Length() int {
return -1
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodIsNull(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) IsNull() bool {
return false
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodAsBool(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) AsBool() (bool, error) {
return false, ipld.ErrWrongKind{ /* todo more content */ }
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodAsInt(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) AsInt() (int, error) {
return 0, ipld.ErrWrongKind{ /* todo more content */ }
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodAsFloat(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) AsFloat() (float64, error) {
return 0, ipld.ErrWrongKind{ /* todo more content */ }
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodAsString(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func (x {{ .Name }}) AsString() (string, error) {
return x.x, nil
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodAsBytes(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) AsBytes() ([]byte, error) {
return nil, ipld.ErrWrongKind{ /* todo more content */ }
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodAsLink(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) AsLink() (ipld.Link, error) {
return nil, ipld.ErrWrongKind{ /* todo more content */ }
}
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodNodeBuilder(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) NodeBuilder() ipld.NodeBuilder {
return {{ .Name }}__NodeBuilder{}
}
`))).Execute(w, gk)
}
package typegen
import (
"io"
"text/template"
wish "github.com/warpfork/go-wish"
)
func (gk generateKindString) EmitNodeType(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
var _ ipld.Node = {{ .Name }}{}
type {{ .Name }} struct { x string }
`))).Execute(w, gk)
}
func (gk generateKindString) EmitNodeMethodReprKind(w io.Writer) {
template.Must(template.New("").Parse("\n"+wish.Dedent(`
func ({{ .Name }}) ReprKind() ipld.ReprKind {
return ipld.ReprKind_String
}
`))).Execute(w, gk)
}
package typegen
import (
"io"
"os"
"testing"
declaration "github.com/ipld/go-ipld-prime/typed/declaration"
)
func TestNuevo(t *testing.T) {
os.Mkdir("test", 0755)
openOrPanic := func(filename string) *os.File {
y, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
return y
}
f := openOrPanic("test/neu.go")
emitFileHeader(f)
emitType := func(tg typeGenerator, w io.Writer) {
tg.EmitNodeType(w)
tg.EmitNodeMethodReprKind(w)
tg.EmitNodeMethodTraverseField(w)
tg.EmitNodeMethodTraverseIndex(w)
tg.EmitNodeMethodMapIterator(w)
tg.EmitNodeMethodListIterator(w)
tg.EmitNodeMethodLength(w)
tg.EmitNodeMethodIsNull(w)
tg.EmitNodeMethodAsBool(w)
tg.EmitNodeMethodAsInt(w)
tg.EmitNodeMethodAsFloat(w)
tg.EmitNodeMethodAsString(w)
tg.EmitNodeMethodAsBytes(w)
tg.EmitNodeMethodAsLink(w)
tg.EmitNodeMethodNodeBuilder(w)
}
emitType(generateKindString{"Strang", declaration.TypeString{}}, f)
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment