tree - write.go
1 // Package tree implements a tree serialiser and reader.
2 package tree // import "vimagination.zapto.org/tree"
3
4 import (
5 "io"
6 "iter"
7 "slices"
8 "sort"
9 "strings"
10
11 "vimagination.zapto.org/byteio"
12 )
13
14 // Node represents a single node in a Tree.
15 type Node interface {
16 // Children returns an iterator that yields a (unique) name and Node for each
17 // of the child nodes.
18 //
19 // Yielding the children in a lexically sorted order is recommended,
20 // but not required.
21 //
22 // If an error occurs, the Node may be of type ChildrenError, which in
23 // addition to being a Node also implements the error interface.
24 Children() iter.Seq2[string, Node]
25
26 // WriterTo accepts an io.Writer to which any data stored on the node will be
27 // passed.
28 io.WriterTo
29 }
30
31 // Serialise writes a tree structure to the given writer.
32 //
33 // The byte-format for each node is as follows:
34 //
35 // Names []string (stored, in lexical order)
36 // Pointers []int64 (pointer to the end (&Size + 1) of each child node record)
37 // NameSizes []uint64 (lengths of each name, stored as variable-length integers)
38 // Data []byte
39 // Sizes []uint64 (size of NamesSizes and Data sections, stored as variable-length integers; zeros are omitted)
40 // Size uint8 (lower 5 bits: size of the Sizes field, bit 6: size Data > 0, bit 7: size NameSizes > 0)
41 //
42 // NB: All slices are stored without separators.
43 func Serialise(w io.Writer, root Node) error {
44 sw := byteio.StickyLittleEndianWriter{Writer: w}
45
46 writeNode(&sw, root)
47
48 return sw.Err
49 }
50
51 type child struct {
52 name string
53 pos int64
54 }
55
56 type children []child
57
58 func (c children) Less(i, j int) bool {
59 return c[i].name < c[j].name
60 }
61
62 // DuplicateChildError is an error that records the duplicated child name.
63 type DuplicateChildError []string
64
65 // Error implements the error interface.
66 func (d DuplicateChildError) Error() string {
67 return "duplicate child name: " + strings.Join(d, "/")
68 }
69
70 func writeNode(w *byteio.StickyLittleEndianWriter, node Node) {
71 start, sizeChildren := getAndWriteChildren(w, node)
72
73 if w.Err != nil {
74 return
75 }
76
77 startData := w.Count
78
79 if _, err := node.WriteTo(w); err != nil {
80 w.Err = err
81
82 return
83 }
84
85 if start != w.Count {
86 startSizes := w.Count
87 dataSize := startSizes - startData
88
89 var toWrite uint8
90
91 if sizeChildren > 0 {
92 w.WriteUintX(uint64(sizeChildren))
93
94 toWrite |= 0x40
95 }
96
97 if dataSize > 0 {
98 w.WriteUintX(uint64(dataSize))
99
100 toWrite |= 0x20
101 }
102
103 w.WriteUint8(toWrite | uint8(w.Count-startSizes))
104 }
105 }
106
107 func getAndWriteChildren(w *byteio.StickyLittleEndianWriter, node Node) (int64, int64) {
108 var c children
109
110 for name, childNode := range node.Children() {
111 cn := child{name: name}
112 childPos, found := slices.BinarySearchFunc(c, cn, func(a, b child) int {
113 return strings.Compare(a.name, b.name)
114 })
115
116 if found {
117 w.Err = DuplicateChildError{name}
118
119 return 0, 0
120 }
121
122 start := w.Count
123
124 writeNode(w, childNode)
125
126 if w.Err != nil {
127 if dce, ok := w.Err.(DuplicateChildError); ok {
128 w.Err = slices.Insert(dce, 0, name)
129 }
130
131 return 0, 0
132 }
133
134 cn.pos = w.Count
135 if start == cn.pos {
136 cn.pos = 0
137 }
138
139 c = slices.Insert(c, childPos, cn)
140 }
141
142 start := w.Count
143
144 return start, writeChildren(w, c)
145 }
146
147 func writeChildren(w *byteio.StickyLittleEndianWriter, c children) int64 {
148 if len(c) == 0 {
149 return 0
150 }
151
152 sort.Slice(c, c.Less)
153
154 for _, child := range c {
155 w.WriteString(child.name)
156 }
157
158 for _, child := range c {
159 w.WriteInt64(child.pos)
160 }
161
162 sizeStart := w.Count
163
164 for _, child := range c {
165 w.WriteUintX(uint64(len(child.name)))
166 }
167
168 return w.Count - sizeStart
169 }
170