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}