package ygo /* #include "libyrs.h" #include */ import "C" import ( "encoding/json" "fmt" "unsafe" ) // Array represents a collaborative array type. type Array struct { branch *C.Branch } // GetArray retrieves or creates a root-level YArray with the given name. func (d *Doc) GetArray(name string) (*Array, error) { if d.ptr == nil { return nil, ErrNilDocument } cName := C.CString(name) defer C.free(unsafe.Pointer(cName)) branch := C.yarray(d.ptr, cName) if branch == nil { return nil, fmt.Errorf("failed to get or create array field %q", name) } a := &Array{branch: branch} return a, nil } // Destroy releases resources. func (a *Array) Destroy() { } // Len returns the number of elements. // Panics if the Array is nil (like len() on a nil slice). func (a *Array) Len() uint32 { if a.branch == nil { panic("ygo: Array.Len called on nil Array") } return uint32(C.yarray_len(a.branch)) } // Get returns the element at index. func (a *Array) Get(txn *Transaction, index uint32) (*Output, error) { if a.branch == nil { return nil, ErrNilBranch } if txn == nil || txn.ptr == nil { return nil, ErrNilTransaction } ptr := C.yarray_get(a.branch, txn.ptr, C.uint32_t(index)) if ptr == nil { return nil, ErrInvalidIndex } return &Output{ptr: ptr}, nil } // InsertRange inserts multiple items starting at index. func (a *Array) InsertRange(txn *Transaction, index uint32, items []Input) error { if a.branch == nil { return ErrNilBranch } if txn == nil || txn.ptr == nil { return ErrNilTransaction } if !txn.IsWriteable() { return ErrNotWriteable } if len(items) == 0 { return nil } // Convert items to C array cInputs := make([]C.YInput, len(items)) for i, item := range items { cInputs[i] = item.cInput } C.yarray_insert_range(a.branch, txn.ptr, C.uint32_t(index), &cInputs[0], C.uint32_t(len(items))) return nil } // Push adds an item to the end. func (a *Array) Push(txn *Transaction, item Input) error { return a.InsertRange(txn, a.Len(), []Input{item}) } // RemoveRange removes elements starting at index. func (a *Array) RemoveRange(txn *Transaction, index, length uint32) error { if a.branch == nil { return ErrNilBranch } if txn == nil || txn.ptr == nil { return ErrNilTransaction } if !txn.IsWriteable() { return ErrNotWriteable } C.yarray_remove_range(a.branch, txn.ptr, C.uint32_t(index), C.uint32_t(length)) return nil } // Iter returns an iterator over the array. func (a *Array) Iter(txn *Transaction) (*ArrayIter, error) { if a.branch == nil { return nil, ErrNilBranch } if txn == nil || txn.ptr == nil { return nil, ErrNilTransaction } ptr := C.yarray_iter(a.branch, txn.ptr) if ptr == nil { return nil, fmt.Errorf("failed to create array iterator") } return &ArrayIter{ptr: ptr}, nil } // ForEach iterates over all elements in the array, calling fn for each element. // The index is passed as the first argument to fn. // If fn returns an error, iteration stops and the error is returned. // The Output is automatically destroyed after fn returns. func (a *Array) ForEach(txn *Transaction, fn func(index uint32, value *Output) error) error { iter, err := a.Iter(txn) if err != nil { return err } defer iter.Destroy() var index uint32 for { out, err := iter.Next() if err != nil { return err } if out == nil { break } if err := fn(index, out); err != nil { out.Destroy() return err } out.Destroy() index++ } return nil } // ArrayIter iterates over array elements. type ArrayIter struct { ptr *C.YArrayIter } // Destroy releases iterator resources. func (it *ArrayIter) Destroy() { if it.ptr != nil { C.yarray_iter_destroy(it.ptr) it.ptr = nil } } // Next returns the next element. Returns nil, nil when iteration is complete. func (it *ArrayIter) Next() (*Output, error) { if it.ptr == nil { return nil, ErrIteratorExhausted } ptr := C.yarray_iter_next(it.ptr) if ptr == nil { return nil, nil // End of iteration } return &Output{ptr: ptr}, nil } // Branch returns the underlying branch pointer. func (a *Array) Branch() unsafe.Pointer { return unsafe.Pointer(a.branch) } // MarshalJSONWithTxn implements JSON marshaling for Array. // Returns a JSON array with all elements marshaled to JSON. func (a *Array) MarshalJSONWithTxn(txn *Transaction) ([]byte, error) { if a.branch == nil { return nil, ErrNilBranch } if txn == nil || txn.ptr == nil { return nil, ErrNilTransaction } // Collect all elements var elements []interface{} iter, err := a.Iter(txn) if err != nil { return nil, err } defer iter.Destroy() for { out, err := iter.Next() if err != nil { return nil, err } if out == nil { break } // Convert value to JSON-compatible type val, err := a.valueToJSON(out, txn) if err != nil { out.Destroy() return nil, err } elements = append(elements, val) out.Destroy() } return json.Marshal(elements) } // valueToJSON converts an Output value to a JSON-compatible Go value. // For primitive types, uses Output methods. For shared types, recursively marshals. func (a *Array) valueToJSON(out *Output, txn *Transaction) (interface{}, error) { if out == nil || out.ptr == nil { return nil, nil } switch out.Tag() { // Primitive JSON types - use Output methods case TagJSONNull: return nil, nil case TagJSONBool: if v, ok := out.Bool(); ok { return v, nil } return nil, fmt.Errorf("failed to read boolean") case TagJSONNum: if v, ok := out.Float(); ok { return v, nil } return nil, fmt.Errorf("failed to read float") case TagJSONInt: if v, ok := out.Int(); ok { return v, nil } return nil, fmt.Errorf("failed to read int") case TagJSONStr: if v, ok := out.String(); ok { return v, nil } return nil, fmt.Errorf("failed to read string") case TagJSONBuf: if v, ok := out.Binary(); ok { return v, nil } return nil, fmt.Errorf("failed to read binary") // JSON array - parsed JSON array case TagJSONArr: return out.convertToJSONArray() // Shared types - recursively marshal case TagMap: branch := C.youtput_read_ymap(out.ptr) if branch == nil { return nil, fmt.Errorf("failed to read ymap from output") } m := &Map{branch: branch} // Create a temporary map with just the branch pointer // We need to get data from it data := make(map[string]interface{}) iter, err := m.Iter(txn) if err != nil { return nil, err } defer iter.Destroy() for { entry, err := iter.Next() if err != nil { return nil, err } if entry == nil { break } val, err := m.valueToJSON(entry.Value, txn) if err != nil { entry.Value.Destroy() return nil, fmt.Errorf("key %q: %w", entry.Key, err) } data[entry.Key] = val entry.Value.Destroy() } return data, nil case TagArray: branch := C.youtput_read_yarray(out.ptr) if branch == nil { return nil, fmt.Errorf("failed to read yarray from output") } arr := &Array{branch: branch} var elements []interface{} iter, err := arr.Iter(txn) if err != nil { return nil, err } defer iter.Destroy() for { el, err := iter.Next() if err != nil { return nil, err } if el == nil { break } val, err := arr.valueToJSON(el, txn) if err != nil { el.Destroy() return nil, err } elements = append(elements, val) el.Destroy() } return elements, nil case TagText, TagXmlElem, TagXmlText, TagXmlFrag: // XML types not supported in array context return nil, fmt.Errorf("XML types not supported in array JSON marshaling: %v", out.Tag()) case TagDoc: return nil, fmt.Errorf("nested documents not supported in JSON marshaling") case TagWeakLink, TagUndefined, TagJSONUndef: return nil, nil default: return nil, fmt.Errorf("unsupported type tag: %v", out.Tag()) } }