-
Notifications
You must be signed in to change notification settings - Fork 50
/
Copy pathnodeBuilder.go
168 lines (154 loc) · 7.99 KB
/
nodeBuilder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package datamodel
// NodeAssembler is the interface that describes all the ways we can set values
// in a node that's under construction.
//
// A NodeAssembler is about filling in data.
// To create a new Node, you should start with a NodeBuilder (which contains a
// superset of the NodeAssembler methods, and can return the finished Node
// from its `Build` method).
// While continuing to build a recursive structure from there,
// you'll see NodeAssembler for all the child values.
//
// For filling scalar data, there's a `Assign{Kind}` method for each kind;
// after calling one of these methods, the data is filled in, and the assembler is done.
// For recursives, there are `BeginMap` and `BeginList` methods,
// which return an object that needs further manipulation to fill in the contents.
//
// There is also one special method: `AssignNode`.
// `AssignNode` takes another `Node` as a parameter,
// and should should internally call one of the other `Assign*` or `Begin*` (and subsequent) functions
// as appropriate for the kind of the `Node` it is given.
// This is roughly equivalent to using the `Copy` function (and is often implemented using it!), but
// `AssignNode` may also try to take faster shortcuts in some implementations, when it detects they're possible.
// (For example, for typed nodes, if they're the same type, lots of checking can be skipped.
// For nodes implemented with pointers, lots of copying can be skipped.
// For nodes that can detect the argument has the same memory layout, faster copy mechanisms can be used; etc.)
//
// Why do both this and the NodeBuilder interface exist?
// In short: NodeBuilder is when you want to cause an allocation;
// NodeAssembler can be used to just "fill in" memory.
// (In the internal gritty details: separate interfaces, one of which lacks a
// `Build` method, helps us write efficient library internals: avoiding the
// requirement to be able to return a Node at any random point in the process
// relieves internals from needing to implement 'freeze' features.
// This is useful in turn because implementing those 'freeze' features in a
// language without first-class/compile-time support for them (as golang is)
// would tend to push complexity and costs to execution time; we'd rather not.)
type NodeAssembler interface {
BeginMap(sizeHint int64) (MapAssembler, error)
BeginList(sizeHint int64) (ListAssembler, error)
AssignNull() error
AssignBool(bool) error
AssignInt(int64) error
AssignFloat(float64) error
AssignString(string) error
AssignBytes([]byte) error
AssignLink(Link) error
AssignNode(Node) error // if you already have a completely constructed subtree, this method puts the whole thing in place at once.
// Prototype returns a NodePrototype describing what kind of value we're assembling.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// Using `this.Prototype().NewBuilder()` to produce a new `Node`,
// then giving that node to `this.AssignNode(n)` should always work.
// (Note that this is not necessarily an _exclusive_ statement on what
// sort of values will be accepted by `this.AssignNode(n)`.)
Prototype() NodePrototype
}
// MapAssembler assembles a map node! (You guessed it.)
//
// Methods on MapAssembler must be called in a valid order:
// assemble a key, then assemble a value, then loop as long as desired;
// when finished, call 'Finish'.
//
// Incorrect order invocations will panic.
// Calling AssembleKey twice in a row will panic;
// calling AssembleValue before finishing using the NodeAssembler from AssembleKey will panic;
// calling AssembleValue twice in a row will panic;
// etc.
//
// Note that the NodeAssembler yielded from AssembleKey has additional behavior:
// if the node assembled there matches a key already present in the map,
// that assembler will emit the error!
type MapAssembler interface {
AssembleKey() NodeAssembler // must be followed by call to AssembleValue.
AssembleValue() NodeAssembler // must be called immediately after AssembleKey.
AssembleEntry(k string) (NodeAssembler, error) // shortcut combining AssembleKey and AssembleValue into one step; valid when the key is a string kind.
Finish() error
// KeyPrototype returns a NodePrototype that knows how to build keys of a type this map uses.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// For all Data Model maps, this will answer with a basic concept of "string".
// For Schema typed maps, this may answer with a more complex type
// (potentially even a struct type or union type -- anything that can have a string representation).
KeyPrototype() NodePrototype
// ValuePrototype returns a NodePrototype that knows how to build values this map can contain.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// ValuePrototype requires a parameter describing the key in order to say what
// NodePrototype will be acceptable as a value for that key, because when using
// struct types (or union types) from the Schemas system, they behave as maps
// but have different acceptable types for each field (or member, for unions).
// For plain maps (that is, not structs or unions masquerading as maps),
// the empty string can be used as a parameter, and the returned NodePrototype
// can be assumed applicable for all values.
// Using an empty string for a struct or union will return nil,
// as will using any string which isn't a field or member of those types.
//
// (Design note: a string is sufficient for the parameter here rather than
// a full Node, because the only cases where the value types vary are also
// cases where the keys may not be complex.)
ValuePrototype(k string) NodePrototype
}
type ListAssembler interface {
AssembleValue() NodeAssembler
Finish() error
// ValuePrototype returns a NodePrototype that knows how to build values this map can contain.
//
// You often don't need this (because you should be able to
// just feed data and check errors), but it's here.
//
// ValuePrototype, much like the matching method on the MapAssembler interface,
// requires a parameter specifying the index in the list in order to say
// what NodePrototype will be acceptable as a value at that position.
// For many lists (and *all* lists which operate exclusively at the Data Model level),
// this will return the same NodePrototype regardless of the value of 'idx';
// the only time this value will vary is when operating with a Schema,
// and handling the representation NodeAssembler for a struct type with
// a representation of a list kind.
// If you know you are operating in a situation that won't have varying
// NodePrototypes, it is acceptable to call `ValuePrototype(0)` and use the
// resulting NodePrototype for all reasoning.
ValuePrototype(idx int64) NodePrototype
}
type NodeBuilder interface {
NodeAssembler
// Build returns the new value after all other assembly has been completed.
//
// A method on the NodeAssembler that finishes assembly of the data must
// be called first (e.g., any of the "Assign*" methods, or "Finish" if
// the assembly was for a map or a list); that finishing method still has
// all responsibility for validating the assembled data and returning
// any errors from that process.
// (Correspondingly, there is no error return from this method.)
//
// Note that building via a representation-level NodePrototype or NodeBuilder
// returns a node at the type level which implements schema.TypedNode.
// To obtain the representation-level node, you can do:
//
// // builder is at the representation level, so it returns typed nodes
// node := builder.Build().(schema.TypedNode)
// reprNode := node.Representation()
Build() Node
// Resets the builder. It can hereafter be used again.
// Reusing a NodeBuilder can reduce allocations and improve performance.
//
// Only call this if you're going to reuse the builder.
// (Otherwise, it's unnecessary, and may cause an unwanted allocation).
Reset()
}