Commit 11223290 authored by Eric Myhre's avatar Eric Myhre

research checkpoint: a "rot13" ADL, for demo purposes.

parent 04afddfb
package rot13adl_test
func ExampleWoo() {
// Output:
}
// An Unmarshal2 function could take a (fairly complex) argument that says what NodePrototype to use in certain positions
// (is this accomplished with a Selector? that's a scary large dependency, but maybe.).
// This can be used for many purposes (efficiency, misc tuning, expecting a *schema* thing part way through...),
// and it could also be used to say where a NodePrototype for an ADL's substrate should be used.
//
// Schemas, even the repr builders, still ultimately result in "returning" (not really, but, roll with me here) the high-level typed info.
// ...
// This ADL, as currently drafted, does not.
// That's... maybe bad?
// It can be passed through the Reify method and there's a feeling of consistency, there.
// But I'm not sure.
package rot13adl
import (
"strings"
)
var replaceTable = []string{
"A", "N",
"B", "O",
"C", "P",
"D", "Q",
"E", "R",
"F", "S",
"G", "T",
"H", "U",
"I", "V",
"J", "W",
"K", "X",
"L", "Y",
"M", "Z",
"N", "A",
"O", "B",
"P", "C",
"Q", "D",
"R", "E",
"S", "F",
"T", "G",
"U", "H",
"V", "I",
"W", "J",
"X", "K",
"Y", "L",
"Z", "M",
"a", "n",
"b", "o",
"c", "p",
"d", "q",
"e", "r",
"f", "s",
"g", "t",
"h", "u",
"i", "v",
"j", "w",
"k", "x",
"l", "y",
"m", "z",
"n", "a",
"o", "b",
"p", "c",
"q", "d",
"r", "e",
"s", "f",
"t", "g",
"u", "h",
"v", "i",
"w", "j",
"x", "k",
"y", "l",
"z", "m",
}
var unreplaceTable = func() []string {
v := make([]string, len(replaceTable))
for i := 0; i < len(replaceTable); i += 2 {
v[i] = replaceTable[i+1]
v[i+1] = replaceTable[i]
}
return v
}()
// rotate transforms from the logical content to the raw content.
func rotate(s string) string {
return strings.NewReplacer(replaceTable...).Replace(s)
}
// unrotate transforms from the raw content to the logical content.
func unrotate(s string) string {
return strings.NewReplacer(unreplaceTable...).Replace(s)
}
/*
rot13adl is a demo ADL -- its purpose is to show what an ADL and its public interface can look like.
It implements a "rot13" string: when creating data through the ADL, the user gives it a regular string;
the ADL will create aninternal representation of it which has the characters altered in a reversable way.
It provides reference and example materal, but it's very unlikely you want to use it in real situations ;)
There are several ways to move data in and out of the ADL:
- using the exported NodePrototype can be used to get a NodeBuilder which can accept the synthesized data;
- Nodes resulting from that construction process can be read to see synthesized data;
- the raw substrate data, if already parsed into Nodes, can be processed into a synthesized Node using the Reify function;
- TODO nodes
*/
package rot13adl
import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/mixins"
"github.com/ipld/go-ipld-prime/schema"
)
// -- Node -->
var _ ipld.Node = (*_R13String)(nil)
type _R13String struct {
raw string // the raw content, before our ADL lens is applied to it.
synthesized string // the content that the ADL presents. calculated proactively from the original, in this implementation (though you could imagine implementing it lazily, in either direction, too).
}
func (*_R13String) ReprKind() ipld.ReprKind {
return ipld.ReprKind_String
}
func (*_R13String) LookupByString(string) (ipld.Node, error) {
return mixins.String{"rot13adl.R13String"}.LookupByString("")
}
func (*_R13String) LookupByNode(ipld.Node) (ipld.Node, error) {
return mixins.String{"rot13adl.R13String"}.LookupByNode(nil)
}
func (*_R13String) LookupByIndex(idx int) (ipld.Node, error) {
return mixins.String{"rot13adl.R13String"}.LookupByIndex(0)
}
func (*_R13String) LookupBySegment(seg ipld.PathSegment) (ipld.Node, error) {
return mixins.String{"rot13adl.R13String"}.LookupBySegment(seg)
}
func (*_R13String) MapIterator() ipld.MapIterator {
return nil
}
func (*_R13String) ListIterator() ipld.ListIterator {
return nil
}
func (*_R13String) Length() int {
return -1
}
func (*_R13String) IsAbsent() bool {
return false
}
func (*_R13String) IsNull() bool {
return false
}
func (*_R13String) AsBool() (bool, error) {
return mixins.String{"rot13adl.R13String"}.AsBool()
}
func (*_R13String) AsInt() (int, error) {
return mixins.String{"rot13adl.R13String"}.AsInt()
}
func (*_R13String) AsFloat() (float64, error) {
return mixins.String{"rot13adl.R13String"}.AsFloat()
}
func (n *_R13String) AsString() (string, error) {
return n.synthesized, nil
}
func (*_R13String) AsBytes() ([]byte, error) {
return mixins.String{"rot13adl.R13String"}.AsBytes()
}
func (*_R13String) AsLink() (ipld.Link, error) {
return mixins.String{"rot13adl.R13String"}.AsLink()
}
func (*_R13String) Prototype() ipld.NodePrototype {
return _R13String__Prototype{}
}
// -- NodePrototype -->
var _ ipld.NodePrototype = Prototype{}
type Prototype = _R13String__Prototype
type _R13String__Prototype struct {
// There's no configuration to this ADL.
// A more complex ADL might have some kind of parameters here.
//
// The general contract of a NodePrototype is supposed to be that:
// when you get one from an existing Node,
// it should have enough information to create a new Node that
// could "replace" the previous one in whatever context it's in.
// For ADLs, that means it should carry most of the configuration.
//
// An ADL that does multi-block stuff might also need functions like a LinkLoader passed in through here.
}
func (np _R13String__Prototype) NewBuilder() ipld.NodeBuilder {
return &_R13String__Builder{}
}
// -- NodeBuilder -->
var _ ipld.NodeBuilder = (*_R13String__Builder)(nil)
type _R13String__Builder struct {
_R13String__Assembler
}
func (nb *_R13String__Builder) Build() ipld.Node {
if nb.m != schema.Maybe_Value {
panic("invalid state: cannot call Build on an assembler that's not finished")
}
return nb.w
}
func (nb *_R13String__Builder) Reset() {
*nb = _R13String__Builder{}
}
// -- NodeAssembler -->
var _ ipld.NodeAssembler = (*_R13String__Assembler)(nil)
type _R13String__Assembler struct {
w *_R13String
m schema.Maybe // REVIEW: if the package where this Maybe enum lives is maybe not the right home for it after all. Or should this line use something different? We're only using some of its values after all.
}
func (_R13String__Assembler) BeginMap(sizeHint int) (ipld.MapAssembler, error) {
return mixins.StringAssembler{"rot13adl.R13String"}.BeginMap(0)
}
func (_R13String__Assembler) BeginList(sizeHint int) (ipld.ListAssembler, error) {
return mixins.StringAssembler{"rot13adl.R13String"}.BeginList(0)
}
func (na *_R13String__Assembler) AssignNull() error {
// REVIEW: unclear how this might compose with some other context (like a schema) which does allow nulls. Probably a wrapper type?
return mixins.StringAssembler{"rot13adl.R13String"}.AssignNull()
}
func (_R13String__Assembler) AssignBool(bool) error {
return mixins.StringAssembler{"rot13adl.R13String"}.AssignBool(false)
}
func (_R13String__Assembler) AssignInt(int) error {
return mixins.StringAssembler{"rot13adl.R13String"}.AssignInt(0)
}
func (_R13String__Assembler) AssignFloat(float64) error {
return mixins.StringAssembler{"rot13adl.R13String"}.AssignFloat(0)
}
func (na *_R13String__Assembler) AssignString(v string) error {
switch na.m {
case schema.Maybe_Value:
panic("invalid state: cannot assign into assembler that's already finished")
}
na.w = &_R13String{
raw: rotate(v),
synthesized: v,
}
na.m = schema.Maybe_Value
return nil
}
func (_R13String__Assembler) AssignBytes([]byte) error {
return mixins.StringAssembler{"rot13adl.R13String"}.AssignBytes(nil)
}
func (_R13String__Assembler) AssignLink(ipld.Link) error {
return mixins.StringAssembler{"rot13adl.R13String"}.AssignLink(nil)
}
func (na *_R13String__Assembler) AssignNode(v ipld.Node) error {
if v.IsNull() {
return na.AssignNull()
}
if v2, ok := v.(*_R13String); ok {
switch na.m {
case schema.Maybe_Value:
panic("invalid state: cannot assign into assembler that's already finished")
}
na.w = v2
na.m = schema.Maybe_Value
return nil
}
if v2, err := v.AsString(); err != nil {
return err
} else {
return na.AssignString(v2)
}
}
func (_R13String__Assembler) Prototype() ipld.NodePrototype {
return _R13String__Prototype{}
}
package rot13adl
import (
"testing"
. "github.com/warpfork/go-wish"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/basic"
)
func TestLogicalNodeRoundtrip(t *testing.T) {
nb := Prototype{}.NewBuilder()
err := nb.AssignString("abcd")
Require(t, err, ShouldEqual, nil)
n := nb.Build()
s, err := n.AsString()
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, "abcd")
}
func TestNodeInternal(t *testing.T) {
nb := Prototype{}.NewBuilder()
err := nb.AssignString("abcd")
Require(t, err, ShouldEqual, nil)
n := nb.Build()
Wish(t, n.(*_R13String).raw, ShouldEqual, "nopq")
}
func TestReify(t *testing.T) {
sn := basicnode.NewString("nopq")
synth, err := Reify(sn)
Require(t, err, ShouldEqual, nil)
Wish(t, synth.ReprKind(), ShouldEqual, ipld.ReprKind_String)
s, err := synth.AsString()
Wish(t, err, ShouldEqual, nil)
Wish(t, s, ShouldEqual, "abcd")
}
package rot13adl
import (
"fmt"
"github.com/ipld/go-ipld-prime"
)
// Reify attempts to process raw Data Model data as substrate data to synthesize an ADL.
// If it succeeds in recognizing the raw data as this ADL,
// Reify returns a new Node which exhibits the logical behaviors of the ADL;
// otherwise, it returns an error.
//
// The input data can be any implementation of ipld.Node;
// it will be considered purely through that interface.
//
// If your application is expecting ADL data, this pipeline can be optimized
// by using the SubstratePrototype right from the start when unmarshalling;
// then, Reify can detect if the rawRoot parameter is of that implementation,
// and it can save some processing work internally that can be known to already be done.
//
func Reify(rawRoot ipld.Node) (ipld.Node, error) {
// Is it evidentally a valid substrate for this ADL?
// This is a pretty trivial check for rot13adl.
// (Other ADLs probably want to include some additional data and structure in them which allow more validation here,
// though in general, this validation should also usually stick to not crossing any block loading boundaries.)
if rawRoot.ReprKind() != ipld.ReprKind_String {
return nil, fmt.Errorf("cannot reify rot13adl: substrate root node is wrong kind (must be string)")
}
// Construct and return the reified node.
// If we can recognize the rawRoot as being our own substrate types,
// we can shortcut some things;
// Otherwise, just process it via the data model.
if x, ok := rawRoot.(*_Substrate); ok {
return (*_R13String)(x), nil
}
// Shortcut didn't work. Process via the data model.
s, _ := rawRoot.AsString()
return &_R13String{
raw: s,
synthesized: unrotate(s),
}, nil
}
package rot13adl
import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/mixins"
"github.com/ipld/go-ipld-prime/schema"
)
// Substrate returns the root node of the raw internal data form of the ADL's content.
func (n *_R13String) Substrate() ipld.Node {
// This is a very minor twist in the case of the rot13 ADL.
// However, for larger ADLs (especially those relating to multi-block collections),
// this could be quite a bit more involved, and would almost certainly be the root node of a larger tree.
return (*_Substrate)(n)
}
// -- Node -->
var _ ipld.Node = (*_Substrate)(nil)
// Somewhat unusually for an ADL, there's only one substrate node type,
// and we actually made it have the same in-memory structure as the synthesized view node.
type _Substrate _R13String
// REVIEW: what on earth we think the "TypeName" strings in error messages and other references to this node should be.
// At the moment, it shares a prefix with the synthesized node, which is potentially confusing (?),
// and I'm not sure what, if any, suffix actually makes meaningful sense to a user either.
// I added the segment ".internal." to the middle of the name mangle; does this seem helpful?
func (*_Substrate) ReprKind() ipld.ReprKind {
return ipld.ReprKind_String
}
func (*_Substrate) LookupByString(string) (ipld.Node, error) {
return mixins.String{"rot13adl.internal.Substrate"}.LookupByString("")
}
func (*_Substrate) LookupByNode(ipld.Node) (ipld.Node, error) {
return mixins.String{"rot13adl.internal.Substrate"}.LookupByNode(nil)
}
func (*_Substrate) LookupByIndex(idx int) (ipld.Node, error) {
return mixins.String{"rot13adl.internal.Substrate"}.LookupByIndex(0)
}
func (*_Substrate) LookupBySegment(seg ipld.PathSegment) (ipld.Node, error) {
return mixins.String{"rot13adl.internal.Substrate"}.LookupBySegment(seg)
}
func (*_Substrate) MapIterator() ipld.MapIterator {
return nil
}
func (*_Substrate) ListIterator() ipld.ListIterator {
return nil
}
func (*_Substrate) Length() int {
return -1
}
func (*_Substrate) IsAbsent() bool {
return false
}
func (*_Substrate) IsNull() bool {
return false
}
func (*_Substrate) AsBool() (bool, error) {
return mixins.String{"rot13adl.internal.Substrate"}.AsBool()
}
func (*_Substrate) AsInt() (int, error) {
return mixins.String{"rot13adl.internal.Substrate"}.AsInt()
}
func (*_Substrate) AsFloat() (float64, error) {
return mixins.String{"rot13adl.internal.Substrate"}.AsFloat()
}
func (n *_Substrate) AsString() (string, error) {
return n.synthesized, nil
}
func (*_Substrate) AsBytes() ([]byte, error) {
return mixins.String{"rot13adl.internal.Substrate"}.AsBytes()
}
func (*_Substrate) AsLink() (ipld.Link, error) {
return mixins.String{"rot13adl.internal.Substrate"}.AsLink()
}
func (*_Substrate) Prototype() ipld.NodePrototype {
return _Substrate__Prototype{}
}
// -- NodePrototype -->
var _ ipld.NodePrototype = Prototype{}
type SubstrateRootPrototype = _Substrate__Prototype
type _Substrate__Prototype struct {
// There's no configuration to this ADL.
}
func (np _Substrate__Prototype) NewBuilder() ipld.NodeBuilder {
return &_Substrate__Builder{}
}
// -- NodeBuilder -->
var _ ipld.NodeBuilder = (*_Substrate__Builder)(nil)
type _Substrate__Builder struct {
_Substrate__Assembler
}
func (nb *_Substrate__Builder) Build() ipld.Node {
if nb.m != schema.Maybe_Value {
panic("invalid state: cannot call Build on an assembler that's not finished")
}
return nb.w
}
func (nb *_Substrate__Builder) Reset() {
*nb = _Substrate__Builder{}
}
// -- NodeAssembler -->
var _ ipld.NodeAssembler = (*_Substrate__Assembler)(nil)
type _Substrate__Assembler struct {
w *_Substrate
m schema.Maybe // REVIEW: if the package where this Maybe enum lives is maybe not the right home for it after all. Or should this line use something different? We're only using some of its values after all.
}
func (_Substrate__Assembler) BeginMap(sizeHint int) (ipld.MapAssembler, error) {
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.BeginMap(0)
}
func (_Substrate__Assembler) BeginList(sizeHint int) (ipld.ListAssembler, error) {
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.BeginList(0)
}
func (na *_Substrate__Assembler) AssignNull() error {
// REVIEW: unclear how this might compose with some other context (like a schema) which does allow nulls. Probably a wrapper type?
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.AssignNull()
}
func (_Substrate__Assembler) AssignBool(bool) error {
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.AssignBool(false)
}
func (_Substrate__Assembler) AssignInt(int) error {
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.AssignInt(0)
}
func (_Substrate__Assembler) AssignFloat(float64) error {
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.AssignFloat(0)
}
func (na *_Substrate__Assembler) AssignString(v string) error {
switch na.m {
case schema.Maybe_Value:
panic("invalid state: cannot assign into assembler that's already finished")
}
na.w = &_Substrate{
raw: v,
synthesized: unrotate(v),
}
na.m = schema.Maybe_Value
return nil
}
func (_Substrate__Assembler) AssignBytes([]byte) error {
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.AssignBytes(nil)
}
func (_Substrate__Assembler) AssignLink(ipld.Link) error {
return mixins.StringAssembler{"rot13adl.internal.Substrate"}.AssignLink(nil)
}
func (na *_Substrate__Assembler) AssignNode(v ipld.Node) error {
if v.IsNull() {
return na.AssignNull()
}
if v2, ok := v.(*_Substrate); ok {
switch na.m {
case schema.Maybe_Value:
panic("invalid state: cannot assign into assembler that's already finished")
}
na.w = v2
na.m = schema.Maybe_Value
return nil
}
if v2, err := v.AsString(); err != nil {
return err
} else {
return na.AssignString(v2)
}
}
func (_Substrate__Assembler) Prototype() ipld.NodePrototype {
return _Substrate__Prototype{}
}
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