qr_reader-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,1052 @@
1
+ /* sp_array.c -- cold typed-array operations (see sp_array.h).
2
+ *
3
+ * The hot accessors (new / push / pop / shift / get / set / length /
4
+ * empty) are inline in sp_array.h and compiled per generated TU. The
5
+ * out-of-line ops below -- sort / slice / dup / set algebra / join /
6
+ * inspect-free reductions -- are cold (never on optcarrot's inner loop),
7
+ * so they compile once into libspinel_rt.a instead of into every TU.
8
+ *
9
+ * They reach the GC string heap via sp_alloc.h and call back into the
10
+ * inline core through sp_array.h; sp_sprintf / sp_raise_* are resolved
11
+ * at the final link against the generated TU. */
12
+ #include <stdlib.h>
13
+ #include <string.h>
14
+ #include <stdio.h>
15
+ #include <math.h>
16
+ #include <float.h>
17
+ #include <stdint.h>
18
+ #include "sp_array.h"
19
+ #include "sp_random.h" /* sp_krand_below for shuffle/sample */
20
+ #include "sp_string.h" /* sp_String builder for join/to_poly */
21
+ #include "sp_inspect.h" /* sp_inspect_container for the #inspect wrappers */
22
+ #include "sp_str.h" /* sp_str_succ for from_string_range */
23
+
24
+ /* Issue #799: clamp e-s+1 against size_t overflow + an arbitrary sanity
25
+ cap (1 << 30 elements; ~8 GB at 8 bytes/elem). Without the cap,
26
+ `(1..INTPTR_MAX).to_a` overflows the realloc size_t to a tiny number,
27
+ then writes past the allocation. */
28
+ sp_IntArray*sp_IntArray_from_range(sp_int s,sp_int e){sp_IntArray*a=sp_IntArray_new();sp_int n=e-s+1;if(n<0)n=0;if(n>(sp_int)(1LL<<30))n=(sp_int)(1LL<<30);if(n>a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes_sub(sizeof(sp_int)*a->cap);h->size-=sizeof(sp_int)*a->cap;a->cap=n;a->data=(sp_int*)sp_pl_realloc(a->data,sizeof(sp_int)*a->cap);h->size+=sizeof(sp_int)*a->cap;sp_gc_bytes_add(sizeof(sp_int)*a->cap);}for(sp_int i=0;i<n;i++)a->data[i]=s+i;a->len=n;return a;}
29
+ /* (beg..end).step(step) materialised as an IntArray. step==0 raises like
30
+ CRuby; a negative step descends; exclusive ranges drop the endpoint. The
31
+ loop tests the bound directly and guards v+=step, so a range near the
32
+ sp_int limits (e.g. an endless range's INTPTR_MAX end) cannot overflow --
33
+ signed integer overflow is undefined behaviour in C. */
34
+ sp_IntArray*sp_IntArray_from_range_step(sp_int beg,sp_int end,sp_int step,sp_int excl){
35
+ if(step==0)sp_raise_cls("ArgumentError","step can't be 0");
36
+ /* An endless range reaches here as INTPTR_MAX, so the loop below walks the
37
+ whole integer line and the process hangs. The float variant already
38
+ refuses a span it cannot materialise; do the same here, counting in
39
+ unsigned arithmetic so the span of an endless range cannot overflow.
40
+ CRuby answers a lazy sequence, which spinel does not have. */
41
+ { int forward = step > 0 ? (beg < end || (!excl && beg == end))
42
+ : (beg > end || (!excl && beg == end));
43
+ uintptr_t span = step > 0 ? (uintptr_t)end - (uintptr_t)beg
44
+ : (uintptr_t)beg - (uintptr_t)end;
45
+ uintptr_t mag = step > 0 ? (uintptr_t)step : -(uintptr_t)step;
46
+ if(forward && span / mag >= (uintptr_t)(1LL<<30))
47
+ sp_raise_cls("RangeError","range too large to materialize"); }
48
+ sp_IntArray*a=sp_IntArray_new();
49
+ if(step>0){
50
+ for(sp_int v=beg;v<end||(!excl&&v==end);){
51
+ sp_IntArray_push(a,v);
52
+ if(v>0&&step>INTPTR_MAX-v)break;
53
+ v+=step;
54
+ }
55
+ }
56
+ else{
57
+ for(sp_int v=beg;v>end||(!excl&&v==end);){
58
+ sp_IntArray_push(a,v);
59
+ if(v<0&&step<INTPTR_MIN-v)break;
60
+ v+=step;
61
+ }
62
+ }
63
+ return a;
64
+ }
65
+ sp_IntArray*sp_IntArray_dup(sp_IntArray*a){SP_GC_ROOT(a);sp_IntArray*b=sp_IntArray_new();if(a->len>b->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)b-sizeof(sp_gc_hdr));sp_gc_bytes_sub(sizeof(sp_int)*b->cap);h->size-=sizeof(sp_int)*b->cap;b->cap=a->len;void*nd=sp_pl_realloc(b->data,sizeof(sp_int)*b->cap);if(!nd)sp_oom_die();b->data=(sp_int*)nd;h->size+=sizeof(sp_int)*b->cap;sp_gc_bytes_add(sizeof(sp_int)*b->cap);}memcpy(b->data,a->data+a->start,sizeof(sp_int)*a->len);b->len=a->len;SP_MAY_NIL(b)=SP_MAY_NIL(a);return b;}
66
+ /* a[start, len] / a[start..end] for IntArray. Negative start counts from
67
+ the end. start past the array length yields an empty result; len is
68
+ clamped so we never read past the source. CRuby returns nil for
69
+ out-of-bounds start; we return an empty IntArray since this typed
70
+ collection has no nullable form. */
71
+ /* Slice length clamp, shared shape across every *_slice below and in
72
+ sp_str.c / spinel_rt.h: written as `len > N - start`, never
73
+ `start + len > N`. Ruby hands the length straight through from user code,
74
+ so `a[1, 2**63-1]` reaches here with len == INT64_MAX; the sum then
75
+ overflows, wraps negative, the guard reads FALSE, and the clamp is skipped
76
+ entirely. The copy loop after it is bounded only by len, so it walks off the
77
+ heap -- a segfault today on ordinary -O2 for an index CRuby answers fine.
78
+ By this point start is already known to be in [0, N), so N - start cannot
79
+ underflow. */
80
+ sp_IntArray*sp_IntArray_slice(sp_IntArray*a,sp_int start,sp_int len){SP_GC_ROOT(a);if(start<0)start+=a->len;if(start<0)start=0;sp_IntArray*b=sp_IntArray_new();if(start>=a->len||len<=0)return b;if(len>a->len-start)len=a->len-start;if(len>b->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)b-sizeof(sp_gc_hdr));sp_gc_bytes_sub(sizeof(sp_int)*b->cap);h->size-=sizeof(sp_int)*b->cap;b->cap=len;b->data=(sp_int*)sp_pl_realloc(b->data,sizeof(sp_int)*b->cap);h->size+=sizeof(sp_int)*b->cap;sp_gc_bytes_add(sizeof(sp_int)*b->cap);}memcpy(b->data,a->data+a->start+start,sizeof(sp_int)*len);b->len=len;SP_MAY_NIL(b)=SP_MAY_NIL(a);return b;}
81
+ /* a[start..end] / a[start...end] with possibly negative endpoints.
82
+ Normalize end against a->len first; the bare _slice already handles
83
+ negative start. Issue #496. */
84
+ sp_IntArray*sp_IntArray_slice_range(sp_IntArray*a,sp_int start,sp_int end_,sp_int excl){SP_GC_ROOT(a);if(end_<0)end_+=a->len;if(start<0)start+=a->len;if(start<0||start>a->len)return NULL;/* a start outside [-len, len] is nil (#4524); start == len is [] */sp_int n=end_-start+(excl?0:1);if(n<0)n=0;return sp_IntArray_slice(a,start,n);}
85
+ /* `a.replace(a)` is a no-op in Ruby. Truncating first and copying after made
86
+ it empty the array instead -- the source is the destination, so its length
87
+ was already zero by the time the copy read it. The poly form takes the same
88
+ guard.
89
+
90
+ And a FROZEN receiver raises before the truncation, not after: every other
91
+ mutator here checks, but replace did not, so `[1, 2].freeze.replace([3, 4])`
92
+ quietly rewrote it. */
93
+ void sp_IntArray_replace(sp_IntArray*dst,sp_IntArray*src){if(dst==src)return;if(dst->frozen){sp_raise_frozen_array_at(dst,SP_BUILTIN_INT_ARRAY);return;}dst->len=0;dst->start=0;if(src->len>dst->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)dst-sizeof(sp_gc_hdr));sp_gc_bytes_sub(sizeof(sp_int)*dst->cap);h->size-=sizeof(sp_int)*dst->cap;void*nd=sp_pl_realloc(dst->data,sizeof(sp_int)*src->len);if(!nd){perror("realloc");exit(1);}dst->data=(sp_int*)nd;dst->cap=src->len;h->size+=sizeof(sp_int)*dst->cap;sp_gc_bytes_add(sizeof(sp_int)*dst->cap);}memcpy(dst->data,src->data+src->start,sizeof(sp_int)*src->len);dst->len=src->len;SP_MAY_NIL(dst)=SP_MAY_NIL(src);}
94
+ /* The cold start-past-the-end splice: nil (the sentinel) up to `s`, then the
95
+ source. Kept apart so the splice's in-range path below is unchanged. */
96
+ static SP_NOINLINE SP_COLD void sp_IntArray_splice_gap(sp_IntArray*a,sp_int s,const sp_int*src,sp_int srcn){
97
+ SP_GC_ROOT(a);
98
+ sp_int*sb=NULL;
99
+ if(srcn>0){sb=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)srcn);if(!sb)sp_oom_die();memcpy(sb,src,sizeof(sp_int)*(size_t)srcn);}
100
+ SP_MAY_NIL(a)=1;
101
+ while(a->len<s)sp_IntArray_push(a,SP_INT_NIL);
102
+ for(sp_int i=0;i<srcn;i++)sp_IntArray_push(a,sb[i]);
103
+ sp_pl_free(sb);
104
+ }
105
+ static SP_NOINLINE SP_COLD void sp_FloatArray_splice_gap(sp_FloatArray*a,sp_int s,const sp_float*src,sp_int srcn){
106
+ SP_GC_ROOT(a);
107
+ sp_float*sb=NULL;
108
+ if(srcn>0){sb=(sp_float*)sp_pl_alloc(sizeof(sp_float)*(size_t)srcn);if(!sb)sp_oom_die();memcpy(sb,src,sizeof(sp_float)*(size_t)srcn);}
109
+ SP_MAY_NIL(a)=1;
110
+ while(a->len<s)sp_FloatArray_push(a,sp_float_nil());
111
+ for(sp_int i=0;i<srcn;i++)sp_FloatArray_push(a,sb[i]);
112
+ sp_pl_free(sb);
113
+ }
114
+ /* arr[start,len] = src / arr[range] = src : remove `len` elements at `start`
115
+ and insert the `srcn` elements of `src` in their place, shifting the tail.
116
+ Reuses push so capacity growth + GC byte accounting stay in one place. src
117
+ is snapshotted first so splicing an array into itself (`a[1,1]=a`) is safe.
118
+ A start beyond the end pads the gap with nil -- the sentinel, or NULL in a
119
+ String array -- as CRuby does (the *_splice_gap helpers); it raised while a
120
+ typed array could not hold nil. */
121
+ void sp_IntArray_splice(sp_IntArray*a,sp_int start,sp_int len,const sp_int*src,sp_int srcn){
122
+ if(!a)return;
123
+ if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}
124
+ /* The equal-length overwrite below allocates nothing, so it needs no root --
125
+ and it is the hot shape (optcarrot's per-tile `@bg_pixels[x, 8] = row`).
126
+ Root only on the way into the general path, which does allocate. */
127
+ sp_int alen=a->len,s=start;
128
+ if(s<0)s+=alen;
129
+ if(len<0){sp_raise_cls("IndexError",sp_sprintf("negative length (%lld)",(long long)len));return;}
130
+ if(s<0){sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)start,(long long)-alen));return;}
131
+ if(s>alen){sp_IntArray_splice_gap(a,s,src,srcn);return;}
132
+ if(s+len>alen)len=alen-s;
133
+ /* Equal-length replacement is a pure overwrite: no length change, no
134
+ capacity growth, no tail shift. memmove, since src may alias a's own
135
+ buffer (self-splice). This is the hot shape (optcarrot's per-tile
136
+ `@bg_pixels[x, 8] = <8-elem row>`); the general path below allocates. */
137
+ if(len==srcn){if(srcn>0)memmove(a->data+a->start+s,src,sizeof(sp_int)*(size_t)srcn);return;}
138
+ SP_GC_ROOT(a);
139
+ sp_int*sb=NULL;
140
+ if(srcn>0){sb=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)srcn);if(!sb)sp_oom_die();memcpy(sb,src,sizeof(sp_int)*(size_t)srcn);}
141
+ sp_int tail_from=s+len,tail_n=alen-tail_from;
142
+ sp_int*tb=NULL;
143
+ if(tail_n>0){tb=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)tail_n);if(!tb){sp_pl_free(sb);sp_oom_die();}memcpy(tb,a->data+a->start+tail_from,sizeof(sp_int)*(size_t)tail_n);}
144
+ a->len=s;
145
+ for(sp_int i=0;i<srcn;i++)sp_IntArray_push(a,sb[i]);
146
+ for(sp_int i=0;i<tail_n;i++)sp_IntArray_push(a,tb[i]);
147
+ sp_pl_free(sb);sp_pl_free(tb);
148
+ }
149
+ void sp_FloatArray_splice(sp_FloatArray*a,sp_int start,sp_int len,const sp_float*src,sp_int srcn){
150
+ if(!a)return;
151
+ if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY);return;}
152
+ sp_int alen=a->len,s=start;
153
+ if(s<0)s+=alen;
154
+ if(len<0){sp_raise_cls("IndexError",sp_sprintf("negative length (%lld)",(long long)len));return;}
155
+ if(s<0){sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)start,(long long)-alen));return;}
156
+ if(s>alen){sp_FloatArray_splice_gap(a,s,src,srcn);return;}
157
+ if(s+len>alen)len=alen-s;
158
+ if(len==srcn){if(srcn>0)memmove(a->data+s,src,sizeof(sp_float)*(size_t)srcn);return;} /* see the int form */
159
+ SP_GC_ROOT(a);
160
+ sp_float*sb=NULL;
161
+ if(srcn>0){sb=(sp_float*)sp_pl_alloc(sizeof(sp_float)*(size_t)srcn);if(!sb)sp_oom_die();memcpy(sb,src,sizeof(sp_float)*(size_t)srcn);}
162
+ sp_int tail_from=s+len,tail_n=alen-tail_from;
163
+ sp_float*tb=NULL;
164
+ if(tail_n>0){tb=(sp_float*)sp_pl_alloc(sizeof(sp_float)*(size_t)tail_n);if(!tb){sp_pl_free(sb);sp_oom_die();}memcpy(tb,a->data+tail_from,sizeof(sp_float)*(size_t)tail_n);}
165
+ a->len=s;
166
+ for(sp_int i=0;i<srcn;i++)sp_FloatArray_push(a,sb[i]);
167
+ for(sp_int i=0;i<tail_n;i++)sp_FloatArray_push(a,tb[i]);
168
+ sp_pl_free(sb);sp_pl_free(tb);
169
+ }
170
+ /* A String array's nil is NULL, as a past-the-end `a[i] = s` pads it. The
171
+ source strings stay reachable through the caller's array: a is not
172
+ truncated here, so the raw snapshot needs no rooted holder. */
173
+ static SP_NOINLINE SP_COLD void sp_StrArray_splice_gap(sp_StrArray*a,sp_int s,const char*const*src,sp_int srcn){
174
+ SP_GC_ROOT(a);
175
+ const char**sb=NULL;
176
+ if(srcn>0){sb=(const char**)sp_pl_alloc(sizeof(const char*)*(size_t)srcn);if(!sb)sp_oom_die();memcpy(sb,src,sizeof(const char*)*(size_t)srcn);}
177
+ while(a->len<s)sp_StrArray_push(a,NULL);
178
+ for(sp_int i=0;i<srcn;i++)sp_StrArray_push(a,sb[i]);
179
+ sp_pl_free(sb);
180
+ }
181
+ void sp_StrArray_splice(sp_StrArray*a,sp_int start,sp_int len,const char*const*src,sp_int srcn){
182
+ if(!a)return;
183
+ if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return;}
184
+ sp_int alen=a->len,s=start;
185
+ if(s<0)s+=alen;
186
+ if(len<0){sp_raise_cls("IndexError",sp_sprintf("negative length (%lld)",(long long)len));return;}
187
+ if(s<0){sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)start,(long long)-alen));return;}
188
+ if(s>alen){sp_StrArray_splice_gap(a,s,src,srcn);return;}
189
+ if(s+len>alen)len=alen-s;
190
+ if(len==srcn){if(srcn>0)memmove(a->data+s,src,sizeof(const char*)*(size_t)srcn);return;} /* see the int form */
191
+ SP_GC_ROOT(a);
192
+ const char**sb=NULL;
193
+ if(srcn>0){sb=(const char**)sp_pl_alloc(sizeof(const char*)*(size_t)srcn);if(!sb)sp_oom_die();memcpy(sb,src,sizeof(const char*)*(size_t)srcn);}
194
+ /* Snapshot the tail into a *rooted* holder before truncating a->len: once
195
+ a->len shrinks, a no longer scans the tail, so a raw buffer would leave the
196
+ tail's GC strings unrooted across the pushes below (which can collect). The
197
+ root sits at function scope -- not inside the guard -- so it stays live for
198
+ the whole splice. */
199
+ sp_int tail_from=s+len,tail_n=alen-tail_from;
200
+ sp_StrArray*tb=NULL;SP_GC_ROOT(tb);
201
+ if(tail_n>0){tb=sp_StrArray_new();for(sp_int i=0;i<tail_n;i++)sp_StrArray_push(tb,a->data[tail_from+i]);}
202
+ a->len=s;
203
+ for(sp_int i=0;i<srcn;i++)sp_StrArray_push(a,sb[i]);
204
+ if(tb)for(sp_int i=0;i<tb->len;i++)sp_StrArray_push(a,tb->data[i]);
205
+ sp_pl_free(sb);
206
+ }
207
+ /* poly-array splice: like the typed forms but elements are boxed, so a nil gap
208
+ (start past the end) is filled with nil rather than raising. Ruby's `a[s,l]=`
209
+ splices the RHS's elements when it is an Array, else inserts it as one
210
+ element -- a runtime fact decided here from src's class id. */
211
+ void sp_PolyArray_splice(sp_PolyArray*a,sp_int start,sp_int len,sp_RbVal src){
212
+ if(!a)return;
213
+ if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_POLY_ARRAY);return;}
214
+ SP_GC_ROOT(a);
215
+ /* src rooted too: the pushes below can trigger a collection while some of
216
+ the snapshotted elements are reachable only through src. */
217
+ SP_GC_ROOT_RBVAL(src);
218
+ sp_int alen=a->len,s=start;
219
+ if(s<0)s+=alen;
220
+ if(len<0){sp_raise_cls("IndexError",sp_sprintf("negative length (%lld)",(long long)len));return;}
221
+ if(s<0){sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)start,(long long)-alen));return;}
222
+ /* Equal-length replacement inside the array is a pure overwrite, as in the
223
+ typed splices above: no snapshot buffer, no tail copy, no pushes. It was
224
+ ~190x the typed arrays' cost on `colors[x, 8] = chunk` (#4841). One
225
+ barrier covers the stores, as sp_PolyArray_set takes one; a poly source
226
+ may be this very array, hence memmove. */
227
+ if(len>0&&s+len<=alen&&src.tag==SP_TAG_OBJ&&src.v.p){
228
+ sp_RbVal*dst=a->data+s;
229
+ switch(src.cls_id){
230
+ case SP_BUILTIN_POLY_ARRAY:{sp_PolyArray*x=(sp_PolyArray*)src.v.p;if(x->len==len){sp_gc_wb((void*)a);memmove(dst,x->data,sizeof(sp_RbVal)*(size_t)len);return;}break;}
231
+ case SP_BUILTIN_INT_ARRAY:{sp_IntArray*x=(sp_IntArray*)src.v.p;if(x->len==len){sp_gc_wb((void*)a);for(sp_int i=0;i<len;i++)dst[i]=sp_box_int_or_nil(x->data[x->start+i]);return;}break;}
232
+ case SP_BUILTIN_FLT_ARRAY:{sp_FloatArray*x=(sp_FloatArray*)src.v.p;if(x->len==len){sp_gc_wb((void*)a);for(sp_int i=0;i<len;i++)dst[i]=sp_box_float_or_nil(x->data[i]);return;}break;}
233
+ case SP_BUILTIN_STR_ARRAY:{sp_StrArray*x=(sp_StrArray*)src.v.p;if(x->len==len){sp_gc_wb((void*)a);for(sp_int i=0;i<len;i++)dst[i]=sp_box_str(x->data[i]);return;}break;}
234
+ default:break;
235
+ }
236
+ }
237
+ /* snapshot the source elements as boxed values (an Integer or Float
238
+ array's sentinel is its nil element, so it boxes as nil, here and in the
239
+ overwrite above). src's class id decides array-vs-single-element (Ruby
240
+ splices an Array RHS, inserts anything else). A user object with to_ary
241
+ is coerced at COMPILE time when its static type is known; a to_ary
242
+ object reaching here as a runtime poly value still inserts as one
243
+ element -- closing that would need a codegen-installed dispatch hook
244
+ (the sp_obj_cmp_hook pattern). */
245
+ int src_is_array=src.tag==SP_TAG_OBJ&&(src.cls_id==SP_BUILTIN_INT_ARRAY||src.cls_id==SP_BUILTIN_FLT_ARRAY||src.cls_id==SP_BUILTIN_STR_ARRAY||src.cls_id==SP_BUILTIN_POLY_ARRAY||src.cls_id==SP_BUILTIN_PTR_ARRAY);
246
+ sp_int srcn=0;sp_RbVal*sb=NULL;
247
+ if(src_is_array){
248
+ void*p=src.v.p;
249
+ switch(src.cls_id){
250
+ case SP_BUILTIN_INT_ARRAY:{sp_IntArray*x=(sp_IntArray*)p;srcn=x->len;if(srcn>0){sb=(sp_RbVal*)sp_pl_alloc(sizeof(sp_RbVal)*(size_t)srcn);if(!sb)sp_oom_die();for(sp_int i=0;i<srcn;i++)sb[i]=sp_box_int_or_nil(x->data[x->start+i]);}break;}
251
+ case SP_BUILTIN_FLT_ARRAY:{sp_FloatArray*x=(sp_FloatArray*)p;srcn=x->len;if(srcn>0){sb=(sp_RbVal*)sp_pl_alloc(sizeof(sp_RbVal)*(size_t)srcn);if(!sb)sp_oom_die();for(sp_int i=0;i<srcn;i++)sb[i]=sp_box_float_or_nil(x->data[i]);}break;}
252
+ case SP_BUILTIN_STR_ARRAY:{sp_StrArray*x=(sp_StrArray*)p;srcn=x->len;if(srcn>0){sb=(sp_RbVal*)sp_pl_alloc(sizeof(sp_RbVal)*(size_t)srcn);if(!sb)sp_oom_die();for(sp_int i=0;i<srcn;i++)sb[i]=sp_box_str(x->data[i]);}break;}
253
+ case SP_BUILTIN_POLY_ARRAY:{sp_PolyArray*x=(sp_PolyArray*)p;srcn=x->len;if(srcn>0){sb=(sp_RbVal*)sp_pl_alloc(sizeof(sp_RbVal)*(size_t)srcn);if(!sb)sp_oom_die();memcpy(sb,x->data,sizeof(sp_RbVal)*(size_t)srcn);}break;}
254
+ case SP_BUILTIN_PTR_ARRAY:{sp_PtrArray*x=(sp_PtrArray*)p;srcn=x->len;if(srcn>0){sb=(sp_RbVal*)sp_pl_alloc(sizeof(sp_RbVal)*(size_t)srcn);if(!sb)sp_oom_die();for(sp_int i=0;i<srcn;i++)sb[i]=sp_PtrArray_elem_box(x,x->data[i]);}break;} /* rows or objects (#4486) */
255
+ default:break;
256
+ }
257
+ }
258
+ else{
259
+ srcn=1;sb=(sp_RbVal*)sp_pl_alloc(sizeof(sp_RbVal));if(!sb)sp_oom_die();sb[0]=src;
260
+ }
261
+ /* clamp/gap: when start is past the end, the [s+len) tail is empty and the
262
+ [alen,s) gap fills with nil */
263
+ sp_int gap=0,tail_from,tail_n;
264
+ if(s<=alen){if(s+len>alen)len=alen-s;tail_from=s+len;tail_n=alen-tail_from;}
265
+ else{gap=s-alen;tail_from=alen;tail_n=0;}
266
+ /* Snapshot the tail into a *rooted* holder before truncating a->len: the tail
267
+ elements belong to a and, once a->len shrinks, are reachable through neither
268
+ a nor src, so a raw buffer would leave them unrooted across the pushes below
269
+ (which can collect). The source snapshot sb needs no such holder -- its
270
+ elements stay reachable through the already-rooted src. */
271
+ sp_PolyArray*tb=NULL;SP_GC_ROOT(tb);
272
+ if(tail_n>0){tb=sp_PolyArray_new();for(sp_int i=0;i<tail_n;i++)sp_PolyArray_push(tb,a->data[tail_from+i]);}
273
+ a->len=(s<=alen)?s:alen;
274
+ for(sp_int i=0;i<gap;i++)sp_PolyArray_push(a,sp_box_nil());
275
+ for(sp_int i=0;i<srcn;i++)sp_PolyArray_push(a,sb[i]);
276
+ if(tb)for(sp_int i=0;i<tb->len;i++)sp_PolyArray_push(a,tb->data[i]);
277
+ sp_pl_free(sb);
278
+ }
279
+ void sp_IntArray_reverse_bang(sp_IntArray*a){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}for(sp_int i=0,j=a->len-1;i<j;i++,j--){sp_int t=a->data[a->start+i];a->data[a->start+i]=a->data[a->start+j];a->data[a->start+j]=t;}}
280
+ /* Array#rotate!: move the first n elements to the end. Two block moves --
281
+ save the smaller side, slide the rest, write the saved side back -- rather
282
+ than the triple-reverse's ~1.5n element swaps. The saved side is at most
283
+ half the array, so a small stack buffer covers the sizes that matter (a
284
+ 16-element PPU pixel row is rotated once per tile fetch). */
285
+ void sp_IntArray_rotate_bang(sp_IntArray*a,sp_int n){SP_GC_ROOT(a);
286
+ if(!a)return;
287
+ if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}
288
+ if(a->len<=0)return;
289
+ /* already in range is the usual count (a window rotated by part of its
290
+ length): two divisions per call otherwise, on every tile */
291
+ if(n<0||n>=a->len)n=((n%a->len)+a->len)%a->len;
292
+ if(n==0)return;
293
+ /* Rotating left by n is "the first n elements move to the end", and the
294
+ window already carries its own start offset (the same one push_grow slides
295
+ home). So it is one copy of n words and an index bump, where the in-place
296
+ form below costs a copy of n, a move of len-n, and a copy back -- three
297
+ times the traffic for optcarrot's 16-word pixel window rotated by eight,
298
+ twice per tile. Headroom past the window is what pays for it: grow once to
299
+ hold a few rotations, and slide home when it runs out, which amortizes to
300
+ well under the old cost. */
301
+ if(a->start+a->len+n>a->cap && a->len+n<=a->cap && a->start>0){
302
+ memmove(a->data,a->data+a->start,sizeof(sp_int)*(size_t)a->len);
303
+ a->start=0;
304
+ }
305
+ /* Only grow for a window small enough that the headroom is cheap and the
306
+ rotation is plausibly repeated (a pixel window, a ring of recent values).
307
+ A one-shot rotate of a large array would pay a doubled buffer for a copy
308
+ it never repeats, so that keeps the in-place form. */
309
+ if(a->start+a->len+n>a->cap && a->len+n>a->cap && a->len<=256){
310
+ sp_int nc=a->len*2+n;
311
+ sp_int*nd=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)nc);
312
+ if(nd){
313
+ memcpy(nd,a->data+a->start,sizeof(sp_int)*(size_t)a->len);
314
+ sp_pl_free(a->data); a->data=nd; a->start=0; a->cap=nc;
315
+ }
316
+ }
317
+ if(a->start+a->len+n<=a->cap){
318
+ memcpy(a->data+a->start+a->len,a->data+a->start,sizeof(sp_int)*(size_t)n);
319
+ a->start+=n;
320
+ return;
321
+ }
322
+ sp_int*d=a->data+a->start;
323
+ sp_int rest=a->len-n;
324
+ sp_int keep=n<rest?n:rest; /* the side we buffer */
325
+ sp_int stackbuf[32];
326
+ sp_int*t=keep<=32?stackbuf:(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)keep);
327
+ if(!t)sp_oom_die();
328
+ if(n<=rest){ /* buffer the head, slide the tail down */
329
+ memcpy(t,d,sizeof(sp_int)*(size_t)n);
330
+ memmove(d,d+n,sizeof(sp_int)*(size_t)rest);
331
+ memcpy(d+rest,t,sizeof(sp_int)*(size_t)n);
332
+ }
333
+ else{ /* buffer the tail, slide the head up */
334
+ memcpy(t,d+n,sizeof(sp_int)*(size_t)rest);
335
+ memmove(d+rest,d,sizeof(sp_int)*(size_t)n);
336
+ memcpy(d,t,sizeof(sp_int)*(size_t)rest);
337
+ }
338
+ if(t!=stackbuf)sp_pl_free(t);
339
+ }
340
+ static int _sp_int_cmp(const void*a,const void*b){sp_int va=*(const sp_int*)a,vb=*(const sp_int*)b;return(va>vb)-(va<vb);}
341
+ sp_IntArray*sp_IntArray_sort(sp_IntArray*a){SP_GC_ROOT(a);sp_IntArray*b=sp_IntArray_dup(a);qsort(b->data+b->start,b->len,sizeof(sp_int),_sp_int_cmp);return b;}
342
+ void sp_IntArray_sort_bang(sp_IntArray*a){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}qsort(a->data+a->start,a->len,sizeof(sp_int),_sp_int_cmp);}
343
+ /* The Array set operations (uniq, uniq!, &, |, -, intersect?) asked
344
+ include? of a growing or a foreign array once per element: quadratic, so
345
+ `uniq` of 100,000 Integers made billions of compares where CRuby, which
346
+ hashes past 16 elements, makes 100,000 probes. Past SP_SETOP_LINEAR
347
+ elements they now probe an open-addressed set; below it the scan, cheaper
348
+ than building one, stays. The answers do not change: the first occurrence
349
+ is kept, in the receiver's order, by the same equality as before. The table
350
+ is malloc'd and holds nothing the collector must see -- Integers are
351
+ values, and a String in it is also in an array the operation roots. */
352
+ #define SP_SETOP_LINEAR 16
353
+ typedef struct { sp_int *k; unsigned char *used; size_t mask; } sp_iset;
354
+ static void sp_iset_init(sp_iset *t, sp_int n) {
355
+ size_t cap = 32;
356
+ while (cap < (size_t)n * 2) cap <<= 1;
357
+ t->k = (sp_int *)malloc(cap * sizeof(sp_int));
358
+ t->used = (unsigned char *)calloc(cap, 1);
359
+ if (!t->k || !t->used) sp_oom_die();
360
+ t->mask = cap - 1;
361
+ }
362
+ static size_t sp_iset_slot(const sp_iset *t, sp_int v) {
363
+ size_t i = (size_t)(((uint64_t)v * 11400714819323198485ULL) >> 32) & t->mask;
364
+ while (t->used[i] && t->k[i] != v) i = (i + 1) & t->mask;
365
+ return i;
366
+ }
367
+ /* 1 when v was not in the set yet (it is now) */
368
+ static int sp_iset_add(sp_iset *t, sp_int v) {
369
+ size_t i = sp_iset_slot(t, v);
370
+ if (t->used[i]) return 0;
371
+ t->used[i] = 1;
372
+ t->k[i] = v;
373
+ return 1;
374
+ }
375
+ static int sp_iset_has(const sp_iset *t, sp_int v) { return t->used[sp_iset_slot(t, v)]; }
376
+ static void sp_iset_free(sp_iset *t) { free(t->k); free(t->used); }
377
+ static void sp_iset_add_all(sp_iset *t, sp_IntArray *a) {
378
+ for (sp_int i = 0; i < a->len; i++) sp_iset_add(t, a->data[a->start + i]);
379
+ }
380
+ /* whether a set over one side beats scanning it once per element of the other */
381
+ static int sp_setop_hashes(sp_int scanned, sp_int probes) {
382
+ return scanned > SP_SETOP_LINEAR && probes > SP_SETOP_LINEAR;
383
+ }
384
+ void sp_IntArray_uniq_bang(sp_IntArray*a){SP_GC_ROOT(a);if(!a||a->frozen){if(a&&a->frozen)sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}
385
+ sp_int *d = a->data + a->start, w = 0;
386
+ if (a->len <= SP_SETOP_LINEAR) {
387
+ for (sp_int i = 0; i < a->len; i++) {
388
+ int dup = 0;
389
+ for (sp_int j = 0; j < w && !dup; j++) dup = d[j] == d[i];
390
+ if (!dup) d[w++] = d[i];
391
+ }
392
+ }
393
+ else {
394
+ sp_iset t; sp_iset_init(&t, a->len);
395
+ for (sp_int i = 0; i < a->len; i++) if (sp_iset_add(&t, d[i])) d[w++] = d[i];
396
+ sp_iset_free(&t);
397
+ }
398
+ a->len = w;
399
+ }
400
+ void sp_IntArray_shuffle_bang(sp_IntArray*a){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}for(sp_int i=a->len-1;i>0;i--){sp_int j=sp_krand_below(i+1);sp_int t=a->data[a->start+i];a->data[a->start+i]=a->data[a->start+j];a->data[a->start+j]=t;}}
401
+ sp_IntArray*sp_IntArray_shuffle(sp_IntArray*a){SP_GC_ROOT(a);sp_IntArray*b=sp_IntArray_dup(a);sp_IntArray_shuffle_bang(b);return b;}
402
+ /* Array#sample. CRuby returns nil for `[].sample`: the typed slot's nil
403
+ sentinel, as pop and shift answer (#4288), so the empty draw reads back
404
+ nil and not a 0 nothing could tell from a real element. The Float and
405
+ String twins answer sp_float_nil() and NULL the same way. sp_krand_below
406
+ guards the len==0 draw itself. Issue #536. */
407
+ sp_int sp_IntArray_sample(sp_IntArray*a){SP_GC_ROOT(a);if(a->len<=0)return SP_INT_NIL;return a->data[a->start+sp_krand_below(a->len)];}
408
+ /* Issue #745/#832: empty min/max return SP_INT_NIL (caller treats as
409
+ int?); without the guard, the first read is uninitialized memory. */
410
+ sp_int sp_IntArray_min(sp_IntArray*a){if(!a||a->len<=0)return SP_INT_NIL;sp_int m=a->data[a->start];for(sp_int i=1;i<a->len;i++)if(a->data[a->start+i]<m)m=a->data[a->start+i];return m;}
411
+ sp_int sp_IntArray_max(sp_IntArray*a){if(!a||a->len<=0)return SP_INT_NIL;sp_int m=a->data[a->start];for(sp_int i=1;i<a->len;i++)if(a->data[a->start+i]>m)m=a->data[a->start+i];return m;}
412
+ sp_int sp_IntArray_sum(sp_IntArray*a,sp_int init){sp_int s=init;for(sp_int i=0;i<a->len;i++)s+=a->data[a->start+i];return s;}
413
+ sp_bool sp_IntArray_include(sp_IntArray*a,sp_int v){if(!a)return FALSE;for(sp_int i=0;i<a->len;i++)if(a->data[a->start+i]==v)return TRUE;return FALSE;}
414
+ sp_int sp_IntArray_index(sp_IntArray*a,sp_int v){for(sp_int i=0;i<a->len;i++)if(a->data[a->start+i]==v)return i;return -1;}
415
+ sp_int sp_IntArray_rindex(sp_IntArray*a,sp_int v){for(sp_int i=a->len-1;i>=0;i--)if(a->data[a->start+i]==v)return i;return -1;}
416
+ sp_int sp_IntArray_delete_at(sp_IntArray*a,sp_int i){SP_GC_ROOT(a);if(a&&a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return SP_INT_NIL;}if(i<0)i+=a->len;if(i<0||i>=a->len)return SP_INT_NIL;sp_int v=a->data[a->start+i];for(sp_int j=i;j<a->len-1;j++)a->data[a->start+j]=a->data[a->start+j+1];a->len--;return v;}
417
+ sp_int sp_IntArray_delete(sp_IntArray*a,sp_int v){SP_GC_ROOT(a);if(a&&a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return 0;}sp_int w=0;for(sp_int i=0;i<a->len;i++){if(a->data[a->start+i]!=v){a->data[a->start+w]=a->data[a->start+i];w++;}}sp_int d=a->len-w;a->len=w;if(v==SP_INT_NIL)SP_MAY_NIL(a)=0;/* no nil is left */return d>0?v:SP_INT_NIL;} /* CRuby: nil when absent */
418
+ /* Issue #788: clamp i so a very-negative index doesn't underflow past
419
+ a->start and write into the array's GC header. */
420
+ void sp_IntArray_insert(sp_IntArray*a,sp_int i,sp_int v){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}sp_int orig=i;if(i<0)i+=a->len+1;if(i<0)sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)orig,(long long)(-(a->len+1))));if(i>a->len)SP_MAY_NIL(a)=1;while(i>a->len)sp_IntArray_push(a,SP_INT_NIL);/* CRuby pads with nils past the end */sp_IntArray_push(a,0);for(sp_int j=a->len-1;j>i;j--)a->data[a->start+j]=a->data[a->start+j-1];a->data[a->start+i]=v;}
421
+ sp_IntArray*sp_IntArray_uniq(sp_IntArray*a){SP_GC_ROOT(a);sp_IntArray*b=sp_IntArray_new();
422
+ if (a->len > SP_SETOP_LINEAR) {
423
+ sp_iset t; sp_iset_init(&t, a->len);
424
+ for (sp_int i = 0; i < a->len; i++) { sp_int v = a->data[a->start + i]; if (sp_iset_add(&t, v)) sp_IntArray_push(b, v); }
425
+ sp_iset_free(&t);
426
+ }
427
+ else for(sp_int i=0;i<a->len;i++){int found=0;for(sp_int j=0;j<b->len;j++){if(b->data[b->start+j]==a->data[a->start+i]){found=1;break;}}if(!found)sp_IntArray_push(b,a->data[a->start+i]);}
428
+ SP_MAY_NIL(b)=SP_MAY_NIL(a);return b;}
429
+ sp_IntArray*sp_IntArray_intersect(sp_IntArray*a,sp_IntArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_IntArray*r=sp_IntArray_new();if(!a||!b)return r;
430
+ if (sp_setop_hashes(b->len, a->len)) {
431
+ sp_iset in_b, seen; sp_iset_init(&in_b, b->len); sp_iset_init(&seen, a->len);
432
+ sp_iset_add_all(&in_b, b);
433
+ for (sp_int i = 0; i < a->len; i++) { sp_int v = a->data[a->start + i]; if (sp_iset_has(&in_b, v) && sp_iset_add(&seen, v)) sp_IntArray_push(r, v); }
434
+ sp_iset_free(&in_b); sp_iset_free(&seen);
435
+ }
436
+ else for(sp_int i=0;i<a->len;i++){sp_int v=a->data[a->start+i];if(sp_IntArray_include(b,v)&&!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}
437
+ SP_MAY_NIL(r)=SP_MAY_NIL(a)||SP_MAY_NIL(b);return r;}
438
+ sp_bool sp_IntArray_intersect_p(sp_IntArray*a,sp_IntArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);if(!a||!b)return 0;
439
+ if (sp_setop_hashes(b->len, a->len)) {
440
+ sp_iset in_b; sp_iset_init(&in_b, b->len); sp_iset_add_all(&in_b, b);
441
+ sp_bool any = 0;
442
+ for (sp_int i = 0; i < a->len && !any; i++) any = sp_iset_has(&in_b, a->data[a->start + i]);
443
+ sp_iset_free(&in_b);
444
+ return any;
445
+ }
446
+ for(sp_int i=0;i<a->len;i++)if(sp_IntArray_include(b,a->data[a->start+i]))return 1;return 0;}
447
+ sp_IntArray*sp_IntArray_union(sp_IntArray*a,sp_IntArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_IntArray*r=sp_IntArray_new();
448
+ sp_int n = (a ? a->len : 0) + (b ? b->len : 0);
449
+ if (n > SP_SETOP_LINEAR) {
450
+ sp_iset seen; sp_iset_init(&seen, n);
451
+ if (a) for (sp_int i = 0; i < a->len; i++) { sp_int v = a->data[a->start + i]; if (sp_iset_add(&seen, v)) sp_IntArray_push(r, v); }
452
+ if (b) for (sp_int i = 0; i < b->len; i++) { sp_int v = b->data[b->start + i]; if (sp_iset_add(&seen, v)) sp_IntArray_push(r, v); }
453
+ sp_iset_free(&seen);
454
+ }
455
+ else{if(a)for(sp_int i=0;i<a->len;i++){sp_int v=a->data[a->start+i];if(!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}if(b){for(sp_int i=0;i<b->len;i++){sp_int v=b->data[b->start+i];if(!sp_IntArray_include(r,v))sp_IntArray_push(r,v);}}}
456
+ SP_MAY_NIL(r)=(a&&SP_MAY_NIL(a))||(b&&SP_MAY_NIL(b));return r;}
457
+ /* Array#- / Array#difference: keep every LHS element NOT in RHS,
458
+ preserving the LHS's duplicates. `[1,1,2,3] - [3]` is `[1,1,2]`. */
459
+ sp_IntArray*sp_IntArray_difference(sp_IntArray*a,sp_IntArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_IntArray*r=sp_IntArray_new();if(!a)return r;
460
+ if (b && sp_setop_hashes(b->len, a->len)) {
461
+ sp_iset in_b; sp_iset_init(&in_b, b->len); sp_iset_add_all(&in_b, b);
462
+ for (sp_int i = 0; i < a->len; i++) { sp_int v = a->data[a->start + i]; if (!sp_iset_has(&in_b, v)) sp_IntArray_push(r, v); }
463
+ sp_iset_free(&in_b);
464
+ }
465
+ else for(sp_int i=0;i<a->len;i++){sp_int v=a->data[a->start+i];if(!sp_IntArray_include(b,v))sp_IntArray_push(r,v);}
466
+ SP_MAY_NIL(r)=SP_MAY_NIL(a);return r;}
467
+ void sp_IntArray_unshift(sp_IntArray*a,sp_int v){SP_GC_ROOT(a);if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY);return;}if(a->start>0){a->start--;a->data[a->start]=v;a->len++;}
468
+ else{sp_int e=a->len+1;if(e>a->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)a-sizeof(sp_gc_hdr));sp_gc_bytes_sub(sizeof(sp_int)*a->cap);h->size-=sizeof(sp_int)*a->cap;a->cap=(((((a->cap*2)))))+1;a->data=(sp_int*)sp_pl_realloc(a->data,sizeof(sp_int)*a->cap);h->size+=sizeof(sp_int)*a->cap;sp_gc_bytes_add(sizeof(sp_int)*a->cap);}memmove(a->data+1,a->data,sizeof(sp_int)*a->len);a->data[0]=v;a->len++;}}
469
+ /* A nil element (the slot's sentinel) joins as nil.to_s, the empty string,
470
+ the way the Array's inspect already renders it as nil. */
471
+ const char*sp_IntArray_join(sp_IntArray*a,const char*sep){if(!sep)sep="";size_t sl=sp_str_byte_len(sep),cap=256;char*buf=(char*)sp_pl_alloc(cap);size_t len=0;for(sp_int i=0;i<a->len;i++){if(i>0){if(len+sl>=cap){cap*=2;buf=(char*)sp_pl_realloc(buf,cap);}memcpy(buf+len,sep,sl);len+=sl;}char tmp[32];sp_int v=a->data[a->start+i];int n=v==SP_INT_NIL?0:snprintf(tmp,32,"%lld",(long long)v);if(len+n>=cap){cap*=2;buf=(char*)sp_pl_realloc(buf,cap);}memcpy(buf+len,tmp,n);len+=n;}buf[len]=0;char*r=sp_str_alloc(len);memcpy(r,buf,len);sp_str_set_len(r,len);sp_pl_free(buf);return r;}
472
+ sp_bool sp_IntArray_eq(sp_IntArray*a,sp_IntArray*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(sp_int i=0;i<a->len;i++)if(a->data[a->start+i]!=b->data[b->start+i])return FALSE;return TRUE;}
473
+ /* Array#<=> for IntArray. Lexicographic: per-element compare, shorter
474
+ array sorts before longer if all common elements match
475
+ (`[1,2] <=> [1,2,3] == -1`). NULL recv compares equal to NULL, lower
476
+ than any non-NULL. */
477
+ sp_int sp_IntArray_cmp(sp_IntArray*a,sp_IntArray*b){if(!a||!b)return a==b?0:(a?1:-1);sp_int n=a->len<b->len?a->len:b->len;for(sp_int i=0;i<n;i++){sp_int av=a->data[a->start+i],bv=b->data[b->start+i];if(av<bv)return -1;if(av>bv)return 1;}if(a->len<b->len)return -1;if(a->len>b->len)return 1;return 0;}
478
+
479
+ /* ============================ sp_FloatArray ============================ */
480
+ void sp_FloatArray_unshift(sp_FloatArray*a,sp_float v){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY);return;}sp_FloatArray_push(a,0.0);if(a->len>1)memmove(&a->data[1],&a->data[0],(size_t)(a->len-1)*sizeof(sp_float));a->data[0]=v;}
481
+ /* CRuby's ruby_float_step_size: how many values beg.step(end, unit) yields,
482
+ floored with an epsilon so float drift never drops or adds one. NaN
483
+ anywhere answers NaN, which a `i < n` walk treats as none. */
484
+ sp_float sp_float_step_size(sp_float beg,sp_float end,sp_float unit,sp_int excl){
485
+ if(isinf(unit))return unit>0?beg<=end:beg>=end;
486
+ sp_float n=(end-beg)/unit;
487
+ sp_float err=(fabs(beg)+fabs(end)+fabs(end-beg))/fabs(unit)*DBL_EPSILON;
488
+ if(err>0.5)err=0.5;
489
+ if(excl){
490
+ if(n<=0)return 0;
491
+ n=(n<1)?0:floor(n-err);
492
+ sp_float d=((n+1)*unit)+beg;
493
+ if(beg<end){if(d<end)n++;}
494
+ else if(beg>end){if(d>end)n++;}
495
+ }
496
+ else{
497
+ if(n<0)return 0;
498
+ n=floor(n+err);
499
+ sp_float d=((n+1)*unit)+beg;
500
+ if(beg<end){if(d<=end)n++;}
501
+ else if(beg>end){if(d>=end)n++;}
502
+ }
503
+ return n+1;
504
+ }
505
+ /* (beg..end).step(step) / Float#step materialised as a FloatArray, following
506
+ CRuby's ruby_float_step value for value. step==0 raises ArgumentError like
507
+ CRuby. */
508
+ sp_FloatArray*sp_FloatArray_from_step(sp_float beg,sp_float end,sp_float step,sp_int excl){
509
+ if(step==0.0)sp_raise_cls("ArgumentError","step can't be 0");
510
+ /* CRuby rejects a NaN range bound at construction with this message. */
511
+ if(isnan(beg)||isnan(end))sp_raise_cls("ArgumentError","bad value for range");
512
+ sp_float n=sp_float_step_size(beg,end,step,excl);
513
+ /* An infinite end (or an enormous span) drives n to infinity or past what can
514
+ be materialised; CRuby loops forever there. Raise rather than exhaust
515
+ memory. The 1<<30 cap matches sp_IntArray_from_range's sanity bound. */
516
+ if(n>=(sp_float)(1LL<<30))sp_raise_cls("RangeError","range too large to materialize");
517
+ sp_FloatArray*a=sp_FloatArray_new();
518
+ for(sp_int i=0;i<n;i++)sp_FloatArray_push(a,sp_float_step_at(beg,end,step,i));
519
+ return a;
520
+ }
521
+ sp_float sp_FloatArray_min(sp_FloatArray*a){if(!a||a->len==0)return sp_float_nil(); /* CRuby: nil on empty (#4288) */ sp_float m=a->data[0];for(sp_int i=1;i<a->len;i++)if(a->data[i]<m)m=a->data[i];return m;}
522
+ sp_float sp_FloatArray_max(sp_FloatArray*a){if(!a||a->len==0)return sp_float_nil(); /* CRuby: nil on empty (#4288) */ sp_float m=a->data[0];for(sp_int i=1;i<a->len;i++)if(a->data[i]>m)m=a->data[i];return m;}
523
+ /* Array#sum for floats: Kahan-Babuska-Neumaier compensated summation,
524
+ matching CRuby (float addition isn't associative, so a naive fold of
525
+ [0.1,0.2,0.3] yields 0.6000000000000001 instead of 0.6). The step, with
526
+ CRuby's NaN/Infinity arms, is sp_float_sum_step in sp_array.h -- shared with
527
+ the boxed fold so the two cannot drift. */
528
+ sp_float sp_FloatArray_sum(sp_FloatArray*a,sp_float init){sp_float s=init,c=0.0;for(sp_int i=0;i<a->len;i++)sp_float_sum_step(&s,&c,a->data[i]);return s+c;}
529
+ void sp_FloatArray_replace(sp_FloatArray*dst,sp_FloatArray*src){if(dst==src)return;if(dst->frozen){sp_raise_frozen_array_at(dst,SP_BUILTIN_FLT_ARRAY);return;}dst->len=0;if(src->len>dst->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)dst-sizeof(sp_gc_hdr));sp_gc_bytes_sub(sizeof(sp_float)*dst->cap);h->size-=sizeof(sp_float)*dst->cap;void*nd=sp_pl_realloc(dst->data,sizeof(sp_float)*src->len);if(!nd){perror("realloc");exit(1);}dst->data=(sp_float*)nd;dst->cap=src->len;h->size+=sizeof(sp_float)*dst->cap;sp_gc_bytes_add(sizeof(sp_float)*dst->cap);}memcpy(dst->data,src->data,sizeof(sp_float)*src->len);dst->len=src->len;SP_MAY_NIL(dst)=SP_MAY_NIL(src);}
530
+ /* a[start, len] / a[start..end] for FloatArray. Same negative-start and
531
+ length-clamping semantics as sp_IntArray_slice. */
532
+ sp_FloatArray*sp_FloatArray_slice(sp_FloatArray*a,sp_int start,sp_int len){SP_GC_ROOT(a);if(start<0)start+=a->len;if(start<0)start=0;sp_FloatArray*b=sp_FloatArray_new();if(start>=a->len||len<=0)return b;if(len>a->len-start)len=a->len-start;if(len>b->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)b-sizeof(sp_gc_hdr));sp_gc_bytes_sub(sizeof(sp_float)*b->cap);h->size-=sizeof(sp_float)*b->cap;b->cap=len;b->data=(sp_float*)sp_pl_realloc(b->data,sizeof(sp_float)*b->cap);h->size+=sizeof(sp_float)*b->cap;sp_gc_bytes_add(sizeof(sp_float)*b->cap);}memcpy(b->data,a->data+start,sizeof(sp_float)*len);b->len=len;SP_MAY_NIL(b)=SP_MAY_NIL(a);return b;}
533
+ sp_FloatArray*sp_FloatArray_slice_range(sp_FloatArray*a,sp_int start,sp_int end_,sp_int excl){SP_GC_ROOT(a);if(end_<0)end_+=a->len;if(start<0)start+=a->len;if(start<0||start>a->len)return NULL;/* a start outside [-len, len] is nil (#4524); start == len is [] */sp_int n=end_-start+(excl?0:1);if(n<0)n=0;return sp_FloatArray_slice(a,start,n);}
534
+ void sp_FloatArray_reverse_bang(sp_FloatArray*a){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY);return;}for(sp_int i=0,j=a->len-1;i<j;i++,j--){sp_float t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
535
+ void sp_FloatArray_rotate_bang(sp_FloatArray*a,sp_int n){SP_GC_ROOT(a);
536
+ if(!a)return;
537
+ if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY);return;}
538
+ if(a->len<=0)return;
539
+ n=((n%a->len)+a->len)%a->len;
540
+ if(n==0)return;
541
+ sp_float*d=a->data;
542
+ sp_int rest=a->len-n;
543
+ sp_int keep=n<rest?n:rest;
544
+ sp_float stackbuf[32];
545
+ sp_float*t=keep<=32?stackbuf:(sp_float*)sp_pl_alloc(sizeof(sp_float)*(size_t)keep);
546
+ if(!t)sp_oom_die();
547
+ if(n<=rest){
548
+ memcpy(t,d,sizeof(sp_float)*(size_t)n);
549
+ memmove(d,d+n,sizeof(sp_float)*(size_t)rest);
550
+ memcpy(d+rest,t,sizeof(sp_float)*(size_t)n);
551
+ }
552
+ else{
553
+ memcpy(t,d+n,sizeof(sp_float)*(size_t)rest);
554
+ memmove(d+rest,d,sizeof(sp_float)*(size_t)n);
555
+ memcpy(d,t,sizeof(sp_float)*(size_t)rest);
556
+ }
557
+ if(t!=stackbuf)sp_pl_free(t);
558
+ }
559
+ static int _sp_float_cmp(const void*a,const void*b){sp_float va=*(const sp_float*)a,vb=*(const sp_float*)b;return(va>vb)-(va<vb);}
560
+ void sp_FloatArray_sort_bang(sp_FloatArray*a){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY);return;}qsort(a->data,a->len,sizeof(sp_float),_sp_float_cmp);}
561
+ void sp_FloatArray_shuffle_bang(sp_FloatArray*a){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY);return;}for(sp_int i=a->len-1;i>0;i--){sp_int j=sp_krand_below(i+1);sp_float t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
562
+ sp_FloatArray*sp_FloatArray_dup(sp_FloatArray*a){SP_GC_ROOT(a);sp_FloatArray*b=sp_FloatArray_new();sp_FloatArray_replace(b,a);return b;}
563
+ sp_FloatArray*sp_FloatArray_sort(sp_FloatArray*a){SP_GC_ROOT(a);sp_FloatArray*b=sp_FloatArray_dup(a);sp_FloatArray_sort_bang(b);return b;}
564
+ sp_FloatArray*sp_FloatArray_shuffle(sp_FloatArray*a){SP_GC_ROOT(a);sp_FloatArray*r=sp_FloatArray_new();sp_FloatArray_replace(r,a);sp_FloatArray_shuffle_bang(r);return r;}
565
+ sp_float sp_FloatArray_sample(sp_FloatArray*a){SP_GC_ROOT(a);if(a->len<=0)return sp_float_nil();return a->data[sp_krand_below(a->len)];}
566
+ /* IEEE 754 == on sp_float: NaN never matches; +0.0 == -0.0 (diverges from Float#eql?). */
567
+ /* a NaN is not == to itself, but CRuby's identity fallback still finds the
568
+ very same NaN in a container (#3650) */
569
+ sp_bool sp_FloatArray_include(sp_FloatArray*a,sp_float v){if(!a)return FALSE;for(sp_int i=0;i<a->len;i++){if(a->data[i]==v)return TRUE;if(v!=v&&a->data[i]!=a->data[i]&&memcmp(&a->data[i],&v,sizeof v)==0)return TRUE;}return FALSE;}
570
+ /* index/rindex/delete match the way sp_FloatArray_include just above does:
571
+ == first, then the bit-pattern fallback so the Float::NAN constant finds
572
+ itself (a computed NaN with the same bits over-matches identically).
573
+ delete answers the deleted ELEMENT, last match, as CRuby does -- -0.0
574
+ shows the difference -- and nil (an e never reassigned) when absent. */
575
+ sp_int sp_FloatArray_index(sp_FloatArray*a,sp_float v){if(!a)return -1;for(sp_int i=0;i<a->len;i++){sp_float x=a->data[i];if(x==v||(v!=v&&x!=x&&memcmp(&x,&v,sizeof v)==0))return i;}return -1;}
576
+ sp_int sp_FloatArray_rindex(sp_FloatArray*a,sp_float v){if(!a)return -1;for(sp_int i=a->len-1;i>=0;i--){sp_float x=a->data[i];if(x==v||(v!=v&&x!=x&&memcmp(&x,&v,sizeof v)==0))return i;}return -1;}
577
+ sp_float sp_FloatArray_delete(sp_FloatArray*a,sp_float v){SP_GC_ROOT(a);if(!a)return sp_float_nil();if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY);return sp_float_nil();}sp_int w=0;sp_float e=sp_float_nil();for(sp_int i=0;i<a->len;i++){sp_float x=a->data[i];if(x==v||(v!=v&&x!=x&&memcmp(&x,&v,sizeof v)==0))e=x;else{a->data[w]=x;w++;}}a->len=w;if(sp_float_is_nil(v))SP_MAY_NIL(a)=0;return e;}
578
+ sp_FloatArray*sp_FloatArray_intersect(sp_FloatArray*a,sp_FloatArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_FloatArray*r=sp_FloatArray_new();if(!a||!b)return r;for(sp_int i=0;i<a->len;i++){sp_float v=a->data[i];if(sp_FloatArray_include(b,v)&&!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}SP_MAY_NIL(r)=SP_MAY_NIL(a)||SP_MAY_NIL(b);return r;}
579
+ sp_bool sp_FloatArray_intersect_p(sp_FloatArray*a,sp_FloatArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);if(!a||!b)return 0;for(sp_int i=0;i<a->len;i++)if(sp_FloatArray_include(b,a->data[i]))return 1;return 0;}
580
+ sp_FloatArray*sp_FloatArray_union(sp_FloatArray*a,sp_FloatArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_FloatArray*r=sp_FloatArray_new();if(a)for(sp_int i=0;i<a->len;i++){sp_float v=a->data[i];if(!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}if(b){for(sp_int i=0;i<b->len;i++){sp_float v=b->data[i];if(!sp_FloatArray_include(r,v))sp_FloatArray_push(r,v);}}SP_MAY_NIL(r)=(a&&SP_MAY_NIL(a))||(b&&SP_MAY_NIL(b));return r;}
581
+ sp_FloatArray*sp_FloatArray_difference(sp_FloatArray*a,sp_FloatArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_FloatArray*r=sp_FloatArray_new();for(sp_int i=0;i<a->len;i++){sp_float v=a->data[i];if(!sp_FloatArray_include(b,v))sp_FloatArray_push(r,v);}SP_MAY_NIL(r)=SP_MAY_NIL(a);return r;}
582
+ /* insert(i, v) on a Float array, as sp_IntArray_insert does on an Integer
583
+ one: a negative index counts from past the end, and the gap before an index
584
+ past the end is nil, the slot's sentinel. */
585
+ void sp_FloatArray_insert(sp_FloatArray *a, sp_int i, sp_float v) {
586
+ SP_GC_ROOT(a);
587
+ if (!a) return;
588
+ if (a->frozen) { sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY); return; }
589
+ sp_int orig = i;
590
+ if (i < 0) i += a->len + 1;
591
+ if (i < 0)
592
+ sp_raise_cls("IndexError", sp_sprintf("index %lld too small for array; minimum: %lld",
593
+ (long long)orig, (long long)(-(a->len + 1))));
594
+ if (i > a->len) SP_MAY_NIL(a) = 1;
595
+ while (i > a->len) sp_FloatArray_push(a, sp_float_nil());
596
+ sp_FloatArray_splice(a, i, 0, &v, 1);
597
+ }
598
+ /* uniq! in place, matching elements the way sp_FloatArray_uniq does */
599
+ void sp_FloatArray_uniq_bang(sp_FloatArray *a) {
600
+ SP_GC_ROOT(a);
601
+ if (!a) return;
602
+ if (a->frozen) { sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY); return; }
603
+ sp_int w = 0;
604
+ for (sp_int i = 0; i < a->len; i++) {
605
+ sp_float v = a->data[i];
606
+ int dup = 0;
607
+ for (sp_int j = 0; j < w && !dup; j++)
608
+ dup = a->data[j] == v || (v != v && a->data[j] != a->data[j] && memcmp(&a->data[j], &v, sizeof v) == 0);
609
+ if (!dup) a->data[w++] = v;
610
+ }
611
+ a->len = w;
612
+ }
613
+ sp_FloatArray*sp_FloatArray_uniq(sp_FloatArray*a){SP_GC_ROOT(a);sp_FloatArray*b=sp_FloatArray_new();if(!a)return b;for(sp_int i=0;i<a->len;i++){sp_float v=sp_FloatArray_get(a,i);if(!sp_FloatArray_include(b,v))sp_FloatArray_push(b,v);}SP_MAY_NIL(b)=SP_MAY_NIL(a);return b;}
614
+
615
+ /* ============================= sp_PtrArray ============================ */
616
+ /* `Array#delete_at(i)` -- remove and return the element at index i.
617
+ Negative indices count from the end; NULL when out of range. */
618
+ void*sp_PtrArray_delete_at(sp_PtrArray*a,sp_int i){ sp_gc_wb((void*)a);if(i<0)i+=a->len;if(i<0||i>=a->len)return NULL;void*v=a->data[i];for(sp_int j=i;j<a->len-1;j++)a->data[j]=a->data[j+1];a->len--;return v;}
619
+ void sp_PtrArray_reverse_bang(sp_PtrArray*a){ sp_gc_wb((void*)a);for(sp_int i=0,j=a->len-1;i<j;i++,j--){void*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
620
+ void sp_PtrArray_rotate_bang(sp_PtrArray*a,sp_int n){
621
+ if(!a)return;
622
+ if(a->len<=0)return;
623
+ n=((n%a->len)+a->len)%a->len;
624
+ if(n==0)return;
625
+ void**d=a->data;
626
+ sp_int rest=a->len-n;
627
+ sp_int keep=n<rest?n:rest;
628
+ void* stackbuf[32];
629
+ void**t=keep<=32?stackbuf:(void**)sp_pl_alloc(sizeof(void*)*(size_t)keep);
630
+ if(!t)sp_oom_die();
631
+ if(n<=rest){
632
+ memcpy(t,d,sizeof(void*)*(size_t)n);
633
+ memmove(d,d+n,sizeof(void*)*(size_t)rest);
634
+ memcpy(d+rest,t,sizeof(void*)*(size_t)n);
635
+ }
636
+ else{
637
+ memcpy(t,d+n,sizeof(void*)*(size_t)rest);
638
+ memmove(d+rest,d,sizeof(void*)*(size_t)n);
639
+ memcpy(d,t,sizeof(void*)*(size_t)rest);
640
+ }
641
+ if(t!=stackbuf)sp_pl_free(t);
642
+ }
643
+ sp_PtrArray*sp_PtrArray_dup(sp_PtrArray*a){SP_GC_ROOT(a);sp_PtrArray*b=sp_PtrArray_new_scan(a->scan_elem);for(sp_int i=0;i<a->len;i++)sp_PtrArray_push(b,a->data[i]);return b;}
644
+ sp_PtrArray*sp_PtrArray_slice(sp_PtrArray*a,sp_int start,sp_int len){SP_GC_ROOT(a);if(start<0)start+=a->len;if(start<0)start=0;sp_PtrArray*b=sp_PtrArray_new_scan(a->scan_elem);if(start>=a->len||len<=0)return b;if(len>a->len-start)len=a->len-start;for(sp_int i=0;i<len;i++)sp_PtrArray_push(b,a->data[start+i]);return b;}
645
+ void sp_PtrArray_shuffle_bang(sp_PtrArray*a){SP_GC_ROOT(a); sp_gc_wb((void*)a);for(sp_int i=a->len-1;i>0;i--){sp_int j=sp_krand_below(i+1);void*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
646
+ sp_PtrArray*sp_PtrArray_shuffle(sp_PtrArray*a){SP_GC_ROOT(a);sp_PtrArray*b=sp_PtrArray_dup(a);sp_PtrArray_shuffle_bang(b);return b;}
647
+ void *sp_PtrArray_sample(sp_PtrArray*a){SP_GC_ROOT(a);if(a->len<=0)return NULL;return a->data[sp_krand_below(a->len)];}
648
+
649
+ /* ============================= sp_StrArray ============================ */
650
+ void sp_StrArray_replace(sp_StrArray*dst,sp_StrArray*src){if(dst==src)return;if(dst->frozen){sp_raise_frozen_array_at(dst,SP_BUILTIN_STR_ARRAY);return;} sp_gc_wb((void*)dst);dst->len=0;if(src->len>dst->cap){sp_gc_hdr*h=(sp_gc_hdr*)((char*)dst-sizeof(sp_gc_hdr));void*nd;if(dst->data==dst->inline_data){nd=sp_pl_alloc(sizeof(const char*)*src->len);if(!nd){perror("malloc");exit(1);}}
651
+ else{sp_gc_bytes_sub(sizeof(const char*)*dst->cap);h->size-=sizeof(const char*)*dst->cap;nd=sp_pl_realloc(dst->data,sizeof(const char*)*src->len);if(!nd){perror("realloc");exit(1);}}dst->data=(const char**)nd;dst->cap=src->len;h->size+=sizeof(const char*)*dst->cap;sp_gc_bytes_add(sizeof(const char*)*dst->cap);}memcpy(dst->data,src->data,sizeof(const char*)*src->len);dst->len=src->len;}
652
+ const char*sp_StrArray_pop(sp_StrArray*a){SP_GC_ROOT(a);if(!a||a->len<=0)return NULL;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return NULL;}return a->data[--a->len];}
653
+ const char*sp_StrArray_shift(sp_StrArray*a){SP_GC_ROOT(a);if(!a||a->len<=0)return NULL;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return NULL;}const char*v=a->data[0];memmove(a->data,a->data+1,(size_t)(--a->len)*sizeof(const char*));return v;}
654
+ /* a[start, len] / a[start..end] for StrArray. Same negative-start and
655
+ length-clamping semantics as sp_IntArray_slice. */
656
+ sp_StrArray*sp_StrArray_slice(sp_StrArray*a,sp_int start,sp_int len){SP_GC_ROOT(a);if(start<0)start+=a->len;if(start<0)start=0;sp_StrArray*b=sp_StrArray_new();if(start>=a->len||len<=0)return b;if(len>a->len-start)len=a->len-start;for(sp_int i=0;i<len;i++)sp_StrArray_push(b,a->data[start+i]);return b;}
657
+ sp_StrArray*sp_StrArray_slice_range(sp_StrArray*a,sp_int start,sp_int end_,sp_int excl){SP_GC_ROOT(a);if(end_<0)end_+=a->len;if(start<0)start+=a->len;if(start<0||start>a->len)return NULL;/* a start outside [-len, len] is nil (#4524); start == len is [] */sp_int n=end_-start+(excl?0:1);if(n<0)n=0;return sp_StrArray_slice(a,start,n);}
658
+ void sp_StrArray_reverse_bang(sp_StrArray*a){SP_GC_ROOT(a); sp_gc_wb((void*)a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return;}for(sp_int i=0,j=a->len-1;i<j;i++,j--){const char*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
659
+ void sp_StrArray_rotate_bang(sp_StrArray*a,sp_int n){SP_GC_ROOT(a);
660
+ if(!a)return;
661
+ if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return;}
662
+ if(a->len<=0)return;
663
+ n=((n%a->len)+a->len)%a->len;
664
+ if(n==0)return;
665
+ const char**d=a->data;
666
+ sp_int rest=a->len-n;
667
+ sp_int keep=n<rest?n:rest;
668
+ const char* stackbuf[32];
669
+ const char**t=keep<=32?stackbuf:(const char**)sp_pl_alloc(sizeof(const char*)*(size_t)keep);
670
+ if(!t)sp_oom_die();
671
+ if(n<=rest){
672
+ memcpy(t,d,sizeof(const char*)*(size_t)n);
673
+ memmove(d,d+n,sizeof(const char*)*(size_t)rest);
674
+ memcpy(d+rest,t,sizeof(const char*)*(size_t)n);
675
+ }
676
+ else{
677
+ memcpy(t,d+n,sizeof(const char*)*(size_t)rest);
678
+ memmove(d+rest,d,sizeof(const char*)*(size_t)n);
679
+ memcpy(d,t,sizeof(const char*)*(size_t)rest);
680
+ }
681
+ if(t!=stackbuf)sp_pl_free(t);
682
+ }
683
+ static int _sp_str_cmp(const void*a,const void*b){return sp_str_cmp_bytes(*(const char*const*)a,*(const char*const*)b);}
684
+ void sp_StrArray_sort_bang(sp_StrArray*a){SP_GC_ROOT(a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return;}qsort(a->data,a->len,sizeof(const char*),_sp_str_cmp);}
685
+ /* The String side of the set operations' table (see sp_iset). `incl` picks
686
+ the equality the operation always used: include?'s String equality
687
+ (sp_str_eq: &, |, -, intersect?), or uniq's byte comparison. Under both,
688
+ nil equals only nil, and equal Strings have equal bytes, so they meet in
689
+ one hash chain. */
690
+ typedef struct { const char **k; unsigned char *used; size_t mask; int incl; } sp_sset;
691
+ static void sp_sset_init(sp_sset *t, sp_int n, int incl) {
692
+ size_t cap = 32;
693
+ while (cap < (size_t)n * 2) cap <<= 1;
694
+ t->k = (const char **)malloc(cap * sizeof(const char *));
695
+ t->used = (unsigned char *)calloc(cap, 1);
696
+ if (!t->k || !t->used) sp_oom_die();
697
+ t->mask = cap - 1;
698
+ t->incl = incl;
699
+ }
700
+ static int sp_sset_eq(const sp_sset *t, const char *x, const char *y) {
701
+ if (t->incl) return sp_str_eq(x, y);
702
+ return x == y || (x && y && !sp_str_cmp_bytes(x, y));
703
+ }
704
+ static size_t sp_sset_slot(const sp_sset *t, const char *v) {
705
+ uint64_t h = sp_str_hash(v ? v : sp_str_empty);
706
+ size_t i = (size_t)(h ^ (h >> 29)) & t->mask;
707
+ while (t->used[i] && !sp_sset_eq(t, t->k[i], v)) i = (i + 1) & t->mask;
708
+ return i;
709
+ }
710
+ static int sp_sset_add(sp_sset *t, const char *v) {
711
+ size_t i = sp_sset_slot(t, v);
712
+ if (t->used[i]) return 0;
713
+ t->used[i] = 1;
714
+ t->k[i] = v;
715
+ return 1;
716
+ }
717
+ static int sp_sset_has(const sp_sset *t, const char *v) { return t->used[sp_sset_slot(t, v)]; }
718
+ static void sp_sset_free(sp_sset *t) { free(t->k); free(t->used); }
719
+ static void sp_sset_add_all(sp_sset *t, sp_StrArray *a) {
720
+ for (sp_int i = 0; i < a->len; i++) sp_sset_add(t, a->data[i]);
721
+ }
722
+ void sp_StrArray_uniq_bang(sp_StrArray*a){SP_GC_ROOT(a); sp_gc_wb((void*)a);if(!a||a->frozen){if(a&&a->frozen)sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return;}
723
+ const char **d = a->data;
724
+ sp_int w = 0;
725
+ if (a->len <= SP_SETOP_LINEAR) {
726
+ for (sp_int i = 0; i < a->len; i++) {
727
+ int dup = 0;
728
+ for (sp_int j = 0; j < w && !dup; j++) dup = d[j] == d[i] || (d[j] && d[i] && !sp_str_cmp_bytes(d[j], d[i]));
729
+ if (!dup) d[w++] = d[i];
730
+ }
731
+ }
732
+ else {
733
+ sp_sset t; sp_sset_init(&t, a->len, 0);
734
+ for (sp_int i = 0; i < a->len; i++) if (sp_sset_add(&t, d[i])) d[w++] = d[i];
735
+ sp_sset_free(&t);
736
+ }
737
+ a->len = w;
738
+ }
739
+ sp_StrArray*sp_StrArray_uniq(sp_StrArray*a){SP_GC_ROOT(a);sp_StrArray*b=sp_StrArray_new();if(!a)return b;
740
+ if (a->len > SP_SETOP_LINEAR) {
741
+ sp_sset t; sp_sset_init(&t, a->len, 0);
742
+ for (sp_int i = 0; i < a->len; i++) if (sp_sset_add(&t, a->data[i])) sp_StrArray_push(b, a->data[i]);
743
+ sp_sset_free(&t);
744
+ return b;
745
+ }
746
+ for(sp_int i=0;i<a->len;i++){int found=0;for(sp_int j=0;j<b->len;j++){if(b->data[j]==a->data[i]||(b->data[j]&&a->data[i]&&!sp_str_cmp_bytes(b->data[j],a->data[i]))){found=1;break;}}if(!found)sp_StrArray_push(b,a->data[i]);}return b;}
747
+ /* byte_len for both the separator and each element: an embedded NUL is a byte
748
+ of the string, and strlen stopped at it (the opportunistic NUL policy). */
749
+ const char*sp_StrArray_join(sp_StrArray*a,const char*sep){if(!sep)sep="";if(!a)return sp_str_empty;size_t sl=sp_str_byte_len(sep),cap=256;char*buf=(char*)sp_pl_alloc(cap);size_t len=0;for(sp_int i=0;i<a->len;i++){if(i>0){if(len+sl>=cap){cap*=2;buf=(char*)sp_pl_realloc(buf,cap);}memcpy(buf+len,sep,sl);len+=sl;}const char*_e=a->data[i]?a->data[i]:"";size_t el=sp_str_byte_len(_e);if(len+el>=cap){cap=((len+el)*2)+1;buf=(char*)sp_pl_realloc(buf,cap);}memcpy(buf+len,_e,el);len+=el;}buf[len]=0;char*r=sp_str_alloc(len);memcpy(r,buf,len);sp_str_set_len(r,len);sp_pl_free(buf);/* the joined encoding, part by part as CRuby picks it (sp_str_enc_step) */{int st=-1;size_t off=0;int sb=sp_str_is_binary(sep);for(sp_int i=0;i<a->len;i++){if(i>0){st=sp_str_enc_step(st,r,off,sep,sl,sb);off+=sl;}const char*_e=a->data[i]?a->data[i]:"";size_t el=sp_str_byte_len(_e);st=sp_str_enc_step(st,r,off,_e,el,a->data[i]&&sp_str_is_binary(_e));off+=el;}if(st==1)sp_str_mark_binary(r);}return r;}
750
+ sp_bool sp_StrArray_include(sp_StrArray*a,const char*v){SP_GC_ROOT(a);SP_GC_ROOT_STR(v);if(!a)return FALSE;for(sp_int i=0;i<a->len;i++)if(sp_str_eq(a->data[i],v))return TRUE;return FALSE;}
751
+ sp_StrArray*sp_StrArray_intersect(sp_StrArray*a,sp_StrArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_StrArray*r=sp_StrArray_new();if(!a||!b)return r;
752
+ if (sp_setop_hashes(b->len, a->len)) {
753
+ sp_sset in_b, seen; sp_sset_init(&in_b, b->len, 1); sp_sset_init(&seen, a->len, 1);
754
+ sp_sset_add_all(&in_b, b);
755
+ for (sp_int i = 0; i < a->len; i++) { const char *v = a->data[i]; if (sp_sset_has(&in_b, v) && sp_sset_add(&seen, v)) sp_StrArray_push(r, v); }
756
+ sp_sset_free(&in_b); sp_sset_free(&seen);
757
+ return r;
758
+ }
759
+ for(sp_int i=0;i<a->len;i++){const char*v=a->data[i];if(sp_StrArray_include(b,v)&&!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}return r;}
760
+ sp_bool sp_StrArray_intersect_p(sp_StrArray*a,sp_StrArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);if(!a||!b)return 0;
761
+ if (sp_setop_hashes(b->len, a->len)) {
762
+ sp_sset in_b; sp_sset_init(&in_b, b->len, 1); sp_sset_add_all(&in_b, b);
763
+ sp_bool any = 0;
764
+ for (sp_int i = 0; i < a->len && !any; i++) any = sp_sset_has(&in_b, a->data[i]);
765
+ sp_sset_free(&in_b);
766
+ return any;
767
+ }
768
+ for(sp_int i=0;i<a->len;i++)if(sp_StrArray_include(b,a->data[i]))return 1;return 0;}
769
+ sp_StrArray*sp_StrArray_union(sp_StrArray*a,sp_StrArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_StrArray*r=sp_StrArray_new();
770
+ sp_int n = (a ? a->len : 0) + (b ? b->len : 0);
771
+ if (n > SP_SETOP_LINEAR) {
772
+ sp_sset seen; sp_sset_init(&seen, n, 1);
773
+ if (a) for (sp_int i = 0; i < a->len; i++) if (sp_sset_add(&seen, a->data[i])) sp_StrArray_push(r, a->data[i]);
774
+ if (b) for (sp_int i = 0; i < b->len; i++) if (sp_sset_add(&seen, b->data[i])) sp_StrArray_push(r, b->data[i]);
775
+ sp_sset_free(&seen);
776
+ return r;
777
+ }
778
+ if(a)for(sp_int i=0;i<a->len;i++){const char*v=a->data[i];if(!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}if(b){for(sp_int i=0;i<b->len;i++){const char*v=b->data[i];if(!sp_StrArray_include(r,v))sp_StrArray_push(r,v);}}return r;}
779
+ sp_StrArray*sp_StrArray_difference(sp_StrArray*a,sp_StrArray*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_StrArray*r=sp_StrArray_new();
780
+ if (b && sp_setop_hashes(b->len, a->len)) {
781
+ sp_sset in_b; sp_sset_init(&in_b, b->len, 1); sp_sset_add_all(&in_b, b);
782
+ for (sp_int i = 0; i < a->len; i++) if (!sp_sset_has(&in_b, a->data[i])) sp_StrArray_push(r, a->data[i]);
783
+ sp_sset_free(&in_b);
784
+ return r;
785
+ }
786
+ for(sp_int i=0;i<a->len;i++){const char*v=a->data[i];if(!sp_StrArray_include(b,v))sp_StrArray_push(r,v);}return r;}
787
+ /* min/max by String#<=> (byte comparison via strcmp); NULL (nil) when empty.
788
+ nil (NULL) elements are skipped so a holey/sparse array can't crash strcmp. */
789
+ const char*sp_StrArray_min(sp_StrArray*a){if(!a||a->len<=0)return NULL;const char*m=NULL;for(sp_int i=0;i<a->len;i++){const char*x=a->data[i];if(x&&(!m||sp_str_cmp_bytes(x,m)<0))m=x;}return m;}
790
+ const char*sp_StrArray_max(sp_StrArray*a){if(!a||a->len<=0)return NULL;const char*m=NULL;for(sp_int i=0;i<a->len;i++){const char*x=a->data[i];if(x&&(!m||sp_str_cmp_bytes(x,m)>0))m=x;}return m;}
791
+ sp_int sp_StrArray_index(sp_StrArray*a,const char*v){SP_GC_ROOT(a);SP_GC_ROOT_STR(v);for(sp_int i=0;i<a->len;i++)if(sp_str_eq(a->data[i],v))return i;return -1;}
792
+ sp_int sp_StrArray_rindex(sp_StrArray*a,const char*v){SP_GC_ROOT(a);SP_GC_ROOT_STR(v);for(sp_int i=a->len-1;i>=0;i--)if(sp_str_eq(a->data[i],v))return i;return -1;}
793
+ sp_StrArray*sp_StrArray_compact(sp_StrArray*a){SP_GC_ROOT(a);sp_StrArray*r=sp_StrArray_new();if(!a)return r;for(sp_int i=0;i<a->len;i++)if(a->data[i]!=NULL)sp_StrArray_push(r,a->data[i]);return r;}
794
+ /* A scalar array cannot nest, but it can hold the nil a nullable read left
795
+ behind (the SP_INT_NIL / NaN sentinel), so compact has to filter. */
796
+ sp_IntArray*sp_IntArray_compact(sp_IntArray*a){SP_GC_ROOT(a);sp_IntArray*r=sp_IntArray_new();if(!a)return r;for(sp_int i=0;i<a->len;i++){sp_int v=a->data[a->start+i];if(v!=SP_INT_NIL)sp_IntArray_push(r,v);}return r;}
797
+ sp_FloatArray*sp_FloatArray_compact(sp_FloatArray*a){SP_GC_ROOT(a);sp_FloatArray*r=sp_FloatArray_new();if(!a)return r;for(sp_int i=0;i<a->len;i++){sp_float v=a->data[i];if(!sp_float_is_nil(v))sp_FloatArray_push(r,v);}return r;}
798
+ /* compact! on one of those: the nils go in place, and the answer is whether
799
+ any did (CRuby's self, or nil for a no-op). */
800
+ sp_bool sp_IntArray_compact_bang(sp_IntArray*a){SP_GC_ROOT(a);if(!a)return FALSE;if(a->frozen){sp_raise_frozen_array_at(a,SP_BUILTIN_INT_ARRAY);return FALSE;}sp_int w=0;for(sp_int i=0;i<a->len;i++){sp_int v=a->data[a->start+i];if(v!=SP_INT_NIL)a->data[a->start+w++]=v;}sp_bool ch=w!=a->len;a->len=w;SP_MAY_NIL(a)=0;return ch;}
801
+ sp_bool sp_FloatArray_compact_bang(sp_FloatArray*a){SP_GC_ROOT(a);if(!a)return FALSE;if(a->frozen){sp_raise_frozen_array_at(a,SP_BUILTIN_FLT_ARRAY);return FALSE;}sp_int w=0;for(sp_int i=0;i<a->len;i++){sp_float v=a->data[i];if(!sp_float_is_nil(v))a->data[w++]=v;}sp_bool ch=w!=a->len;a->len=w;SP_MAY_NIL(a)=0;return ch;}
802
+ /* min, max, minmax and sort compare the elements pairwise, and nil compares
803
+ with nothing but nil, so CRuby raises once one meets a number; an array of
804
+ nils alone sorts and answers nil. The message names the pair the way a
805
+ left-to-right scan meets it, which is Array#min's and #max's order (sort
806
+ and the counted min(n)/max(n) may meet the pair the other way round).
807
+ Emitted ahead of a receiver analyze marked as able to hold the sentinel
808
+ (#3505), and reached through the nil_*_if_flagged wrappers in sp_array.h
809
+ for any other whose may_nil is set; each answers the array it was handed. */
810
+ sp_IntArray*sp_IntArray_nil_cmp_ck(sp_IntArray*a){if(!a)return a;sp_int ni=-1,vi=-1;for(sp_int i=0;i<a->len&&(ni<0||vi<0);i++){if(a->data[a->start+i]==SP_INT_NIL){if(ni<0)ni=i;}else if(vi<0)vi=i;}if(ni<0||vi<0)return a;if(ni<vi)sp_raise_cls("ArgumentError",sp_sprintf("comparison of NilClass with %lld failed",(long long)a->data[a->start+vi]));sp_raise_cls("ArgumentError","comparison of Integer with nil failed");return a;}
811
+ sp_FloatArray*sp_FloatArray_nil_cmp_ck(sp_FloatArray*a){if(!a)return a;sp_int ni=-1,vi=-1;for(sp_int i=0;i<a->len&&(ni<0||vi<0);i++){if(sp_float_is_nil(a->data[i])){if(ni<0)ni=i;}else if(vi<0)vi=i;}if(ni<0||vi<0)return a;if(ni<vi)sp_raise_cls("ArgumentError",sp_sprintf("comparison of NilClass with %s failed",sp_float_to_s(a->data[vi])));sp_raise_cls("ArgumentError","comparison of Float with nil failed");return a;}
812
+ /* sum adds each element to the running total, and a nil is no addend: the
813
+ TypeError names the total's class when the nil arrives -- the seed's before
814
+ the first element (`float_seed`: a Float one), then the array's own, or
815
+ Float once a Float seed has made it one. */
816
+ /* A typed array just converted from a boxed one: a nil element became the
817
+ sentinel, which sets may_nil. One pass after the conversion, out of line,
818
+ so the converting loops stay the size they were. */
819
+ void sp_IntArray_note_nils(sp_IntArray*a){if(!a)return;for(sp_int i=0;i<a->len;i++)if(a->data[a->start+i]==SP_INT_NIL){SP_MAY_NIL(a)=1;return;}}
820
+ void sp_FloatArray_note_nils(sp_FloatArray*a){if(!a)return;for(sp_int i=0;i<a->len;i++)if(sp_float_is_nil(a->data[i])){SP_MAY_NIL(a)=1;return;}}
821
+ /* all? / any? / none? / one? of an array that may hold nil: its non-nil
822
+ elements (sp_*Array_truthy_count, the length where none can be nil) */
823
+ sp_int sp_IntArray_truthy_scan(sp_IntArray*a){sp_int n=0;for(sp_int i=0;i<a->len;i++)if(a->data[a->start+i]!=SP_INT_NIL)n++;return n;}
824
+ sp_int sp_FloatArray_truthy_scan(sp_FloatArray*a){sp_int n=0;for(sp_int i=0;i<a->len;i++)if(!sp_float_is_nil(a->data[i]))n++;return n;}
825
+ sp_IntArray*sp_IntArray_nil_sum_ck(sp_IntArray*a,int float_seed){if(!a)return a;for(sp_int i=0;i<a->len;i++)if(a->data[a->start+i]==SP_INT_NIL)sp_raise_cls("TypeError",float_seed?"nil can't be coerced into Float":"nil can't be coerced into Integer");return a;}
826
+ sp_FloatArray*sp_FloatArray_nil_sum_ck(sp_FloatArray*a,int float_seed){if(!a)return a;for(sp_int i=0;i<a->len;i++)if(sp_float_is_nil(a->data[i]))sp_raise_cls("TypeError",(i>0||float_seed)?"nil can't be coerced into Float":"nil can't be coerced into Integer");return a;}
827
+ const char*sp_StrArray_delete_at(sp_StrArray*a,sp_int i){SP_GC_ROOT(a); sp_gc_wb((void*)a);if(!a)return NULL;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return NULL;}if(i<0)i+=a->len;if(i<0||i>=a->len)return NULL;const char*v=a->data[i];for(sp_int j=i;j<a->len-1;j++)a->data[j]=a->data[j+1];a->len--;return v;}
828
+ const char*sp_StrArray_delete(sp_StrArray*a,const char*v){SP_GC_ROOT(a);SP_GC_ROOT_STR(v); sp_gc_wb((void*)a);if(!a)return NULL;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return NULL;}sp_int w=0;const char*found=NULL;for(sp_int i=0;i<a->len;i++){if(!sp_str_eq(a->data[i],v)){a->data[w]=a->data[i];w++;}
829
+ else{found=a->data[i];}}a->len=w;return found;}
830
+ void sp_StrArray_insert(sp_StrArray*a,sp_int i,const char*v){SP_GC_ROOT(a);SP_GC_ROOT_STR(v); sp_gc_wb((void*)a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return;}sp_int orig=i;if(i<0)i+=a->len+1;if(i<0)sp_raise_cls("IndexError",sp_sprintf("index %lld too small for array; minimum: %lld",(long long)orig,(long long)(-(a->len+1))));while(i>a->len)sp_StrArray_push(a,NULL);/* CRuby pads with nils past the end */sp_StrArray_push(a,sp_str_empty);for(sp_int j=a->len-1;j>i;j--)a->data[j]=a->data[j-1];a->data[i]=v;}
831
+ void sp_StrArray_shuffle_bang(sp_StrArray*a){SP_GC_ROOT(a); sp_gc_wb((void*)a);if(!a)return;if(a->frozen){sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY);return;}for(sp_int i=a->len-1;i>0;i--){sp_int j=sp_krand_below(i+1);const char*t=a->data[i];a->data[i]=a->data[j];a->data[j]=t;}}
832
+ sp_StrArray*sp_StrArray_dup(sp_StrArray*a){SP_GC_ROOT(a);sp_StrArray*r=sp_StrArray_new();sp_StrArray_replace(r,a);return r;}
833
+ sp_StrArray*sp_StrArray_sort(sp_StrArray*a){SP_GC_ROOT(a);sp_StrArray*b=sp_StrArray_dup(a);sp_StrArray_sort_bang(b);return b;}
834
+ sp_StrArray*sp_StrArray_shuffle(sp_StrArray*a){SP_GC_ROOT(a);sp_StrArray*r=sp_StrArray_new();sp_StrArray_replace(r,a);sp_StrArray_shuffle_bang(r);return r;}
835
+ const char *sp_StrArray_sample(sp_StrArray*a){SP_GC_ROOT(a);if(a->len<=0)return NULL;return a->data[sp_krand_below(a->len)];}
836
+
837
+ /* ============ poly/inspect-dependent array ops (display, concat, to_poly) ============ */
838
+ /* The members String#upto yields, in CRuby's rb_str_upto_each order of
839
+ cases, each a fresh copy (the frozen begin is not handed out) passed to
840
+ fn until it answers nonzero. */
841
+ void sp_str_upto_each(const char *s, const char *e, sp_int excl, int (*fn)(const char *, void *), void *arg) {SP_GC_ROOT_STR(s);SP_GC_ROOT_STR(e);
842
+ if (!s || !e) return;
843
+ size_t sl = sp_str_byte_len(s), el = sp_str_byte_len(e);
844
+ int ascii = 1;
845
+ for (size_t i = 0; i < sl; i++) if ((unsigned char)s[i] >= 0x80) ascii = 0;
846
+ for (size_t i = 0; i < el; i++) if ((unsigned char)e[i] >= 0x80) ascii = 0;
847
+ /* one ASCII character at each end: every byte between, so ("A".."c")
848
+ holds the punctuation between "Z" and "a" */
849
+ if (ascii && sl == 1 && el == 1) {
850
+ int lim = (unsigned char)e[0] + (excl ? 0 : 1);
851
+ for (int ch = (unsigned char)s[0]; ch < lim; ch++) {
852
+ char one = (char)ch;
853
+ if (fn(sp_str_from_bytes(&one, 1), arg)) return;
854
+ }
855
+ return;
856
+ }
857
+ /* two all-digit ends: the numbers between, zero-padded to the begin's
858
+ width, so ("9".."11") holds "9", "10", "11" (#3549) and ("1".."010")
859
+ stops at "10". Past 18 digits the succ walk below serves. */
860
+ int digits = ascii && sl > 0 && el > 0 && sl <= 18 && el <= 18;
861
+ for (size_t i = 0; digits && i < sl; i++) if (s[i] < '0' || s[i] > '9') digits = 0;
862
+ for (size_t i = 0; digits && i < el; i++) if (e[i] < '0' || e[i] > '9') digits = 0;
863
+ if (digits) {
864
+ long long bi = strtoll(s, NULL, 10), ei = strtoll(e, NULL, 10);
865
+ for (; excl ? bi < ei : bi <= ei; bi++) {
866
+ char buf[32];
867
+ int n = snprintf(buf, sizeof buf, "%.*lld", (int)sl, bi);
868
+ if (fn(sp_str_from_bytes(buf, (size_t)n), arg)) return;
869
+ }
870
+ return;
871
+ }
872
+ /* otherwise String#succ from the begin up to the end, never past the
873
+ end's length, so ("a".."bb") runs through "z" and on to "bb", and
874
+ ("aa".."z") -- whose begin is the end's successor -- is empty */
875
+ int cmp = sp_str_cmp_bytes(s, e);
876
+ if (cmp > 0 || (excl && cmp == 0)) return;
877
+ const char *after = sp_str_succ(e);
878
+ SP_GC_ROOT_STR(after);
879
+ /* `cur` walks the range via String#succ, allocating a fresh heap string each
880
+ step; the next sp_str_alloc can trigger a GC that would sweep both the
881
+ array under construction and the current (unrooted) succ string, so the
882
+ strcpy read freed memory (#3152). Root the slot -- it tracks each succ
883
+ reassignment. */
884
+ const char *cur = s;
885
+ SP_GC_ROOT_STR(cur);
886
+ while (!sp_str_eq(cur, after)) {
887
+ int last = !excl && sp_str_eq(cur, e);
888
+ if (fn(sp_str_from_bytes(cur, sp_str_byte_len(cur)), arg)) return;
889
+ if (last) return;
890
+ cur = sp_str_succ(cur);
891
+ if (excl && sp_str_eq(cur, e)) return;
892
+ size_t cl = sp_str_byte_len(cur);
893
+ if (cl > el || cl == 0) return;
894
+ }
895
+ }
896
+ static int sp_str_upto_push(const char *m, void *arg) {
897
+ sp_StrArray_push((sp_StrArray *)arg, m);
898
+ return 0;
899
+ }
900
+ sp_StrArray *sp_StrArray_from_string_range(const char *s, const char *e, sp_int excl) {SP_GC_ROOT_STR(s);SP_GC_ROOT_STR(e);
901
+ sp_StrArray *a = sp_StrArray_new();
902
+ SP_GC_ROOT(a);
903
+ sp_str_upto_each(s, e, excl, sp_str_upto_push, a);
904
+ return a;
905
+ }
906
+ const char*sp_IntArray_inspect(sp_IntArray*a){SP_GC_ROOT(a);return a?sp_inspect_container(sp_box_obj(a,SP_BUILTIN_INT_ARRAY)):"nil";}
907
+ const char*sp_FloatArray_inspect(sp_FloatArray*a){SP_GC_ROOT(a);return a?sp_inspect_container(sp_box_obj(a,SP_BUILTIN_FLT_ARRAY)):"nil";}
908
+ /* the sentinel joins as nil, as sp_IntArray_join's does */
909
+ const char*sp_FloatArray_join(sp_FloatArray*a,const char*sep){if(!sep)sep="";SP_GC_ROOT(a);size_t sl=sp_str_byte_len(sep),cap=256;char*buf=(char*)sp_pl_alloc(cap);size_t len=0;if(a){for(sp_int i=0;i<a->len;i++){if(i>0){if(len+sl>=cap){cap*=2;buf=(char*)sp_pl_realloc(buf,cap);}memcpy(buf+len,sep,sl);len+=sl;}const char*es=sp_float_opt_to_s(a->data[i]);size_t el=strlen(es);if(len+el>=cap){while(len+el>=cap)cap*=2;buf=(char*)sp_pl_realloc(buf,cap);}memcpy(buf+len,es,el);len+=el;}}buf[len]=0;char*r=sp_str_alloc(len);memcpy(r,buf,len);sp_str_set_len(r,len);sp_pl_free(buf);return r;}
910
+ /* Two nils are ==, though the sentinel is a NaN and NaN != NaN: tested only
911
+ on the pair that already differs, so equal numbers pay nothing. */
912
+ sp_bool sp_FloatArray_eq(sp_FloatArray*a,sp_FloatArray*b){if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(sp_int i=0;i<a->len;i++)if(a->data[i]!=b->data[i]&&!(sp_float_is_nil(a->data[i])&&sp_float_is_nil(b->data[i])))return FALSE;return TRUE;}
913
+ const char*sp_StrArray_inspect(sp_StrArray*a){SP_GC_ROOT(a);return a?sp_inspect_container(sp_box_obj(a,SP_BUILTIN_STR_ARRAY)):"nil";}
914
+ const char*sp_PtrArray_inspect(sp_PtrArray*a){if(!a)return SPL("nil");SP_GC_ROOT(a);sp_String*s=sp_String_new("[");SP_GC_ROOT(s);for(sp_int i=0;i<a->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,"#<Object>");}sp_String_append(s,"]");return sp_str_dup(s->data);}
915
+ /* Array#slice_before(delim): start a new chunk before each element == delim. */
916
+ sp_PtrArray*sp_IntArray_slice_before(sp_IntArray*a,sp_int d){SP_GC_ROOT(a);sp_PtrArray*out=sp_PtrArray_new();SP_GC_ROOT(out);if(!a)return out;sp_IntArray*cur=sp_IntArray_new();SP_GC_ROOT(cur);for(sp_int i=0;i<a->len;i++){sp_int e=a->data[a->start+i];if(e==d&&cur->len>0){sp_PtrArray_push(out,cur);cur=sp_IntArray_new();}SP_MAY_NIL(cur)=SP_MAY_NIL(a);sp_IntArray_push(cur,e);}if(cur->len>0)sp_PtrArray_push(out,cur);return out;}
917
+ /* Array#slice_after(delim): end a chunk after each element == delim. */
918
+ sp_PtrArray*sp_IntArray_slice_after(sp_IntArray*a,sp_int d){SP_GC_ROOT(a);sp_PtrArray*out=sp_PtrArray_new();SP_GC_ROOT(out);if(!a)return out;sp_IntArray*cur=sp_IntArray_new();SP_GC_ROOT(cur);for(sp_int i=0;i<a->len;i++){sp_int e=a->data[a->start+i];SP_MAY_NIL(cur)=SP_MAY_NIL(a);sp_IntArray_push(cur,e);if(e==d){sp_PtrArray_push(out,cur);cur=sp_IntArray_new();}}if(cur->len>0)sp_PtrArray_push(out,cur);return out;}
919
+ sp_PtrArray *sp_IntArray_product(sp_IntArray *a, sp_IntArray *b) {
920
+ SP_GC_ROOT(a); SP_GC_ROOT(b);
921
+ sp_PtrArray *out = sp_PtrArray_new();
922
+ SP_GC_ROOT(out);
923
+ if (!a || !b) return out;
924
+ for (sp_int i = 0; i < a->len; i++) {
925
+ for (sp_int j = 0; j < b->len; j++) {
926
+ sp_IntArray *pair = sp_IntArray_new();
927
+ SP_MAY_NIL(pair) = SP_MAY_NIL(a) || SP_MAY_NIL(b); /* the rows carry the operands' nils */
928
+ sp_IntArray_push(pair, a->data[a->start + i]);
929
+ sp_IntArray_push(pair, b->data[b->start + j]);
930
+ sp_PtrArray_push(out, pair);
931
+ }
932
+ }
933
+ return out;
934
+ }
935
+ const char*sp_PtrArray_str_join(sp_PtrArray*a,const char*sep){SP_GC_ROOT(a);SP_GC_ROOT_STR(sep);sp_int al=a->len;if(al==0)return sp_str_empty;size_t sl=strlen(sep),total=0;for(sp_int i=0;i<al;i++){if(i>0)total+=sl;sp_String*s=(sp_String*)a->data[i];if(s)total+=(size_t)s->len;}char*r=sp_str_alloc(total);size_t cur=0;for(sp_int i=0;i<al;i++){if(i>0){memcpy(r+cur,sep,sl);cur+=sl;}sp_String*s=(sp_String*)a->data[i];if(s&&s->len){memcpy(r+cur,s->data,(size_t)s->len);cur+=(size_t)s->len;}}return r;}
936
+ sp_RbVal sp_IntArray_index_poly(sp_IntArray *a, sp_int v) {SP_GC_ROOT(a); sp_int n = sp_IntArray_index(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
937
+ sp_RbVal sp_IntArray_rindex_poly(sp_IntArray *a, sp_int v) {SP_GC_ROOT(a); sp_int n = sp_IntArray_rindex(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
938
+ sp_RbVal sp_StrArray_index_poly(sp_StrArray *a, const char *v) {SP_GC_ROOT(a);SP_GC_ROOT_STR(v); sp_int n = sp_StrArray_index(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
939
+ sp_RbVal sp_StrArray_rindex_poly(sp_StrArray *a, const char *v) {SP_GC_ROOT(a);SP_GC_ROOT_STR(v); sp_int n = sp_StrArray_rindex(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
940
+ sp_int sp_IntArray_index_opt(sp_IntArray *a, sp_int v) {SP_GC_ROOT(a); sp_int n = sp_IntArray_index(a, v); return n < 0 ? SP_INT_NIL : n; }
941
+ sp_int sp_IntArray_rindex_opt(sp_IntArray *a, sp_int v) {SP_GC_ROOT(a); sp_int n = sp_IntArray_rindex(a, v); return n < 0 ? SP_INT_NIL : n; }
942
+ sp_RbVal sp_FloatArray_index_poly(sp_FloatArray *a, sp_float v) {SP_GC_ROOT(a); sp_int n = sp_FloatArray_index(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
943
+ sp_RbVal sp_FloatArray_rindex_poly(sp_FloatArray *a, sp_float v) {SP_GC_ROOT(a); sp_int n = sp_FloatArray_rindex(a, v); return n < 0 ? sp_box_nil() : sp_box_int(n); }
944
+ /* A boxed needle compares only for a Float or Integer tag; every other tag
945
+ misses, never riding a to-f coercion that would have parsed a String or
946
+ interned a Symbol into a false hit -- and delete into a lost element.
947
+ Bignum, Rational and Complex miss too, where CRuby's == can match: the
948
+ family-wide trade, the Int arm fails the C build on the same shapes. A nil
949
+ is the slot's sentinel, found where the array holds one. */
950
+ sp_RbVal sp_FloatArray_index_key(sp_FloatArray *a, sp_RbVal v) {SP_GC_ROOT(a); if (v.tag == SP_TAG_FLT) return sp_FloatArray_index_poly(a, v.v.f); if (v.tag == SP_TAG_INT) return sp_FloatArray_index_poly(a, (sp_float)v.v.i); if (v.tag == SP_TAG_NIL) return sp_FloatArray_index_poly(a, sp_float_nil()); return sp_box_nil(); }
951
+ sp_RbVal sp_FloatArray_rindex_key(sp_FloatArray *a, sp_RbVal v) {SP_GC_ROOT(a); if (v.tag == SP_TAG_FLT) return sp_FloatArray_rindex_poly(a, v.v.f); if (v.tag == SP_TAG_INT) return sp_FloatArray_rindex_poly(a, (sp_float)v.v.i); if (v.tag == SP_TAG_NIL) return sp_FloatArray_rindex_poly(a, sp_float_nil()); return sp_box_nil(); }
952
+ sp_float sp_FloatArray_delete_key(sp_FloatArray *a, sp_RbVal v) {SP_GC_ROOT(a); if (v.tag == SP_TAG_FLT) return sp_FloatArray_delete(a, v.v.f); if (v.tag == SP_TAG_INT) return sp_FloatArray_delete(a, (sp_float)v.v.i); if (v.tag == SP_TAG_NIL) return sp_FloatArray_delete(a, sp_float_nil()); return sp_float_nil(); }
953
+ const int64_t *sp_IntArray_ffi_data(sp_IntArray *a) { return a ? (const int64_t *)(a->data + a->start) : (const int64_t *)0; }
954
+ const double *sp_FloatArray_ffi_data(sp_FloatArray *a) { return a ? (const double *)a->data : (const double *)0; }
955
+ sp_IntArray *sp_IntArray_concat(sp_IntArray *a, sp_IntArray *b) { SP_GC_ROOT(a); SP_GC_ROOT(b); sp_IntArray *r = sp_IntArray_new(); SP_GC_ROOT(r); if (a) for (sp_int i = 0; i < a->len; i++) sp_IntArray_push(r, sp_IntArray_get(a, i)); if (b) for (sp_int i = 0; i < b->len; i++) sp_IntArray_push(r, sp_IntArray_get(b, i)); sp_IntArray_nil_from(r, a); sp_IntArray_nil_from(r, b); return r; }
956
+ sp_StrArray *sp_StrArray_concat(sp_StrArray *a, sp_StrArray *b) {SP_GC_ROOT(a);SP_GC_ROOT(b); sp_StrArray *r = sp_StrArray_new(); SP_GC_ROOT(r); if (a) for (sp_int i = 0; i < a->len; i++) sp_StrArray_push(r, sp_StrArray_get(a, i)); if (b) for (sp_int i = 0; i < b->len; i++) sp_StrArray_push(r, sp_StrArray_get(b, i)); return r; }
957
+ sp_FloatArray *sp_FloatArray_concat(sp_FloatArray *a, sp_FloatArray *b) {SP_GC_ROOT(a);SP_GC_ROOT(b); sp_FloatArray *r = sp_FloatArray_new(); SP_GC_ROOT(r); if (a) for (sp_int i = 0; i < a->len; i++) sp_FloatArray_push(r, sp_FloatArray_get(a, i)); if (b) for (sp_int i = 0; i < b->len; i++) sp_FloatArray_push(r, sp_FloatArray_get(b, i)); sp_FloatArray_nil_from(r, a); sp_FloatArray_nil_from(r, b); return r; }
958
+ /* A typed array widened whole into a poly one boxes its nil (the slot's
959
+ sentinel) as nil, as sp_PolyArray_from_int_array does: once boxed as a
960
+ number, nothing downstream -- a set operation, ==, a zip row -- can tell.
961
+ The test is paid per converted element, beside an allocation and a push,
962
+ not on the single-element read #3505 keeps plain. */
963
+ sp_PolyArray *sp_IntArray_to_poly(sp_IntArray *a) {
964
+ SP_GC_ROOT(a);
965
+ sp_PolyArray *r = sp_PolyArray_new();
966
+ SP_GC_ROOT(r);
967
+ if (!a) return r;
968
+ for (sp_int i = 0; i < a->len; i++) sp_PolyArray_push(r, sp_box_int_or_nil(a->data[a->start + i]));
969
+ return r;
970
+ }
971
+ sp_PolyArray *sp_StrArray_to_poly_fmt(sp_StrArray *a) {SP_GC_ROOT(a);
972
+ sp_PolyArray *r = sp_PolyArray_new();
973
+ if (!a) return r;
974
+ for (sp_int i = 0; i < a->len; i++) sp_PolyArray_push(r, sp_box_str(a->data[i]));
975
+ return r;
976
+ }
977
+ sp_PolyArray *sp_FloatArray_to_poly(sp_FloatArray *a) {
978
+ SP_GC_ROOT(a);
979
+ sp_PolyArray *r = sp_PolyArray_new();
980
+ SP_GC_ROOT(r);
981
+ if (!a) return r;
982
+ for (sp_int i = 0; i < a->len; i++) sp_PolyArray_push(r, sp_box_float_or_nil(a->data[i]));
983
+ return r;
984
+ }
985
+ sp_IntArray *sp_IntArray_slice_bang(sp_IntArray *a, sp_int from, sp_int n) {SP_GC_ROOT(a);
986
+ if (!a) return sp_IntArray_new();
987
+ if (a->frozen) { sp_raise_frozen_array_at(a, SP_BUILTIN_INT_ARRAY); return sp_IntArray_new(); }
988
+ /* a start past the end (or before it after normalizing) is nil, not an
989
+ empty slice: only start == len answers [] (#3607) */
990
+ if (from < 0) from += a->len;
991
+ if (from < 0 || from > a->len) return NULL;
992
+ if (n < 0) return NULL; /* slice!(start, -1) is nil, not an empty slice */
993
+ if (from + n > a->len) n = a->len - from;
994
+ sp_IntArray *r = sp_IntArray_new();
995
+ for (sp_int i = 0; i < n; i++) sp_IntArray_push(r, a->data[a->start + from + i]);
996
+ SP_MAY_NIL(r) = SP_MAY_NIL(a);
997
+ if (from == 0) {
998
+ a->start += n;
999
+ a->len -= n;
1000
+ }
1001
+ else {
1002
+ for (sp_int i = from; i + n < a->len; i++) a->data[a->start + i] = a->data[a->start + i + n];
1003
+ a->len -= n;
1004
+ }
1005
+ return r;
1006
+ }
1007
+ sp_FloatArray *sp_FloatArray_slice_bang(sp_FloatArray *a, sp_int from, sp_int n) {SP_GC_ROOT(a);
1008
+ if (!a) return sp_FloatArray_new();
1009
+ if (a->frozen) { sp_raise_frozen_array_at(a, SP_BUILTIN_FLT_ARRAY); return sp_FloatArray_new(); }
1010
+ /* a start past the end (or before it after normalizing) is nil, not an
1011
+ empty slice: only start == len answers [] (#3607) */
1012
+ if (from < 0) from += a->len;
1013
+ if (from < 0 || from > a->len) return NULL;
1014
+ if (n < 0) return NULL; /* slice!(start, -1) is nil, not an empty slice */
1015
+ if (from + n > a->len) n = a->len - from;
1016
+ sp_FloatArray *r = sp_FloatArray_new();
1017
+ for (sp_int i = 0; i < n; i++) sp_FloatArray_push(r, a->data[from + i]);
1018
+ SP_MAY_NIL(r) = SP_MAY_NIL(a);
1019
+ for (sp_int i = from; i + n < a->len; i++) a->data[i] = a->data[i + n];
1020
+ a->len -= n;
1021
+ return r;
1022
+ }
1023
+ sp_StrArray *sp_StrArray_slice_bang(sp_StrArray *a, sp_int from, sp_int n) {SP_GC_ROOT(a); sp_gc_wb((void*)a);
1024
+ if (!a) return sp_StrArray_new();
1025
+ if (a->frozen) { sp_raise_frozen_array_at(a, SP_BUILTIN_STR_ARRAY); return sp_StrArray_new(); }
1026
+ /* a start past the end (or before it after normalizing) is nil, not an
1027
+ empty slice: only start == len answers [] (#3607) */
1028
+ if (from < 0) from += a->len;
1029
+ if (from < 0 || from > a->len) return NULL;
1030
+ if (n < 0) return NULL; /* slice!(start, -1) is nil, not an empty slice */
1031
+ if (from + n > a->len) n = a->len - from;
1032
+ sp_StrArray *r = sp_StrArray_new();
1033
+ for (sp_int i = 0; i < n; i++) sp_StrArray_push(r, a->data[from + i]);
1034
+ for (sp_int i = from; i + n < a->len; i++) a->data[i] = a->data[i + n];
1035
+ a->len -= n;
1036
+ return r;
1037
+ }
1038
+ sp_PtrArray *sp_PtrArray_slice_bang(sp_PtrArray *a, sp_int from, sp_int n) {SP_GC_ROOT(a); sp_gc_wb((void*)a);
1039
+ if (!a) return sp_PtrArray_new_scan(NULL);
1040
+ if (a->frozen) { sp_raise_frozen_array_at(a, SP_BUILTIN_PTR_ARRAY); return sp_PtrArray_new_scan(a->scan_elem); }
1041
+ /* a start past the end (or before it after normalizing) is nil, not an
1042
+ empty slice: only start == len answers [] (#3607) */
1043
+ if (from < 0) from += a->len;
1044
+ if (from < 0 || from > a->len) return NULL;
1045
+ if (n < 0) return NULL; /* slice!(start, -1) is nil, not an empty slice */
1046
+ if (from + n > a->len) n = a->len - from;
1047
+ sp_PtrArray *r = sp_PtrArray_new_scan(a->scan_elem);
1048
+ for (sp_int i = 0; i < n; i++) sp_PtrArray_push(r, a->data[from + i]);
1049
+ for (sp_int i = from; i + n < a->len; i++) a->data[i] = a->data[i + n];
1050
+ a->len -= n;
1051
+ return r;
1052
+ }