array.go
1package ygo
2
3/*
4#include "libyrs.h"
5#include <stdlib.h>
6*/
7import "C"
8import (
9 "encoding/json"
10 "fmt"
11 "unsafe"
12)
13
14// Array represents a collaborative array type.
15type Array struct {
16 branch *C.Branch
17}
18
19// GetArray retrieves or creates a root-level YArray with the given name.
20func (d *Doc) GetArray(name string) (*Array, error) {
21 if d.ptr == nil {
22 return nil, ErrNilDocument
23 }
24 cName := C.CString(name)
25 defer C.free(unsafe.Pointer(cName))
26
27 branch := C.yarray(d.ptr, cName)
28 if branch == nil {
29 return nil, fmt.Errorf("failed to get or create array field %q", name)
30 }
31
32 a := &Array{branch: branch}
33 return a, nil
34}
35
36// Destroy releases resources.
37func (a *Array) Destroy() {
38}
39
40// Len returns the number of elements.
41// Panics if the Array is nil (like len() on a nil slice).
42func (a *Array) Len() uint32 {
43 if a.branch == nil {
44 panic("ygo: Array.Len called on nil Array")
45 }
46 return uint32(C.yarray_len(a.branch))
47}
48
49// Get returns the element at index.
50func (a *Array) Get(txn *Transaction, index uint32) (*Output, error) {
51 if a.branch == nil {
52 return nil, ErrNilBranch
53 }
54 if txn == nil || txn.ptr == nil {
55 return nil, ErrNilTransaction
56 }
57 ptr := C.yarray_get(a.branch, txn.ptr, C.uint32_t(index))
58 if ptr == nil {
59 return nil, ErrInvalidIndex
60 }
61 return &Output{ptr: ptr}, nil
62}
63
64// InsertRange inserts multiple items starting at index.
65func (a *Array) InsertRange(txn *Transaction, index uint32, items []Input) error {
66 if a.branch == nil {
67 return ErrNilBranch
68 }
69 if txn == nil || txn.ptr == nil {
70 return ErrNilTransaction
71 }
72 if !txn.IsWriteable() {
73 return ErrNotWriteable
74 }
75 if len(items) == 0 {
76 return nil
77 }
78
79 // Convert items to C array
80 cInputs := make([]C.YInput, len(items))
81 for i, item := range items {
82 cInputs[i] = item.cInput
83 }
84
85 C.yarray_insert_range(a.branch, txn.ptr, C.uint32_t(index), &cInputs[0], C.uint32_t(len(items)))
86 return nil
87}
88
89// Push adds an item to the end.
90func (a *Array) Push(txn *Transaction, item Input) error {
91 return a.InsertRange(txn, a.Len(), []Input{item})
92}
93
94// RemoveRange removes elements starting at index.
95func (a *Array) RemoveRange(txn *Transaction, index, length uint32) error {
96 if a.branch == nil {
97 return ErrNilBranch
98 }
99 if txn == nil || txn.ptr == nil {
100 return ErrNilTransaction
101 }
102 if !txn.IsWriteable() {
103 return ErrNotWriteable
104 }
105 C.yarray_remove_range(a.branch, txn.ptr, C.uint32_t(index), C.uint32_t(length))
106 return nil
107}
108
109// Iter returns an iterator over the array.
110func (a *Array) Iter(txn *Transaction) (*ArrayIter, error) {
111 if a.branch == nil {
112 return nil, ErrNilBranch
113 }
114 if txn == nil || txn.ptr == nil {
115 return nil, ErrNilTransaction
116 }
117 ptr := C.yarray_iter(a.branch, txn.ptr)
118 if ptr == nil {
119 return nil, fmt.Errorf("failed to create array iterator")
120 }
121 return &ArrayIter{ptr: ptr}, nil
122}
123
124// ForEach iterates over all elements in the array, calling fn for each element.
125// The index is passed as the first argument to fn.
126// If fn returns an error, iteration stops and the error is returned.
127// The Output is automatically destroyed after fn returns.
128func (a *Array) ForEach(txn *Transaction, fn func(index uint32, value *Output) error) error {
129 iter, err := a.Iter(txn)
130 if err != nil {
131 return err
132 }
133 defer iter.Destroy()
134
135 var index uint32
136 for {
137 out, err := iter.Next()
138 if err != nil {
139 return err
140 }
141 if out == nil {
142 break
143 }
144
145 if err := fn(index, out); err != nil {
146 out.Destroy()
147 return err
148 }
149 out.Destroy()
150 index++
151 }
152 return nil
153}
154
155// ArrayIter iterates over array elements.
156type ArrayIter struct {
157 ptr *C.YArrayIter
158}
159
160// Destroy releases iterator resources.
161func (it *ArrayIter) Destroy() {
162 if it.ptr != nil {
163 C.yarray_iter_destroy(it.ptr)
164 it.ptr = nil
165 }
166}
167
168// Next returns the next element. Returns nil, nil when iteration is complete.
169func (it *ArrayIter) Next() (*Output, error) {
170 if it.ptr == nil {
171 return nil, ErrIteratorExhausted
172 }
173 ptr := C.yarray_iter_next(it.ptr)
174 if ptr == nil {
175 return nil, nil // End of iteration
176 }
177 return &Output{ptr: ptr}, nil
178}
179
180// Branch returns the underlying branch pointer.
181func (a *Array) Branch() unsafe.Pointer {
182 return unsafe.Pointer(a.branch)
183}
184
185// MarshalJSONWithTxn implements JSON marshaling for Array.
186// Returns a JSON array with all elements marshaled to JSON.
187func (a *Array) MarshalJSONWithTxn(txn *Transaction) ([]byte, error) {
188 if a.branch == nil {
189 return nil, ErrNilBranch
190 }
191 if txn == nil || txn.ptr == nil {
192 return nil, ErrNilTransaction
193 }
194
195 // Collect all elements
196 var elements []interface{}
197
198 iter, err := a.Iter(txn)
199 if err != nil {
200 return nil, err
201 }
202 defer iter.Destroy()
203
204 for {
205 out, err := iter.Next()
206 if err != nil {
207 return nil, err
208 }
209 if out == nil {
210 break
211 }
212
213 // Convert value to JSON-compatible type
214 val, err := a.valueToJSON(out, txn)
215 if err != nil {
216 out.Destroy()
217 return nil, err
218 }
219
220 elements = append(elements, val)
221 out.Destroy()
222 }
223
224 return json.Marshal(elements)
225}
226
227// valueToJSON converts an Output value to a JSON-compatible Go value.
228// For primitive types, uses Output methods. For shared types, recursively marshals.
229func (a *Array) valueToJSON(out *Output, txn *Transaction) (interface{}, error) {
230 if out == nil || out.ptr == nil {
231 return nil, nil
232 }
233
234 switch out.Tag() {
235 // Primitive JSON types - use Output methods
236 case TagJSONNull:
237 return nil, nil
238 case TagJSONBool:
239 if v, ok := out.Bool(); ok {
240 return v, nil
241 }
242 return nil, fmt.Errorf("failed to read boolean")
243 case TagJSONNum:
244 if v, ok := out.Float(); ok {
245 return v, nil
246 }
247 return nil, fmt.Errorf("failed to read float")
248 case TagJSONInt:
249 if v, ok := out.Int(); ok {
250 return v, nil
251 }
252 return nil, fmt.Errorf("failed to read int")
253 case TagJSONStr:
254 if v, ok := out.String(); ok {
255 return v, nil
256 }
257 return nil, fmt.Errorf("failed to read string")
258 case TagJSONBuf:
259 if v, ok := out.Binary(); ok {
260 return v, nil
261 }
262 return nil, fmt.Errorf("failed to read binary")
263
264 // JSON array - parsed JSON array
265 case TagJSONArr:
266 return out.convertToJSONArray()
267
268 // Shared types - recursively marshal
269 case TagMap:
270 branch := C.youtput_read_ymap(out.ptr)
271 if branch == nil {
272 return nil, fmt.Errorf("failed to read ymap from output")
273 }
274 m := &Map{branch: branch}
275 // Create a temporary map with just the branch pointer
276 // We need to get data from it
277 data := make(map[string]interface{})
278 iter, err := m.Iter(txn)
279 if err != nil {
280 return nil, err
281 }
282 defer iter.Destroy()
283 for {
284 entry, err := iter.Next()
285 if err != nil {
286 return nil, err
287 }
288 if entry == nil {
289 break
290 }
291 val, err := m.valueToJSON(entry.Value, txn)
292 if err != nil {
293 entry.Value.Destroy()
294 return nil, fmt.Errorf("key %q: %w", entry.Key, err)
295 }
296 data[entry.Key] = val
297 entry.Value.Destroy()
298 }
299 return data, nil
300 case TagArray:
301 branch := C.youtput_read_yarray(out.ptr)
302 if branch == nil {
303 return nil, fmt.Errorf("failed to read yarray from output")
304 }
305 arr := &Array{branch: branch}
306 var elements []interface{}
307 iter, err := arr.Iter(txn)
308 if err != nil {
309 return nil, err
310 }
311 defer iter.Destroy()
312 for {
313 el, err := iter.Next()
314 if err != nil {
315 return nil, err
316 }
317 if el == nil {
318 break
319 }
320 val, err := arr.valueToJSON(el, txn)
321 if err != nil {
322 el.Destroy()
323 return nil, err
324 }
325 elements = append(elements, val)
326 el.Destroy()
327 }
328 return elements, nil
329 case TagText, TagXmlElem, TagXmlText, TagXmlFrag:
330 // XML types not supported in array context
331 return nil, fmt.Errorf("XML types not supported in array JSON marshaling: %v", out.Tag())
332 case TagDoc:
333 return nil, fmt.Errorf("nested documents not supported in JSON marshaling")
334 case TagWeakLink, TagUndefined, TagJSONUndef:
335 return nil, nil
336 default:
337 return nil, fmt.Errorf("unsupported type tag: %v", out.Tag())
338 }
339}