Skip to content

Commit 35ad3e3

Browse files
authored
Merge pull request #98 from ipld/adl-demo-rot13
Add a demo ADL (rot13adl)
2 parents 04afddf + 6541383 commit 35ad3e3

7 files changed

+740
-0
lines changed

adl/rot13adl/example_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package rot13adl_test
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/polydawn/refmt/json"
9+
10+
"github.com/ipld/go-ipld-prime/adl/rot13adl"
11+
"github.com/ipld/go-ipld-prime/codec/dagjson"
12+
"github.com/ipld/go-ipld-prime/must"
13+
)
14+
15+
func ExampleUnmarshallingToADL() {
16+
// Create a NodeBuilder for the ADL's substrate.
17+
// Unmarshalling into this memory structure is optimal,
18+
// because it immediately puts data into the right memory layout for the ADL code to work on,
19+
// but you could use any other kind of NodeBuilder just as well and still get correct results.
20+
nb := rot13adl.Prototype.SubstrateRoot.NewBuilder()
21+
22+
// Unmarshal -- using the substrate's nodebuilder just like you'd unmarshal with any other nodebuilder.
23+
err := dagjson.Unmarshal(nb, json.NewDecoder(strings.NewReader(`"n pbby fgevat"`)))
24+
fmt.Printf("unmarshal error: %v\n", err)
25+
26+
// Use `Reify` to get the synthetic high-level view of the ADL data.
27+
substrateNode := nb.Build()
28+
syntheticView, err := rot13adl.Reify(substrateNode)
29+
fmt.Printf("reify error: %v\n", err)
30+
31+
// We can inspect the synthetic ADL node like any other node!
32+
fmt.Printf("adl node kind: %v\n", syntheticView.ReprKind())
33+
fmt.Printf("adl view value: %q\n", must.String(syntheticView))
34+
35+
// Output:
36+
// unmarshal error: <nil>
37+
// reify error: <nil>
38+
// adl node kind: string
39+
// adl view value: "a cool string"
40+
}
41+
42+
func ExampleCreatingViaADL() {
43+
// Create a NodeBuilder for the ADL -- the high-level synthesized thing (not the substrate).
44+
nb := rot13adl.Prototype.Node.NewBuilder()
45+
46+
// Create a ADL node via its builder. This is just like creating any other node in IPLD.
47+
nb.AssignString("woohoo")
48+
n := nb.Build()
49+
50+
// We can inspect the synthetic ADL node like any other node!
51+
fmt.Printf("adl node kind: %v\n", n.ReprKind())
52+
fmt.Printf("adl view value: %q\n", must.String(n))
53+
54+
// We can get the substrate view and examine that as a node too.
55+
// (This requires a cast to see that we have an ADL, though. Not all IPLD nodes have a 'Substrate' property.)
56+
substrateNode := n.(rot13adl.R13String).Substrate()
57+
fmt.Printf("substrate node kind: %v\n", substrateNode.ReprKind())
58+
fmt.Printf("substrate value: %q\n", must.String(substrateNode))
59+
60+
// To marshal the ADL, just use marshal methods on its substrate as normal:
61+
var marshalBuffer bytes.Buffer
62+
err := dagjson.Marshal(substrateNode, json.NewEncoder(&marshalBuffer, json.EncodeOptions{}))
63+
fmt.Printf("marshalled: %v\n", marshalBuffer.String())
64+
fmt.Printf("marshal error: %v\n", err)
65+
66+
// Output:
67+
// adl node kind: string
68+
// adl view value: "woohoo"
69+
// substrate node kind: string
70+
// substrate value: "jbbubb"
71+
// marshalled: "jbbubb"
72+
// marshal error: <nil>
73+
}
74+
75+
// It's worth noting that the builders for an ADL substrate node still return the substrate.
76+
// (This is interesting in contrast to Schemas, where codegenerated representation-level builders
77+
// yield the type-level node values (and not the representation level node).)
78+
//
79+
// To convert the substrate node to the high level synthesized view of the ADL,
80+
// use Reify as normal -- it's the same whether you've used the substrate type
81+
// or if you've used any other node implementation to hold the data.
82+
//
83+
84+
// Future work: unmarshalling which can invoke an ADL mid-structure,
85+
// and automatically places the reified ADL in place in the larger structure.
86+
//
87+
// There will be several ways to do this (it hinges around "the signalling problem",
88+
// discussed in https://github.com/ipld/specs/issues/130 ):
89+
//
90+
// The first way is to use IPLD Schemas, which provide a signalling mechanism
91+
// by leaning on the schema, and the matching of shape of surrounding data to the schema,
92+
// as a way to determine where an ADL is expected to appear.
93+
//
94+
// A second mechanism could involve new unmarshal function contracts
95+
// which would ake a (fairly complex) argument that says what NodePrototype to use in certain positions.
96+
// This could be accomplished by use of Selectors.
97+
// (This would also have many other potential purposes -- implementing this in terms of NodePrototype selection is very multi-purpose,
98+
// and could be used for efficiency and misc tuning purposes,
99+
// for expecting a *schema* thing part way through, and so forth.)
100+
//

adl/rot13adl/rot13logic.go

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package rot13adl
2+
3+
import (
4+
"strings"
5+
)
6+
7+
var replaceTable = []string{
8+
"A", "N",
9+
"B", "O",
10+
"C", "P",
11+
"D", "Q",
12+
"E", "R",
13+
"F", "S",
14+
"G", "T",
15+
"H", "U",
16+
"I", "V",
17+
"J", "W",
18+
"K", "X",
19+
"L", "Y",
20+
"M", "Z",
21+
"N", "A",
22+
"O", "B",
23+
"P", "C",
24+
"Q", "D",
25+
"R", "E",
26+
"S", "F",
27+
"T", "G",
28+
"U", "H",
29+
"V", "I",
30+
"W", "J",
31+
"X", "K",
32+
"Y", "L",
33+
"Z", "M",
34+
"a", "n",
35+
"b", "o",
36+
"c", "p",
37+
"d", "q",
38+
"e", "r",
39+
"f", "s",
40+
"g", "t",
41+
"h", "u",
42+
"i", "v",
43+
"j", "w",
44+
"k", "x",
45+
"l", "y",
46+
"m", "z",
47+
"n", "a",
48+
"o", "b",
49+
"p", "c",
50+
"q", "d",
51+
"r", "e",
52+
"s", "f",
53+
"t", "g",
54+
"u", "h",
55+
"v", "i",
56+
"w", "j",
57+
"x", "k",
58+
"y", "l",
59+
"z", "m",
60+
}
61+
var unreplaceTable = func() []string {
62+
v := make([]string, len(replaceTable))
63+
for i := 0; i < len(replaceTable); i += 2 {
64+
v[i] = replaceTable[i+1]
65+
v[i+1] = replaceTable[i]
66+
}
67+
return v
68+
}()
69+
70+
// rotate transforms from the logical content to the raw content.
71+
func rotate(s string) string {
72+
return strings.NewReplacer(replaceTable...).Replace(s)
73+
}
74+
75+
// unrotate transforms from the raw content to the logical content.
76+
func unrotate(s string) string {
77+
return strings.NewReplacer(unreplaceTable...).Replace(s)
78+
}

adl/rot13adl/rot13node.go

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
rot13adl is a demo ADL -- its purpose is to show what an ADL and its public interface can look like.
3+
It implements a "rot13" string: when creating data through the ADL, the user gives it a regular string;
4+
the ADL will create aninternal representation of it which has the characters altered in a reversable way.
5+
6+
It provides reference and example materal, but it's very unlikely you want to use it in real situations ;)
7+
8+
There are several ways to move data in and out of the ADL:
9+
10+
- treat it like a regular IPLD map:
11+
- using the exported NodePrototype can be used to get a NodeBuilder which can accept keys and values;
12+
- using the resulting Node and doing lookup operations on it like a regular map;
13+
- load up raw substrate data and `Reify()` it into the synthesized form, and *then* treat it like a regular map:
14+
- this is handy if the raw data already parsed into Nodes.
15+
- optionally, use `SubstrateRootPrototype` as the prototype for loading the raw substrate data;
16+
any kind of Node is a valid input to Reify, but this one will generally have optimal performance.
17+
- take the synthesized form and inspect its substrate data:
18+
- the `Substrate()` method will return another ipld.Node which is the root of the raw substrate data,
19+
and can be walked normally like any other ipld.Node.
20+
*/
21+
package rot13adl
22+
23+
import (
24+
"github.com/ipld/go-ipld-prime"
25+
"github.com/ipld/go-ipld-prime/node/mixins"
26+
"github.com/ipld/go-ipld-prime/schema"
27+
)
28+
29+
// -- Node -->
30+
31+
var _ ipld.Node = (R13String)(nil)
32+
33+
type R13String = *_R13String
34+
35+
type _R13String struct {
36+
raw string // the raw content, before our ADL lens is applied to it.
37+
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).
38+
}
39+
40+
func (*_R13String) ReprKind() ipld.ReprKind {
41+
return ipld.ReprKind_String
42+
}
43+
func (*_R13String) LookupByString(string) (ipld.Node, error) {
44+
return mixins.String{"rot13adl.R13String"}.LookupByString("")
45+
}
46+
func (*_R13String) LookupByNode(ipld.Node) (ipld.Node, error) {
47+
return mixins.String{"rot13adl.R13String"}.LookupByNode(nil)
48+
}
49+
func (*_R13String) LookupByIndex(idx int) (ipld.Node, error) {
50+
return mixins.String{"rot13adl.R13String"}.LookupByIndex(0)
51+
}
52+
func (*_R13String) LookupBySegment(seg ipld.PathSegment) (ipld.Node, error) {
53+
return mixins.String{"rot13adl.R13String"}.LookupBySegment(seg)
54+
}
55+
func (*_R13String) MapIterator() ipld.MapIterator {
56+
return nil
57+
}
58+
func (*_R13String) ListIterator() ipld.ListIterator {
59+
return nil
60+
}
61+
func (*_R13String) Length() int {
62+
return -1
63+
}
64+
func (*_R13String) IsAbsent() bool {
65+
return false
66+
}
67+
func (*_R13String) IsNull() bool {
68+
return false
69+
}
70+
func (*_R13String) AsBool() (bool, error) {
71+
return mixins.String{"rot13adl.R13String"}.AsBool()
72+
}
73+
func (*_R13String) AsInt() (int, error) {
74+
return mixins.String{"rot13adl.R13String"}.AsInt()
75+
}
76+
func (*_R13String) AsFloat() (float64, error) {
77+
return mixins.String{"rot13adl.R13String"}.AsFloat()
78+
}
79+
func (n *_R13String) AsString() (string, error) {
80+
return n.synthesized, nil
81+
}
82+
func (*_R13String) AsBytes() ([]byte, error) {
83+
return mixins.String{"rot13adl.R13String"}.AsBytes()
84+
}
85+
func (*_R13String) AsLink() (ipld.Link, error) {
86+
return mixins.String{"rot13adl.R13String"}.AsLink()
87+
}
88+
func (*_R13String) Prototype() ipld.NodePrototype {
89+
return _R13String__Prototype{}
90+
}
91+
92+
// -- NodePrototype -->
93+
94+
var _ ipld.NodePrototype = _R13String__Prototype{}
95+
96+
type _R13String__Prototype struct {
97+
// There's no configuration to this ADL.
98+
99+
// A more complex ADL might have some kind of parameters here.
100+
//
101+
// The general contract of a NodePrototype is supposed to be that:
102+
// when you get one from an existing Node,
103+
// it should have enough information to create a new Node that
104+
// could "replace" the previous one in whatever context it's in.
105+
// For ADLs, that means it should carry most of the configuration.
106+
//
107+
// An ADL that does multi-block stuff might also need functions like a LinkLoader passed in through here.
108+
}
109+
110+
func (np _R13String__Prototype) NewBuilder() ipld.NodeBuilder {
111+
return &_R13String__Builder{}
112+
}
113+
114+
// -- NodeBuilder -->
115+
116+
var _ ipld.NodeBuilder = (*_R13String__Builder)(nil)
117+
118+
type _R13String__Builder struct {
119+
_R13String__Assembler
120+
}
121+
122+
func (nb *_R13String__Builder) Build() ipld.Node {
123+
if nb.m != schema.Maybe_Value {
124+
panic("invalid state: cannot call Build on an assembler that's not finished")
125+
}
126+
return nb.w
127+
}
128+
func (nb *_R13String__Builder) Reset() {
129+
*nb = _R13String__Builder{}
130+
}
131+
132+
// -- NodeAssembler -->
133+
134+
var _ ipld.NodeAssembler = (*_R13String__Assembler)(nil)
135+
136+
type _R13String__Assembler struct {
137+
w *_R13String
138+
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.
139+
}
140+
141+
func (_R13String__Assembler) BeginMap(sizeHint int) (ipld.MapAssembler, error) {
142+
return mixins.StringAssembler{"rot13adl.R13String"}.BeginMap(0)
143+
}
144+
func (_R13String__Assembler) BeginList(sizeHint int) (ipld.ListAssembler, error) {
145+
return mixins.StringAssembler{"rot13adl.R13String"}.BeginList(0)
146+
}
147+
func (na *_R13String__Assembler) AssignNull() error {
148+
// REVIEW: unclear how this might compose with some other context (like a schema) which does allow nulls. Probably a wrapper type?
149+
return mixins.StringAssembler{"rot13adl.R13String"}.AssignNull()
150+
}
151+
func (_R13String__Assembler) AssignBool(bool) error {
152+
return mixins.StringAssembler{"rot13adl.R13String"}.AssignBool(false)
153+
}
154+
func (_R13String__Assembler) AssignInt(int) error {
155+
return mixins.StringAssembler{"rot13adl.R13String"}.AssignInt(0)
156+
}
157+
func (_R13String__Assembler) AssignFloat(float64) error {
158+
return mixins.StringAssembler{"rot13adl.R13String"}.AssignFloat(0)
159+
}
160+
func (na *_R13String__Assembler) AssignString(v string) error {
161+
switch na.m {
162+
case schema.Maybe_Value:
163+
panic("invalid state: cannot assign into assembler that's already finished")
164+
}
165+
na.w = &_R13String{
166+
raw: rotate(v),
167+
synthesized: v,
168+
}
169+
na.m = schema.Maybe_Value
170+
return nil
171+
}
172+
func (_R13String__Assembler) AssignBytes([]byte) error {
173+
return mixins.StringAssembler{"rot13adl.R13String"}.AssignBytes(nil)
174+
}
175+
func (_R13String__Assembler) AssignLink(ipld.Link) error {
176+
return mixins.StringAssembler{"rot13adl.R13String"}.AssignLink(nil)
177+
}
178+
func (na *_R13String__Assembler) AssignNode(v ipld.Node) error {
179+
if v.IsNull() {
180+
return na.AssignNull()
181+
}
182+
if v2, ok := v.(*_R13String); ok {
183+
switch na.m {
184+
case schema.Maybe_Value:
185+
panic("invalid state: cannot assign into assembler that's already finished")
186+
}
187+
na.w = v2
188+
na.m = schema.Maybe_Value
189+
return nil
190+
}
191+
if v2, err := v.AsString(); err != nil {
192+
return err
193+
} else {
194+
return na.AssignString(v2)
195+
}
196+
}
197+
func (_R13String__Assembler) Prototype() ipld.NodePrototype {
198+
return _R13String__Prototype{}
199+
}

0 commit comments

Comments
 (0)