@@ -3392,6 +3392,110 @@ path_walk_steps(mrb_state *mrb, mrb_value node, mrb_value steps)
33923392 return node ;
33933393}
33943394
3395+ /* Cursor-based step walking for use inside [*] iteration.
3396+ *
3397+ * Operates entirely on (buf, offset) without allocating a Lazy per step.
3398+ * Mirrors lazy_aref_array / lazy_aref_map's matching logic. Returns
3399+ * the offset of the matched value; raises on key/index miss exactly
3400+ * like the Lazy variants. */
3401+ static mrb_int
3402+ cursor_walk_one_step (mrb_state * mrb , mrb_value buf , mrb_int offset ,
3403+ mrb_value sharedrefs , mrb_value key )
3404+ {
3405+ const uint8_t * base = (const uint8_t * )RSTRING_PTR (buf );
3406+ size_t total = (size_t )RSTRING_LEN (buf );
3407+
3408+ if (unlikely ((size_t )offset >= total ))
3409+ mrb_raise (mrb , E_RANGE_ERROR , "cursor offset out of bounds" );
3410+
3411+ Reader r ;
3412+ r .base = base ; r .p = base + offset ; r .end = base + total ;
3413+ r .depth = 0 ; r .pending_slot = -1 ;
3414+ reader_read_header (mrb , & r );
3415+
3416+ mrb_value resolved ;
3417+ uint8_t major = lazy_resolve_tags (mrb , & r , buf , sharedrefs , & resolved );
3418+
3419+ if (major == 0xFF ) {
3420+ cbor_lazy_t * rp = mrb_data_check_get_ptr (mrb , resolved , & cbor_lazy_type );
3421+ if (rp ) {
3422+ return cursor_walk_one_step (mrb , rp -> buf , rp -> offset , sharedrefs , key );
3423+ }
3424+ mrb_raise (mrb , E_TYPE_ERROR , "CBOR::Path cursor: sharedref target is not indexable" );
3425+ }
3426+
3427+ if (major == 4 ) {
3428+ mrb_int idx = mrb_as_int (mrb , key );
3429+ mrb_int len = cbor_len_to_mrb_int (mrb , read_cbor_uint (mrb , & r , r .info ));
3430+ if (idx < 0 ) idx += len ;
3431+ if (idx < 0 || idx >= len )
3432+ mrb_raisef (mrb , E_INDEX_ERROR , "array index out of range: %d" , (int )idx );
3433+ for (mrb_int i = 0 ; i < idx ; i ++ ) skip_cbor (mrb , & r , buf , sharedrefs );
3434+ return cbor_pdiff (mrb , r .p , r .base );
3435+ }
3436+
3437+ if (major == 5 ) {
3438+ mrb_int pairs = cbor_len_to_mrb_int (mrb , read_cbor_uint (mrb , & r , r .info ));
3439+ const mrb_bool key_is_str = mrb_string_p (key );
3440+
3441+ for (mrb_int i = 0 ; i < pairs ; i ++ ) {
3442+ mrb_bool match ;
3443+ uint8_t kb = reader_read8 (mrb , & r );
3444+ uint8_t kmaj = (uint8_t )(kb >> 5 );
3445+ uint8_t kinfo = (uint8_t )(kb & 0x1F );
3446+
3447+ if (key_is_str && (kmaj == 2 || kmaj == 3 )) {
3448+ mrb_int klen = cbor_len_to_mrb_int (mrb , read_cbor_uint (mrb , & r , kinfo ));
3449+ reader_advance_checked (mrb , & r , klen );
3450+ match = (klen == RSTRING_LEN (key ) &&
3451+ memcmp (r .p - klen , RSTRING_PTR (key ), (size_t )klen ) == 0 );
3452+ } else {
3453+ r .p -- ;
3454+ match = mrb_equal (mrb , decode_value (mrb , & r , buf , sharedrefs ), key );
3455+ }
3456+
3457+ if (match ) return cbor_pdiff (mrb , r .p , r .base );
3458+ skip_cbor (mrb , & r , buf , sharedrefs );
3459+ }
3460+ mrb_raisef (mrb , E_KEY_ERROR , "key not found: \"%v\"" , key );
3461+ }
3462+
3463+ mrb_raise (mrb , E_TYPE_ERROR , "CBOR::Path cursor: not indexable" );
3464+ }
3465+
3466+ /* Walk all steps of a step-list starting from `offset`, returning the
3467+ * final offset. The step-list format is [sym, key, sym, key, ...] —
3468+ * same as path_walk_steps consumes via cbor_lazy_aref. */
3469+ static mrb_int
3470+ cursor_walk_steps (mrb_state * mrb , mrb_value buf , mrb_int offset ,
3471+ mrb_value sharedrefs , mrb_value steps )
3472+ {
3473+ mrb_int n = RARRAY_LEN (steps );
3474+ for (mrb_int i = 1 ; i < n ; i += 2 ) {
3475+ offset = cursor_walk_one_step (mrb , buf , offset , sharedrefs ,
3476+ mrb_ary_ref (mrb , steps , i ));
3477+ }
3478+ return offset ;
3479+ }
3480+
3481+ /* Materialize the value at offset. Replaces cbor_lazy_value at the leaf
3482+ * of a wildcard walk. */
3483+ static mrb_value
3484+ cursor_materialize (mrb_state * mrb , mrb_value buf , mrb_int offset ,
3485+ mrb_value sharedrefs )
3486+ {
3487+ const uint8_t * base = (const uint8_t * )RSTRING_PTR (buf );
3488+ size_t total = (size_t )RSTRING_LEN (buf );
3489+
3490+ if (unlikely ((size_t )offset >= total ))
3491+ mrb_raise (mrb , E_RANGE_ERROR , "cursor offset out of bounds" );
3492+
3493+ Reader r ;
3494+ r .base = base ; r .p = base + offset ; r .end = base + total ;
3495+ r .depth = 0 ; r .pending_slot = -1 ;
3496+ return decode_value (mrb , & r , buf , sharedrefs );
3497+ }
3498+
33953499/* [*] driver. `node` is a Lazy positioned at the wildcard's array
33963500 * (possibly behind a Tag 29 sharedref). Resolves the sharedref chain
33973501 * once up front, reads the array length from the header, then indexes
@@ -3422,33 +3526,33 @@ path_walk_wildcards(mrb_state *mrb, mrb_value node,
34223526 mrb_int nseg = RARRAY_LEN (segments );
34233527 mrb_value next_steps = mrb_ary_ref (mrb , segments , depth + 1 );
34243528 mrb_bool is_leaf = (depth == nseg - 2 );
3425- mrb_value kcache = mrb_iv_get (mrb , node , MRB_SYM (kcache ));
34263529 mrb_value results = mrb_ary_new_capa (mrb , len );
34273530 int arena = mrb_gc_arena_save (mrb );
34283531
3429- /* All elements cached iff kcache has at least len entries.
3430- * In that case r.p tracking and skip_cbor are not needed. */
3431- mrb_bool fully_cached = (mrb_hash_size (mrb , kcache ) >= len );
3432-
3532+ /* Cursor-based iteration: walk by offset, never allocate a Lazy
3533+ * for sub-elements within this loop. The kcache previously
3534+ * populated here was never read (each index visited at most
3535+ * once per wildcard call), so we drop it.
3536+ *
3537+ * Per element:
3538+ * 1. snapshot r.p as element offset
3539+ * 2. skip_cbor advances r.p past the element for next iteration
3540+ * 3. cursor_walk_steps walks remaining steps from element offset
3541+ * 4. cursor_materialize at the leaf, or recurse with one Lazy
3542+ * for inner [*] (one Lazy per outer iter is acceptable). */
34333543 for (mrb_int i = 0 ; i < len ; i ++ ) {
3434- mrb_value idx_v = mrb_convert_mrb_int (mrb , i );
3435- mrb_value elem = mrb_hash_fetch (mrb , kcache , idx_v , mrb_undef_value ());
3436-
3437- if (mrb_undef_p (elem )) {
3438- /* Cache miss — r.p is at element i, create lazy and advance. */
3439- mrb_int elem_offset = cbor_pdiff (mrb , r .p , r .base );
3440- elem = cbor_lazy_new (mrb , p -> buf , elem_offset , sharedrefs );
3441- mrb_hash_set (mrb , kcache , idx_v , elem );
3442- }
3443-
3444- if (!fully_cached ) {
3445- skip_cbor (mrb , & r , p -> buf , sharedrefs );
3544+ mrb_int elem_offset = cbor_pdiff (mrb , r .p , r .base );
3545+ skip_cbor (mrb , & r , p -> buf , sharedrefs );
3546+
3547+ mrb_int leaf_offset = cursor_walk_steps (mrb , p -> buf , elem_offset ,
3548+ sharedrefs , next_steps );
3549+ mrb_value val ;
3550+ if (is_leaf ) {
3551+ val = cursor_materialize (mrb , p -> buf , leaf_offset , sharedrefs );
3552+ } else {
3553+ mrb_value sub_lazy = cbor_lazy_new (mrb , p -> buf , leaf_offset , sharedrefs );
3554+ val = path_walk_wildcards (mrb , sub_lazy , segments , depth + 1 );
34463555 }
3447-
3448- mrb_value next = path_walk_steps (mrb , elem , next_steps );
3449- mrb_value val = is_leaf
3450- ? cbor_lazy_value (mrb , next )
3451- : path_walk_wildcards (mrb , next , segments , depth + 1 );
34523556 mrb_ary_push (mrb , results , val );
34533557 mrb_gc_arena_restore (mrb , arena );
34543558 }
0 commit comments