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,1254 @@
1
+ /*
2
+ * sp_pack.c -- Array#pack / String#unpack for Spinel
3
+ *
4
+ * Implements the common Perl/Ruby pack format specifiers. Built
5
+ * as a separate translation unit and linked into libspinel_rt.a;
6
+ * it allocates GC-managed strings and PolyArrays directly through
7
+ * the shared headers (sp_alloc.h), so no sp_ext_* shim is needed.
8
+ *
9
+ * Supported specifiers (initial):
10
+ * C / c unsigned / signed 8-bit
11
+ * n / N unsigned 16 / 32-bit big-endian
12
+ * v / V unsigned 16 / 32-bit little-endian
13
+ * s / S signed / unsigned 16-bit native
14
+ * l / L signed / unsigned 32-bit native
15
+ * q / Q signed / unsigned 64-bit native
16
+ * a binary string (NUL-padded on pack)
17
+ * A text string (space-padded on pack, space-trimmed on unpack)
18
+ * Z NUL-terminated string
19
+ * x null byte
20
+ *
21
+ * Counts: `<spec>N` packs N items; `<spec>*` consumes all remaining.
22
+ */
23
+
24
+ #include <stdio.h>
25
+ #include <stdlib.h>
26
+ #include <string.h>
27
+ #include <stdint.h>
28
+ #include <math.h>
29
+
30
+ /* The shared runtime types (sp_RbVal, sp_IntArray, sp_sym, sp_int, SP_TAG_*) and
31
+ the string allocator (sp_str_alloc / _set_len / _byte_len / sp_str_empty) come
32
+ straight from the shared headers: this separate TU can now allocate GC strings
33
+ directly onto the one shared heap, so no sp_ext_str_* shim is needed. */
34
+ #include "sp_alloc.h" /* string + object allocation, sp_box_*, sp_PolyArray */
35
+ #include "sp_str.h" /* sp_nil_recv for the nil-receiver unpack raise */
36
+ #include "sp_string.h" /* sp_String: a shared String handle element */
37
+
38
+ /* ---------- Helpers ---------- */
39
+
40
+ static void pk_append(char **buf, size_t *len, size_t *cap, const char *src, size_t n) {
41
+ if (*len + n + 1 > *cap) {
42
+ size_t nc = (*len + n + 1) * 2;
43
+ char *nb = (char *)realloc(*buf, nc);
44
+ if (!nb) { perror("realloc"); exit(1); }
45
+ *buf = nb;
46
+ *cap = nc;
47
+ }
48
+ memcpy(*buf + *len, src, n);
49
+ *len += n;
50
+ }
51
+
52
+ /* Encode codepoint `v` as UTF-8 (the `U` directive) into out[0..5].
53
+ Mirrors CRuby's permissive scheme: up to 6 bytes for values through
54
+ 0x7FFFFFFF. Negative / out-of-range values encode nothing (CRuby
55
+ raises RangeError; spinel has no exception path here). Returns the
56
+ byte count written. */
57
+ static int pk_utf8(unsigned char *out, int64_t v) {
58
+ if (v < 0) return 0;
59
+ uint64_t uv = (uint64_t)v;
60
+ if (uv <= 0x7F) {
61
+ out[0] = (unsigned char)uv;
62
+ return 1;
63
+ }
64
+ if (uv <= 0x7FF) {
65
+ out[0] = (unsigned char)(0xC0 | (uv >> 6));
66
+ out[1] = (unsigned char)(0x80 | (uv & 0x3F));
67
+ return 2;
68
+ }
69
+ if (uv <= 0xFFFF) {
70
+ out[0] = (unsigned char)(0xE0 | (uv >> 12));
71
+ out[1] = (unsigned char)(0x80 | ((uv >> 6) & 0x3F));
72
+ out[2] = (unsigned char)(0x80 | (uv & 0x3F));
73
+ return 3;
74
+ }
75
+ if (uv <= 0x1FFFFF) {
76
+ out[0] = (unsigned char)(0xF0 | (uv >> 18));
77
+ out[1] = (unsigned char)(0x80 | ((uv >> 12) & 0x3F));
78
+ out[2] = (unsigned char)(0x80 | ((uv >> 6) & 0x3F));
79
+ out[3] = (unsigned char)(0x80 | (uv & 0x3F));
80
+ return 4;
81
+ }
82
+ if (uv <= 0x3FFFFFF) {
83
+ out[0] = (unsigned char)(0xF8 | (uv >> 24));
84
+ out[1] = (unsigned char)(0x80 | ((uv >> 18) & 0x3F));
85
+ out[2] = (unsigned char)(0x80 | ((uv >> 12) & 0x3F));
86
+ out[3] = (unsigned char)(0x80 | ((uv >> 6) & 0x3F));
87
+ out[4] = (unsigned char)(0x80 | (uv & 0x3F));
88
+ return 5;
89
+ }
90
+ if (uv <= 0x7FFFFFFF) {
91
+ out[0] = (unsigned char)(0xFC | (uv >> 30));
92
+ out[1] = (unsigned char)(0x80 | ((uv >> 24) & 0x3F));
93
+ out[2] = (unsigned char)(0x80 | ((uv >> 18) & 0x3F));
94
+ out[3] = (unsigned char)(0x80 | ((uv >> 12) & 0x3F));
95
+ out[4] = (unsigned char)(0x80 | ((uv >> 6) & 0x3F));
96
+ out[5] = (unsigned char)(0x80 | (uv & 0x3F));
97
+ return 6;
98
+ }
99
+ return 0;
100
+ }
101
+
102
+ /* Consume any '<' '>' '!' '_' modifiers after a directive letter, then the
103
+ optional count. Without this, `pack('l<l<l<l<')` treated each '<' as its
104
+ own directive -- and since the element cursor advances per directive, every
105
+ other value was silently dropped (doom's SDL_Rect bytes came out as
106
+ {x,w,0,0}). '!'/'_' pick the native size (what the plain directive
107
+ already is here); '<'/'>' set *big. CRuby raises RangeError on a
108
+ repeated endian modifier ("l><"); spinel's pack has no exception
109
+ path, so the last one wins instead. */
110
+ static int64_t pk_parse_count_mods(const char **pp, int *big) {
111
+ const char *p = *pp;
112
+ while (*p == '<' || *p == '>' || *p == '!' || *p == '_') {
113
+ if (big) {
114
+ if (*p == '>') *big = 1;
115
+ else if (*p == '<') *big = 0;
116
+ }
117
+ p++;
118
+ }
119
+ if (*p == '*') { *pp = p + 1; return -1; }
120
+ if (*p < '0' || *p > '9') { *pp = p; return 1; }
121
+ int64_t n = 0;
122
+ while (*p >= '0' && *p <= '9') { n = n * 10 + (*p - '0'); p++; }
123
+ *pp = p;
124
+ return n;
125
+ }
126
+ /* `l_` / `l!` (and `L`) are the native long, 8 bytes on LP64, as CRuby packs
127
+ and unpacks them; the plain `l` stays 32-bit. Answers the directive to use,
128
+ peeking at the modifiers after it without consuming them. */
129
+ static char pk_native_spec(char spec, const char *p) {
130
+ if ((spec == 'l' || spec == 'L') && sizeof(long) == 8) {
131
+ for (const char *q = p; *q == '<' || *q == '>' || *q == '!' || *q == '_'; q++)
132
+ if (*q == '!' || *q == '_') return spec == 'l' ? 'q' : 'Q';
133
+ }
134
+ return spec;
135
+ }
136
+ static int64_t pk_parse_count(const char **pp) {
137
+ return pk_parse_count_mods(pp, NULL);
138
+ }
139
+ /* Serialize / deserialize an integer as `n` bytes, little- or
140
+ big-endian, via shifts (host-endianness independent, like the
141
+ n/N/v/V paths). pk_put_bits takes the raw unsigned pattern so the
142
+ float directives can pass IEEE-754 bits without a narrowing
143
+ uint64 -> int64 conversion (implementation-defined for values with
144
+ the sign bit set). */
145
+ static void pk_put_bits(char *out, uint64_t uv, size_t n, int big) {
146
+ for (size_t i = 0; i < n; i++) {
147
+ size_t sh = big ? (n - 1 - i) : i;
148
+ out[i] = (char)((uv >> (8 * sh)) & 0xff);
149
+ }
150
+ }
151
+ static void pk_put_int(char *out, int64_t v, size_t n, int big) {
152
+ pk_put_bits(out, (uint64_t)v, n, big);
153
+ }
154
+ static uint64_t pk_get_int(const unsigned char *u, size_t n, int big) {
155
+ uint64_t v = 0;
156
+ for (size_t i = 0; i < n; i++) {
157
+ size_t sh = big ? (n - 1 - i) : i;
158
+ v |= (uint64_t)u[i] << (8 * sh);
159
+ }
160
+ return v;
161
+ }
162
+
163
+ /* Float/double directives: D/d/F/f native byte order, E/e explicit
164
+ little-endian, G/g explicit big-endian (double for the uppercase-native/
165
+ E/G forms, single precision for f/e/g). The IEEE-754 bit pattern round-
166
+ trips through an integer so the endian-explicit forms can reuse the
167
+ shift-based byte writer (host-endianness independent). */
168
+ static int pk_is_flt_spec(char spec) {
169
+ return spec == 'D' || spec == 'd' || spec == 'F' || spec == 'f' ||
170
+ spec == 'E' || spec == 'e' || spec == 'G' || spec == 'g';
171
+ }
172
+ static void pk_flt_directive(char spec, double v, char **buf, size_t *len, size_t *cap) {
173
+ char tmp[8];
174
+ switch (spec) {
175
+ case 'D': case 'd':
176
+ memcpy(tmp, &v, 8);
177
+ pk_append(buf, len, cap, tmp, 8);
178
+ break;
179
+ case 'E': case 'G': {
180
+ uint64_t bits; memcpy(&bits, &v, 8);
181
+ pk_put_bits(tmp, bits, 8, spec == 'G');
182
+ pk_append(buf, len, cap, tmp, 8);
183
+ break;
184
+ }
185
+ case 'F': case 'f': {
186
+ float f = (float)v;
187
+ memcpy(tmp, &f, 4);
188
+ pk_append(buf, len, cap, tmp, 4);
189
+ break;
190
+ }
191
+ case 'e': case 'g': {
192
+ float f = (float)v;
193
+ uint32_t bits; memcpy(&bits, &f, 4);
194
+ pk_put_bits(tmp, bits, 4, spec == 'g');
195
+ pk_append(buf, len, cap, tmp, 4);
196
+ break;
197
+ }
198
+ }
199
+ }
200
+ /* Pack one integer element for the integer directives. Returns 0 when the
201
+ directive consumed no element (x emits a NUL without taking one). */
202
+ static int pk_int_directive(char spec, int64_t v, int big, char **buf, size_t *len, size_t *cap) {
203
+ char tmp[8];
204
+ switch (spec) {
205
+ case 'C': case 'c':
206
+ tmp[0] = (char)(v & 0xff);
207
+ pk_append(buf, len, cap, tmp, 1);
208
+ break;
209
+ case 'n':
210
+ tmp[0] = (char)((v >> 8) & 0xff); tmp[1] = (char)(v & 0xff);
211
+ pk_append(buf, len, cap, tmp, 2);
212
+ break;
213
+ case 'N':
214
+ tmp[0] = (char)((v >> 24) & 0xff); tmp[1] = (char)((v >> 16) & 0xff);
215
+ tmp[2] = (char)((v >> 8) & 0xff); tmp[3] = (char)(v & 0xff);
216
+ pk_append(buf, len, cap, tmp, 4);
217
+ break;
218
+ case 'v':
219
+ tmp[0] = (char)(v & 0xff); tmp[1] = (char)((v >> 8) & 0xff);
220
+ pk_append(buf, len, cap, tmp, 2);
221
+ break;
222
+ case 'V':
223
+ tmp[0] = (char)(v & 0xff); tmp[1] = (char)((v >> 8) & 0xff);
224
+ tmp[2] = (char)((v >> 16) & 0xff); tmp[3] = (char)((v >> 24) & 0xff);
225
+ pk_append(buf, len, cap, tmp, 4);
226
+ break;
227
+ case 's': case 'S':
228
+ pk_put_int(tmp, v, 2, big);
229
+ pk_append(buf, len, cap, tmp, 2);
230
+ break;
231
+ case 'l': case 'L':
232
+ pk_put_int(tmp, v, 4, big);
233
+ pk_append(buf, len, cap, tmp, 4);
234
+ break;
235
+ case 'q': case 'Q':
236
+ pk_put_int(tmp, v, 8, big);
237
+ pk_append(buf, len, cap, tmp, 8);
238
+ break;
239
+ case 'i': case 'I': /* native int, as unpack reads it */
240
+ pk_put_int(tmp, v, (int)sizeof(int), big);
241
+ pk_append(buf, len, cap, tmp, sizeof(int));
242
+ break;
243
+ case 'j': case 'J': /* intptr_t */
244
+ pk_put_int(tmp, v, (int)sizeof(intptr_t), big);
245
+ pk_append(buf, len, cap, tmp, sizeof(intptr_t));
246
+ break;
247
+ case 'x':
248
+ tmp[0] = 0;
249
+ pk_append(buf, len, cap, tmp, 1);
250
+ return 0;
251
+ case 'U': {
252
+ unsigned char ub[6];
253
+ int ulen = pk_utf8(ub, v);
254
+ pk_append(buf, len, cap, (const char *)ub, (size_t)ulen);
255
+ break;
256
+ }
257
+ default:
258
+ break;
259
+ }
260
+ return 1;
261
+ }
262
+ /* Decode one float/double element on unpack (4 bytes for f/F/e/g, 8 for
263
+ d/D/E/G -- the caller sizes the read via fsize). */
264
+ static double uk_get_flt(char spec, const unsigned char *u) {
265
+ if (spec == 'D' || spec == 'd') { double v; memcpy(&v, u, 8); return v; }
266
+ if (spec == 'F' || spec == 'f') { float v; memcpy(&v, u, 4); return (double)v; }
267
+ if (spec == 'E' || spec == 'G') {
268
+ uint64_t bits = pk_get_int(u, 8, spec == 'G');
269
+ double v; memcpy(&v, &bits, 8); return v;
270
+ }
271
+ uint32_t bits = (uint32_t)pk_get_int(u, 4, spec == 'g');
272
+ float v; memcpy(&v, &bits, 4); return (double)v;
273
+ }
274
+
275
+ /* Truncate a double toward zero for the integer directives with CRuby's
276
+ Array#pack semantics: NaN / +-Infinity raise FloatDomainError, and finite
277
+ values outside the int64 range wrap modulo 2^64 (CRuby coerces through an
278
+ exact Integer, and the directive's byte mask keeps only the low bits). A
279
+ plain (int64_t) cast is undefined C for anything outside
280
+ [INT64_MIN, INT64_MAX], including NaN. */
281
+ static int64_t pk_flt_to_int(double dv) {
282
+ if (isnan(dv)) sp_raise_cls("FloatDomainError", "NaN");
283
+ if (isinf(dv)) sp_raise_cls("FloatDomainError", dv > 0 ? "Infinity" : "-Infinity");
284
+ if (dv >= -9223372036854775808.0 && dv < 9223372036854775808.0)
285
+ return (int64_t)dv;
286
+ /* Out-of-range finite: |dv| >= 2^63, so the mantissa is shifted left by
287
+ its (positive) binary exponent; the unsigned shift wraps mod 2^64. */
288
+ double a = dv < 0 ? -dv : dv;
289
+ uint64_t bits; memcpy(&bits, &a, 8);
290
+ int sh = (int)((bits >> 52) & 0x7FF) - 1075; /* a = mant * 2^sh */
291
+ uint64_t mant = (bits & 0xFFFFFFFFFFFFFULL) | (1ULL << 52);
292
+ uint64_t u = sh >= 64 ? 0 : mant << sh;
293
+ return (int64_t)(dv < 0 ? 0 - u : u);
294
+ }
295
+
296
+ /* The wording CRuby uses when a value cannot become an Integer: special
297
+ constants by their inspect (nil / true / false), everything else by class. */
298
+ static const char *pk_val_name(sp_RbVal v) {
299
+ switch (v.tag) {
300
+ case SP_TAG_NIL: return "nil";
301
+ case SP_TAG_BOOL: return v.v.b ? "true" : "false";
302
+ case SP_TAG_STR: return "String";
303
+ case SP_TAG_SYM: return "Symbol";
304
+ case SP_TAG_OBJ:
305
+ if (v.cls_id >= 0 && sp_obj_cls_name_fn) return sp_obj_cls_name_fn((int)v.cls_id);
306
+ return "Object";
307
+ default: return "Object";
308
+ }
309
+ }
310
+
311
+ int64_t sp_bigint_to_int(sp_Bigint *b); /* wraps mod 2^64, as pack does */
312
+
313
+ /* an unpacked integer is a Ruby Integer: sp_int when it fits, a Bignum
314
+ otherwise (a 64-bit quantity on a 32-bit sp_int), as CRuby answers */
315
+ #define pk_box_i64 sp_box_i64
316
+
317
+ sp_Bigint *sp_bigint_new_u64(uint64_t v);
318
+ /* The same for an UNSIGNED 64-bit quantity, whose top half no int64 holds:
319
+ `Q` above 2**63-1 is a Bignum in CRuby, and reading it as a signed value
320
+ answered a negative number for every such byte pattern. */
321
+ static sp_RbVal pk_box_u64(uint64_t v) {
322
+ if (v > (uint64_t)INT64_MAX) return sp_box_bigint(sp_bigint_new_u64(v));
323
+ return pk_box_i64((int64_t)v);
324
+ }
325
+
326
+ static int64_t pk_poly_to_int(sp_RbVal v) {
327
+ switch (v.tag) {
328
+ case SP_TAG_INT: return v.v.i;
329
+ case SP_TAG_FLT: return pk_flt_to_int(v.v.f);
330
+ case SP_TAG_BIGINT: return sp_bigint_to_int((sp_Bigint *)v.v.p);
331
+ case SP_TAG_OBJ:
332
+ /* a user object converts through its compiled #to_int (the generated
333
+ bridge); one without the method falls through to CRuby's TypeError */
334
+ if (v.cls_id >= 0 && v.v.p && sp_obj_to_int_fn) {
335
+ int ok = 0;
336
+ SP_GC_ROOT_RBVAL(v); /* across the user #to_int */
337
+ int64_t r = sp_obj_to_int_fn((int)v.cls_id, v.v.p, &ok);
338
+ if (ok) return r;
339
+ }
340
+ break;
341
+ default: break;
342
+ }
343
+ sp_raise_cls("TypeError",
344
+ sp_sprintf("no implicit conversion of %s into Integer", pk_val_name(v)));
345
+ return 0;
346
+ }
347
+
348
+ static double pk_poly_to_flt(sp_RbVal v) {
349
+ switch (v.tag) {
350
+ case SP_TAG_FLT: return v.v.f;
351
+ case SP_TAG_INT: return (double)v.v.i;
352
+ case SP_TAG_BOOL: return v.v.b ? 1.0 : 0.0;
353
+ case SP_TAG_STR: return v.v.s ? strtod(v.v.s, NULL) : 0.0;
354
+ default: return 0.0;
355
+ }
356
+ }
357
+
358
+ const char *sp_poly_class_name(sp_RbVal v); /* lib/sp_poly_cold.c */
359
+
360
+ /* The value a conversion TypeError names: nil, true and false by
361
+ themselves, anything else by its class. */
362
+ static const char *pk_conv_name(sp_RbVal v) {
363
+ if (v.tag == SP_TAG_NIL) return "nil";
364
+ if (v.tag == SP_TAG_BOOL) return v.v.b ? "true" : "false";
365
+ return sp_poly_class_name(v);
366
+ }
367
+
368
+ /* The bytes one element gives a String directive, as CRuby's pack reads
369
+ them. M takes any value's #to_s (rb_obj_as_string; nil's is ""). The
370
+ others take a String, or a user object's #to_str (StringValue); nil is no
371
+ bytes for a A Z B b H h, which pad it, where m and u have no nil arm.
372
+ Anything else is CRuby's TypeError ("no implicit conversion of Integer
373
+ into String"): the element read as no bytes, so `[1].pack("a*")`
374
+ answered "" and `[1].pack("a")` a NUL. A shared String handle in a poly
375
+ Array is its live bytes; it too read as none. */
376
+ static const char *pk_str_elem_bytes(char spec, sp_RbVal v, size_t *n) {
377
+ *n = 0;
378
+ if (v.tag == SP_TAG_STR && v.v.s) { *n = sp_str_byte_len(v.v.s); return v.v.s; }
379
+ if (v.tag == SP_TAG_OBJ && v.cls_id == SP_BUILTIN_STRBUF && v.v.p) {
380
+ sp_String *h = (sp_String *)v.v.p;
381
+ if (!h->data) return "";
382
+ *n = (size_t)h->len;
383
+ return h->data;
384
+ }
385
+ if (spec == 'M') {
386
+ const char *s = v.tag == SP_TAG_NIL ? ""
387
+ : v.tag == SP_TAG_INT ? sp_int_to_s(v.v.i)
388
+ : v.tag == SP_TAG_FLT ? sp_float_to_s(v.v.f)
389
+ : sp_poly_to_s_fn ? sp_poly_to_s_fn(v) : "";
390
+ *n = s ? strlen(s) : 0;
391
+ return s ? s : "";
392
+ }
393
+ if (v.tag == SP_TAG_NIL && spec != 'm' && spec != 'u') return "";
394
+ if (v.tag == SP_TAG_OBJ && v.cls_id >= 0 && v.v.p && sp_obj_to_str_fn) {
395
+ SP_GC_ROOT_RBVAL(v); /* across the user #to_str */
396
+ const char *r = sp_obj_to_str_fn((int)v.cls_id, v.v.p);
397
+ if (r) { *n = sp_str_byte_len(r); return r; }
398
+ }
399
+ sp_raise_cls("TypeError", sp_sprintf("no implicit conversion of %s into String", pk_conv_name(v)));
400
+ return "";
401
+ }
402
+
403
+ /* ---------- Base64 (`m`) and quoted-printable (`M`) encoders ---------- */
404
+
405
+ /* Standard Base64 (RFC 4648 §4): `+/` alphabet, `=` padding. Encodes one
406
+ run of `n` input bytes as ceil(n/3)*4 output chars into the growable buf. */
407
+ static void pk_b64_run(char **buf, size_t *len, size_t *cap,
408
+ const unsigned char *s, size_t n) {
409
+ static const char B64[] =
410
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
411
+ char o[4];
412
+ size_t i = 0;
413
+ for (; i + 3 <= n; i += 3) {
414
+ uint32_t v = ((uint32_t)s[i] << 16) | ((uint32_t)s[i + 1] << 8) | s[i + 2];
415
+ o[0] = B64[(v >> 18) & 63]; o[1] = B64[(v >> 12) & 63];
416
+ o[2] = B64[(v >> 6) & 63]; o[3] = B64[v & 63];
417
+ pk_append(buf, len, cap, o, 4);
418
+ }
419
+ size_t rem = n - i;
420
+ if (rem == 1) {
421
+ uint32_t v = (uint32_t)s[i] << 16;
422
+ o[0] = B64[(v >> 18) & 63]; o[1] = B64[(v >> 12) & 63]; o[2] = '='; o[3] = '=';
423
+ pk_append(buf, len, cap, o, 4);
424
+ }
425
+ else if (rem == 2) {
426
+ uint32_t v = ((uint32_t)s[i] << 16) | ((uint32_t)s[i + 1] << 8);
427
+ o[0] = B64[(v >> 18) & 63]; o[1] = B64[(v >> 12) & 63];
428
+ o[2] = B64[(v >> 6) & 63]; o[3] = '=';
429
+ pk_append(buf, len, cap, o, 4);
430
+ }
431
+ }
432
+
433
+ /* `m` directive, matching CRuby pack.c: count 0 (`m0`) = one unbroken base64
434
+ string, no trailing newline; a bare `m` (count 1) or `m1`/`m2` wraps every
435
+ 45 input bytes; `mN` (N>=3) wraps every N/3*3 bytes. Each wrapped line, and
436
+ the final one, ends with `\n`. An empty input yields the empty string. */
437
+ static void pk_emit_base64(char **buf, size_t *len, size_t *cap,
438
+ const unsigned char *s, size_t sl, int64_t count) {
439
+ if (count == 0) { pk_b64_run(buf, len, cap, s, sl); return; }
440
+ size_t chunk = (count <= 2) ? 45 : (size_t)((count / 3) * 3);
441
+ if (chunk < 3) chunk = 45;
442
+ for (size_t off = 0; off < sl; off += chunk) {
443
+ size_t n = (sl - off < chunk) ? (sl - off) : chunk;
444
+ pk_b64_run(buf, len, cap, s + off, n);
445
+ pk_append(buf, len, cap, "\n", 1);
446
+ }
447
+ }
448
+
449
+ /* `M` directive (quoted-printable), faithful to CRuby pack.c qpencode: a bare
450
+ `M` (count 1) or `M0`/`M1` uses a 72-char soft-wrap; `MN` wraps at N. */
451
+ static void pk_emit_qp(char **buf, size_t *len, size_t *cap,
452
+ const unsigned char *s, size_t sl, int64_t count) {
453
+ static const char HEX[] = "0123456789ABCDEF";
454
+ int64_t line = (count <= 1) ? 72 : count;
455
+ long n = 0;
456
+ int prev = -1;
457
+ for (size_t k = 0; k < sl; k++) {
458
+ unsigned char ch = s[k];
459
+ if (ch > 126 || (ch < 32 && ch != '\n' && ch != '\t') || ch == '=') {
460
+ char o[3] = { '=', HEX[ch >> 4], HEX[ch & 0x0f] };
461
+ pk_append(buf, len, cap, o, 3); n += 3; prev = -1;
462
+ }
463
+ else if (ch == '\n') {
464
+ if (prev == ' ' || prev == '\t') { char e[2] = { '=', '\n' }; pk_append(buf, len, cap, e, 2); }
465
+ pk_append(buf, len, cap, "\n", 1); n = 0; prev = ch;
466
+ }
467
+ else {
468
+ pk_append(buf, len, cap, (const char *)&ch, 1); n++; prev = ch;
469
+ }
470
+ if (n > line) { char e[2] = { '=', '\n' }; pk_append(buf, len, cap, e, 2); n = 0; prev = '\n'; }
471
+ }
472
+ if (n > 0) { char e[2] = { '=', '\n' }; pk_append(buf, len, cap, e, 2); }
473
+ }
474
+
475
+ /* Shared m/M dispatch for the string-directive branch of both pack paths. */
476
+ static void pk_str_directive(char spec, int64_t count, const char *s, size_t sl,
477
+ char **buf, size_t *len, size_t *cap) {
478
+ if (spec == 'm') pk_emit_base64(buf, len, cap, (const unsigned char *)s, sl, count);
479
+ else pk_emit_qp(buf, len, cap, (const unsigned char *)s, sl, count);
480
+ }
481
+
482
+ /* Pack a string element under a byte-producing directive: H/h (hex nibbles),
483
+ B/b (bit string), u (uuencode). Shared by the typed-array and poly-array pack
484
+ entry points. `sl` is the source byte length; `count` (< 0 means `*`) is a
485
+ nibble/bit count for H/h/B/b and unused for u. */
486
+ static void pk_str_bytes_directive(char spec, int64_t count, const char *s, size_t sl,
487
+ char **buf, size_t *len, size_t *cap) {
488
+ if (spec == 'H' || spec == 'h') {
489
+ size_t n = (count < 0) ? sl : (size_t)count;
490
+ for (size_t bi = 0; bi < (n + 1) / 2; bi++) {
491
+ char c0 = (2 * bi < n && 2 * bi < sl) ? s[2 * bi] : '0';
492
+ char c1 = (2 * bi + 1 < n && 2 * bi + 1 < sl) ? s[2 * bi + 1] : '0';
493
+ int hi = (c0 >= '0' && c0 <= '9') ? c0 - '0' : (c0 | 0x20) - 'a' + 10;
494
+ int lo = (c1 >= '0' && c1 <= '9') ? c1 - '0' : (c1 | 0x20) - 'a' + 10;
495
+ char byte = (char)((spec == 'H') ? ((hi << 4) | lo) : ((lo << 4) | hi));
496
+ pk_append(buf, len, cap, &byte, 1);
497
+ }
498
+ }
499
+ else if (spec == 'B' || spec == 'b') {
500
+ size_t n = (count < 0) ? sl : (size_t)count;
501
+ for (size_t bi = 0; bi < (n + 7) / 8; bi++) {
502
+ unsigned char byte = 0;
503
+ for (int j = 0; j < 8; j++) {
504
+ size_t k = bi * 8 + j;
505
+ int bit = (k < n && k < sl) ? (s[k] & 1) : 0;
506
+ byte |= (unsigned char)(bit << ((spec == 'B') ? (7 - j) : j));
507
+ }
508
+ pk_append(buf, len, cap, (char *)&byte, 1);
509
+ }
510
+ }
511
+ else if (spec == 'u') {
512
+ size_t pos = 0;
513
+ while (pos < sl) {
514
+ size_t ll = sl - pos; if (ll > 45) ll = 45;
515
+ char lc = (char)(ll + 0x20); pk_append(buf, len, cap, &lc, 1);
516
+ for (size_t i = 0; i < ll; i += 3) {
517
+ unsigned char b0 = (unsigned char)s[pos + i];
518
+ unsigned char b1 = (i + 1 < ll) ? (unsigned char)s[pos + i + 1] : 0;
519
+ unsigned char b2 = (i + 2 < ll) ? (unsigned char)s[pos + i + 2] : 0;
520
+ int e[4] = { b0 >> 2, ((b0 << 4) | (b1 >> 4)) & 0x3F, ((b1 << 2) | (b2 >> 6)) & 0x3F, b2 & 0x3F };
521
+ for (int j = 0; j < 4; j++) { char ch = (char)(e[j] ? e[j] + 0x20 : 0x60); pk_append(buf, len, cap, &ch, 1); }
522
+ }
523
+ char nl = '\n'; pk_append(buf, len, cap, &nl, 1);
524
+ pos += ll;
525
+ }
526
+ }
527
+ }
528
+
529
+
530
+ /* The String directives: each takes one element, its count a width. */
531
+ static int pk_is_str_spec(char spec) {
532
+ return spec && strchr("aAZmMuHhBb", spec) != NULL;
533
+ }
534
+
535
+ /* One String directive over element `e` (`have` 0: the elements ran out,
536
+ which packs no bytes), shared by the four pack entry points so a typed
537
+ Array's element converts as a poly one's does. */
538
+ static void pk_str_spec(char spec, int64_t count, sp_RbVal e, int have,
539
+ char **buf, size_t *len, size_t *cap) {
540
+ size_t sl = 0;
541
+ const char *s = have ? pk_str_elem_bytes(spec, e, &sl) : "";
542
+ if (spec == 'a' || spec == 'A' || spec == 'Z') {
543
+ size_t want = (count < 0) ? sl : (size_t)count;
544
+ if (spec == 'Z' && count < 0) want = sl + 1;
545
+ size_t take = sl < want ? sl : want;
546
+ pk_append(buf, len, cap, s, take);
547
+ if (take < want) {
548
+ char pad = (spec == 'A') ? ' ' : 0;
549
+ for (size_t pi = 0; pi < want - take; pi++) pk_append(buf, len, cap, &pad, 1);
550
+ }
551
+ }
552
+ else if (spec == 'm' || spec == 'M') pk_str_directive(spec, count, s, sl, buf, len, cap);
553
+ else pk_str_bytes_directive(spec, count, s, sl, buf, len, cap);
554
+ }
555
+
556
+ /* ---------- Pack entry points ---------- */
557
+
558
+ /* X backs the output up; @ pads with NUL to (or truncates to) an absolute
559
+ length. Both were dropped, so the packed bytes came out shifted (#3553).
560
+ Shared by the four pack entry points, whose prologues are otherwise
561
+ identical. Returns 1 when it handled the directive (caller should
562
+ `continue`), 0 for any other spec. */
563
+ static int pk_cursor_directive(char spec, int64_t count, char **buf, size_t *len, size_t *cap) {
564
+ if (spec == 'X') {
565
+ size_t back = count < 0 ? 1 : (size_t)count;
566
+ if (back > *len) sp_raise_cls("ArgumentError", "X outside of string");
567
+ *len -= back;
568
+ return 1;
569
+ }
570
+ if (spec == '@') {
571
+ size_t abs = count < 0 ? *len : (size_t)count;
572
+ if (abs <= *len) *len = abs;
573
+ else { char _z = 0; while (*len < abs) pk_append(buf, len, cap, &_z, 1); }
574
+ return 1;
575
+ }
576
+ return 0;
577
+ }
578
+
579
+ /* A typed array's nil is the slot's sentinel (SP_INT_NIL, the Float NaN
580
+ payload), and nil converts to neither number: CRuby raises the
581
+ conversion's TypeError where these packed the sentinel's bits. */
582
+ static int pk_int_directive_consumes(char spec) {
583
+ return spec && strchr("CcnNvVsSlLqQiIjJU", spec) != NULL; /* the ones pk_int_directive packs */
584
+ }
585
+ static SP_NORETURN void pk_nil_elem(int flt) {
586
+ sp_raise_cls("TypeError", flt ? "can't convert nil into Float" : "no implicit conversion of nil into Integer");
587
+ }
588
+
589
+ /* CRuby's pack result encoding: a template of U directives (m, M and u
590
+ alongside them too) answers UTF-8 text, and any other directive makes the
591
+ bytes ASCII-8BIT -- so [233].pack("U") is "\u00e9", one character, where it
592
+ was marked binary and read as two bytes. */
593
+ static int pk_fmt_utf8(const char *fmt) {
594
+ int u = 0;
595
+ for (const char *q = fmt; q && *q; q++) {
596
+ char d = *q;
597
+ if (d == ' ' || d == '\t' || d == '\n' || d == '*' || d == '_' || d == '!' ||
598
+ d == '<' || d == '>' || (d >= '0' && d <= '9')) continue;
599
+ if (d == 'U') u = 1;
600
+ else if (d != 'm' && d != 'M' && d != 'u') return 0;
601
+ }
602
+ return u;
603
+ }
604
+
605
+ const char *sp_IntArray_pack(sp_IntArray *arr, const char *fmt) {SP_GC_ROOT(arr);
606
+ if (!arr || !fmt) return sp_str_empty;
607
+ size_t cap = 64;
608
+ char *buf = (char *)malloc(cap);
609
+ if (!buf) { perror("malloc"); exit(1); }
610
+ size_t len = 0;
611
+ sp_int idx = 0;
612
+ const char *p = fmt;
613
+ while (*p) {
614
+ char spec = *p++;
615
+ spec = pk_native_spec(spec, p);
616
+ if (spec == ' ' || spec == '\t' || spec == '\n') continue;
617
+ int big = 0;
618
+ int64_t count = pk_parse_count_mods(&p, &big);
619
+ if (pk_cursor_directive(spec, count, &buf, &len, &cap)) continue;
620
+ /* a String directive converts the element as CRuby does: an Integer
621
+ is no String (TypeError), the nil sentinel is nil */
622
+ if (pk_is_str_spec(spec)) {
623
+ int have = idx < arr->len;
624
+ sp_int v = have ? arr->data[arr->start + idx] : 0;
625
+ pk_str_spec(spec, count, v == SP_INT_NIL ? sp_box_nil() : sp_box_int(v), have, &buf, &len, &cap);
626
+ idx++;
627
+ continue;
628
+ }
629
+ /* w: BER-compressed integers (base-128, high bit = continuation). */
630
+ if (spec == 'w') {
631
+ int64_t wc = count < 0 ? arr->len - idx : count;
632
+ for (int64_t k = 0; k < wc; k++) {
633
+ int64_t sv = (idx < arr->len) ? arr->data[arr->start + idx] : 0; idx++;
634
+ if (sv == SP_INT_NIL) pk_nil_elem(0);
635
+ if (sv < 0) sp_raise_cls("ArgumentError", "can't compress negative numbers");
636
+ uint64_t v = (uint64_t)sv;
637
+ unsigned char tmp[10]; int ti = 0;
638
+ tmp[ti++] = (unsigned char)(v & 0x7F); v >>= 7;
639
+ while (v > 0) { tmp[ti++] = (unsigned char)((v & 0x7F) | 0x80); v >>= 7; }
640
+ for (int j = ti - 1; j >= 0; j--) pk_append(&buf, &len, &cap, (char *)&tmp[j], 1);
641
+ }
642
+ continue;
643
+ }
644
+ if (count < 0) count = arr->len - idx;
645
+ if (count < 0) count = 0;
646
+ if (pk_is_flt_spec(spec)) {
647
+ for (int64_t k = 0; k < count; k++) {
648
+ if (idx < arr->len && arr->data[arr->start + idx] == SP_INT_NIL) pk_nil_elem(1);
649
+ double dv = (idx < arr->len) ? (double)arr->data[arr->start + idx] : 0.0;
650
+ idx++;
651
+ pk_flt_directive(spec, dv, &buf, &len, &cap);
652
+ }
653
+ continue;
654
+ }
655
+ for (int64_t k = 0; k < count; k++) {
656
+ int64_t v = (idx < arr->len) ? arr->data[arr->start + idx] : 0;
657
+ if (idx < arr->len && v == SP_INT_NIL && pk_int_directive_consumes(spec)) pk_nil_elem(0);
658
+ idx++;
659
+ if (!pk_int_directive(spec, v, big, &buf, &len, &cap)) idx--;
660
+ }
661
+ }
662
+ /* Hand back via GC-tracked sp_str_alloc so the main file's GC
663
+ can free the buffer. */
664
+ char *r = sp_str_alloc(len);
665
+ memcpy(r, buf, len);
666
+ sp_str_set_len(r, len);
667
+ if (!pk_fmt_utf8(fmt)) sp_str_mark_binary(r); /* pack answers ASCII-8BIT bytes: inspect them \xNN */
668
+ free(buf);
669
+ return r;
670
+ }
671
+
672
+ /* Pack a Float array: the float/double directives pack the element's
673
+ IEEE-754 encoding directly; the integer directives truncate toward zero
674
+ via pk_flt_to_int (CRuby coerces Float through an exact Integer the same
675
+ way, so [1.5].pack("C") is "\x01" and NaN/Infinity raise
676
+ FloatDomainError). Mirrors sp_IntArray_pack otherwise -- note
677
+ sp_FloatArray has no `start` offset field. */
678
+ const char *sp_FloatArray_pack(sp_FloatArray *arr, const char *fmt) {
679
+ if (!arr || !fmt) return sp_str_empty;
680
+ size_t cap = 64;
681
+ char *buf = (char *)malloc(cap);
682
+ if (!buf) { perror("malloc"); exit(1); }
683
+ size_t len = 0;
684
+ sp_int idx = 0;
685
+ const char *p = fmt;
686
+ while (*p) {
687
+ char spec = *p++;
688
+ spec = pk_native_spec(spec, p);
689
+ if (spec == ' ' || spec == '\t' || spec == '\n') continue;
690
+ int big = 0;
691
+ int64_t count = pk_parse_count_mods(&p, &big);
692
+ if (pk_cursor_directive(spec, count, &buf, &len, &cap)) continue;
693
+ /* a String directive converts the element as CRuby does: a Float is no
694
+ String (TypeError), the nil sentinel is nil */
695
+ if (pk_is_str_spec(spec)) {
696
+ int have = idx < arr->len;
697
+ sp_float v = have ? arr->data[idx] : 0.0;
698
+ pk_str_spec(spec, count, sp_float_is_nil(v) ? sp_box_nil() : sp_box_float(v), have, &buf, &len, &cap);
699
+ idx++;
700
+ continue;
701
+ }
702
+ if (count < 0) count = arr->len - idx;
703
+ if (count < 0) count = 0;
704
+ if (pk_is_flt_spec(spec)) {
705
+ for (int64_t k = 0; k < count; k++) {
706
+ double dv = (idx < arr->len) ? arr->data[idx] : 0.0;
707
+ if (idx < arr->len && sp_float_is_nil(dv)) pk_nil_elem(1);
708
+ idx++;
709
+ pk_flt_directive(spec, dv, &buf, &len, &cap);
710
+ }
711
+ continue;
712
+ }
713
+ for (int64_t k = 0; k < count; k++) {
714
+ if (idx < arr->len && sp_float_is_nil(arr->data[idx]) && pk_int_directive_consumes(spec)) pk_nil_elem(0);
715
+ int64_t v = (idx < arr->len) ? pk_flt_to_int(arr->data[idx]) : 0;
716
+ idx++;
717
+ if (!pk_int_directive(spec, v, big, &buf, &len, &cap)) idx--;
718
+ }
719
+ }
720
+ char *r = sp_str_alloc(len);
721
+ memcpy(r, buf, len);
722
+ sp_str_set_len(r, len);
723
+ if (!pk_fmt_utf8(fmt)) sp_str_mark_binary(r); /* pack answers ASCII-8BIT bytes: inspect them \xNN */
724
+ free(buf);
725
+ return r;
726
+ }
727
+
728
+ const char *sp_PolyArray_pack(sp_PolyArray *arr, const char *fmt) {SP_GC_ROOT(arr);
729
+ if (!arr || !fmt) return sp_str_empty;
730
+ size_t cap = 64;
731
+ char *buf = (char *)malloc(cap);
732
+ if (!buf) { perror("malloc"); exit(1); }
733
+ size_t len = 0;
734
+ sp_int idx = 0;
735
+ const char *p = fmt;
736
+ while (*p) {
737
+ char spec = *p++;
738
+ spec = pk_native_spec(spec, p);
739
+ if (spec == ' ' || spec == '\t' || spec == '\n') continue;
740
+ int big = 0;
741
+ int64_t count = pk_parse_count_mods(&p, &big);
742
+ if (pk_cursor_directive(spec, count, &buf, &len, &cap)) continue;
743
+ /* a/A/Z, m/M, H/h, B/b, u: consume one element, converted as CRuby
744
+ converts it (pk_str_elem_bytes) */
745
+ if (pk_is_str_spec(spec)) {
746
+ int have = idx < arr->len;
747
+ pk_str_spec(spec, count, have ? arr->data[idx] : sp_box_nil(), have, &buf, &len, &cap);
748
+ idx++;
749
+ continue;
750
+ }
751
+ /* w: BER-compressed integers (base-128, high bit = continuation). */
752
+ if (spec == 'w') {
753
+ int64_t wc = count < 0 ? arr->len - idx : count;
754
+ for (int64_t k = 0; k < wc; k++) {
755
+ int64_t sv = (idx < arr->len) ? pk_poly_to_int(arr->data[idx]) : 0; idx++;
756
+ if (sv < 0) sp_raise_cls("ArgumentError", "can't compress negative numbers");
757
+ uint64_t v = (uint64_t)sv;
758
+ unsigned char tmp[10]; int ti = 0;
759
+ tmp[ti++] = (unsigned char)(v & 0x7F); v >>= 7;
760
+ while (v > 0) { tmp[ti++] = (unsigned char)((v & 0x7F) | 0x80); v >>= 7; }
761
+ for (int j = ti - 1; j >= 0; j--) pk_append(&buf, &len, &cap, (char *)&tmp[j], 1);
762
+ }
763
+ continue;
764
+ }
765
+ if (count < 0) count = arr->len - idx;
766
+ if (count < 0) count = 0;
767
+ if (pk_is_flt_spec(spec)) {
768
+ for (int64_t k = 0; k < count; k++) {
769
+ /* a poly array's nil is a tagged one, which pk_poly_to_flt reads as
770
+ 0.0; it raises here as a typed array's sentinel does */
771
+ if (idx < arr->len && arr->data[idx].tag == SP_TAG_NIL) pk_nil_elem(1);
772
+ double dv = (idx < arr->len) ? pk_poly_to_flt(arr->data[idx]) : 0.0;
773
+ idx++;
774
+ pk_flt_directive(spec, dv, &buf, &len, &cap);
775
+ }
776
+ continue;
777
+ }
778
+ for (int64_t k = 0; k < count; k++) {
779
+ /* converted only for a directive that takes the element: `x` packs a
780
+ NUL and leaves it, so a nil under it (`[1, nil].pack("qx")`) is not
781
+ converted, as the typed arrays' sentinel check already skips it */
782
+ int64_t v = (idx < arr->len && pk_int_directive_consumes(spec)) ? pk_poly_to_int(arr->data[idx]) : 0;
783
+ idx++;
784
+ if (!pk_int_directive(spec, v, big, &buf, &len, &cap)) idx--;
785
+ }
786
+ }
787
+ char *r = sp_str_alloc(len);
788
+ memcpy(r, buf, len);
789
+ sp_str_set_len(r, len);
790
+ if (!pk_fmt_utf8(fmt)) sp_str_mark_binary(r); /* pack answers ASCII-8BIT bytes: inspect them \xNN */
791
+ free(buf);
792
+ return r;
793
+ }
794
+
795
+ /* Pack a String array. Only the string directives are meaningful for String
796
+ elements (CRuby raises TypeError for numeric directives on a String), so we
797
+ handle a/A/Z padding and m/M base64/quoted-printable; other directives emit
798
+ nothing. Reads the real byte length (embedded NULs survive base64). */
799
+ const char *sp_StrArray_pack(sp_StrArray *arr, const char *fmt) {
800
+ if (!arr || !fmt) return sp_str_empty;
801
+ size_t cap = 64;
802
+ char *buf = (char *)malloc(cap);
803
+ if (!buf) { perror("malloc"); exit(1); }
804
+ size_t len = 0;
805
+ sp_int idx = 0;
806
+ const char *p = fmt;
807
+ while (*p) {
808
+ char spec = *p++;
809
+ spec = pk_native_spec(spec, p);
810
+ if (spec == ' ' || spec == '\t' || spec == '\n') continue;
811
+ int big = 0;
812
+ int64_t count = pk_parse_count_mods(&p, &big);
813
+ if (pk_cursor_directive(spec, count, &buf, &len, &cap)) continue;
814
+ /* a nil element (NULL) is nil to the String directives: padding for
815
+ a A Z B b H h, "" for M, and the TypeError m and u raise */
816
+ if (pk_is_str_spec(spec)) {
817
+ int have = idx < arr->len;
818
+ pk_str_spec(spec, count, sp_box_str(have ? sp_StrArray_get(arr, idx) : NULL), have, &buf, &len, &cap);
819
+ idx++;
820
+ continue;
821
+ }
822
+ idx++;
823
+ if (spec == 'p' || spec == 'P') {
824
+ /* the string's address, a pointer-sized J as CRuby packs it */
825
+ pk_int_directive('J', (int64_t)(intptr_t)sp_StrArray_get(arr, idx - 1), 0, &buf, &len, &cap);
826
+ }
827
+ else if (strchr("cCsSlLqQnNvVjJiIfdeEgGUw", spec)) {
828
+ /* a numeric directive cannot take a String element: CRuby's TypeError
829
+ (the directive was silently dropped before) */
830
+ sp_raise_cls("TypeError", "no implicit conversion of String into Integer");
831
+ }
832
+ }
833
+ char *r = sp_str_alloc(len);
834
+ memcpy(r, buf, len);
835
+ sp_str_set_len(r, len);
836
+ if (!pk_fmt_utf8(fmt)) sp_str_mark_binary(r); /* pack answers ASCII-8BIT bytes: inspect them \xNN */
837
+ free(buf);
838
+ return r;
839
+ }
840
+
841
+ /* ---------- Unpack entry point ---------- */
842
+
843
+ /* base64 sextet value for an ASCII byte, or -1 for a non-alphabet char. */
844
+ static int uk_b64val(unsigned char ch) {
845
+ if (ch >= 'A' && ch <= 'Z') return ch - 'A';
846
+ if (ch >= 'a' && ch <= 'z') return ch - 'a' + 26;
847
+ if (ch >= '0' && ch <= '9') return ch - '0' + 52;
848
+ if (ch == '+') return 62;
849
+ if (ch == '/') return 63;
850
+ return -1;
851
+ }
852
+ /* Decode `n` base64 bytes from `src` into a fresh GC string (the `m` directive).
853
+ Non-alphabet bytes (whitespace, newlines) are skipped; `=` ends the data. In
854
+ `strict` mode (the `m0` count) any non-alphabet byte is an ArgumentError, as
855
+ RFC 4648 requires. */
856
+ static char *uk_b64_decode_strict(const char *src, size_t n, int strict) {
857
+ char *out = sp_str_alloc(n); /* decoded size <= input size */
858
+ size_t o = 0;
859
+ int quad[4], qi = 0;
860
+ for (size_t i = 0; i < n; i++) {
861
+ unsigned char ch = (unsigned char)src[i];
862
+ if (ch == '=') break;
863
+ int val = uk_b64val(ch);
864
+ if (val < 0) { if (strict) sp_raise_cls("ArgumentError", "invalid base64"); continue; }
865
+ quad[qi++] = val;
866
+ if (qi == 4) {
867
+ out[o++] = (char)((quad[0] << 2) | (quad[1] >> 4));
868
+ out[o++] = (char)((quad[1] << 4) | (quad[2] >> 2));
869
+ out[o++] = (char)((quad[2] << 6) | quad[3]);
870
+ qi = 0;
871
+ }
872
+ }
873
+ if (qi >= 2) {
874
+ out[o++] = (char)((quad[0] << 2) | (quad[1] >> 4));
875
+ if (qi >= 3) out[o++] = (char)((quad[1] << 4) | (quad[2] >> 2));
876
+ }
877
+ out[o] = 0; sp_str_set_len(out, o);
878
+ return out;
879
+ }
880
+ static char *uk_b64_decode(const char *src, size_t n) { return uk_b64_decode_strict(src, n, 0); }
881
+ /* Decode `n` quoted-printable bytes into a fresh GC string (the `M` directive):
882
+ `=XX` is a hex byte, `=\n` (soft line break) is dropped, everything else is
883
+ literal. */
884
+ static char *uk_qp_decode(const char *src, size_t n) {
885
+ char *out = sp_str_alloc(n);
886
+ size_t o = 0;
887
+ for (size_t i = 0; i < n; i++) {
888
+ unsigned char ch = (unsigned char)src[i];
889
+ if (ch == '=' && i + 2 < n) {
890
+ char h1 = src[i + 1], h2 = src[i + 2];
891
+ int d1 = (h1 >= '0' && h1 <= '9') ? h1 - '0' : (h1 >= 'A' && h1 <= 'F') ? h1 - 'A' + 10
892
+ : (h1 >= 'a' && h1 <= 'f') ? h1 - 'a' + 10 : -1;
893
+ int d2 = (h2 >= '0' && h2 <= '9') ? h2 - '0' : (h2 >= 'A' && h2 <= 'F') ? h2 - 'A' + 10
894
+ : (h2 >= 'a' && h2 <= 'f') ? h2 - 'a' + 10 : -1;
895
+ if (d1 >= 0 && d2 >= 0) { out[o++] = (char)((d1 << 4) | d2); i += 2; continue; }
896
+ }
897
+ if (ch == '=' && i + 1 < n && (src[i + 1] == '\n')) { i += 1; continue; }
898
+ if (ch == '=' && i + 2 < n && src[i + 1] == '\r' && src[i + 2] == '\n') { i += 2; continue; }
899
+ out[o++] = (char)ch;
900
+ }
901
+ out[o] = 0; sp_str_set_len(out, o);
902
+ return out;
903
+ }
904
+
905
+ /* An unpack format byte that is no directive: ArgumentError, as CRuby's
906
+ unknown_directive words it. The directive shows as itself when printable,
907
+ else as \xNN; the format as an escaped string (\0, \e, \n, ..., \xNN for
908
+ a byte, \uNNNN for a UTF-8 character or, in a String that is not binary,
909
+ another control character). It was skipped, so a typo read on with the
910
+ directives after it, and a NUL byte ended the format. */
911
+ static SP_NORETURN void uk_unknown_directive(char type, const char *fmt, size_t flen) {
912
+ size_t cap = flen * 10 + 64, o = 0;
913
+ char *msg = (char *)malloc(cap);
914
+ if (!msg) sp_raise_cls("ArgumentError", "unknown unpack directive");
915
+ unsigned char t = (unsigned char)type;
916
+ int bin = sp_str_is_binary(fmt);
917
+ if (t >= 0x20 && t < 0x7f) o += (size_t)snprintf(msg + o, cap - o, "unknown unpack directive '%c' in '", type);
918
+ else o += (size_t)snprintf(msg + o, cap - o, "unknown unpack directive '\\x%02x' in '", t);
919
+ /* A format every character of which is printable is shown as it is, as
920
+ CRuby's rb_str_quote_unprintable leaves it ('Cé'); only one holding an
921
+ unprintable character is escaped as a whole. */
922
+ int printable = 1;
923
+ for (size_t i = 0; i < flen && printable; i++) {
924
+ unsigned char ch = (unsigned char)fmt[i];
925
+ if (ch < 0x20 || ch == 0x7f) printable = 0;
926
+ else if (ch >= 0x80) {
927
+ int len = bin ? 0 : ch >= 0xC2 && ch < 0xE0 ? 2 : ch >= 0xE0 && ch < 0xF0 ? 3 : ch >= 0xF0 && ch < 0xF5 ? 4 : 0;
928
+ if (!len || i + (size_t)len > flen) { printable = 0; break; }
929
+ for (int j = 1; j < len; j++)
930
+ if (((unsigned char)fmt[i + j] & 0xC0) != 0x80) { printable = 0; break; }
931
+ i += (size_t)len - 1;
932
+ }
933
+ }
934
+ if (printable) { memcpy(msg + o, fmt, flen); o += flen; }
935
+ for (size_t i = 0; i < flen && !printable; i++) {
936
+ unsigned char ch = (unsigned char)fmt[i];
937
+ const char *esc = NULL;
938
+ switch (ch) {
939
+ case 0: esc = "\\0"; break;
940
+ case '\n': esc = "\\n"; break;
941
+ case '\t': esc = "\\t"; break;
942
+ case '\r': esc = "\\r"; break;
943
+ case '\f': esc = "\\f"; break;
944
+ case '\v': esc = "\\v"; break;
945
+ case '\b': esc = "\\b"; break;
946
+ case '\a': esc = "\\a"; break;
947
+ case 0x1b: esc = "\\e"; break;
948
+ case 0x7f: esc = "\\c?"; break;
949
+ default: break;
950
+ }
951
+ if (esc) { o += (size_t)snprintf(msg + o, cap - o, "%s", esc); continue; }
952
+ if (ch >= 0x20 && ch < 0x7f) { msg[o++] = (char)ch; continue; }
953
+ /* another control character is a codepoint in a UTF-8 String */
954
+ if (ch < 0x20 && !bin) { o += (size_t)snprintf(msg + o, cap - o, "\\u%04X", ch); continue; }
955
+ /* a whole UTF-8 character: \uNNNN (\u{N} past the BMP) */
956
+ int len = bin ? 0 : ch >= 0xC2 && ch < 0xE0 ? 2 : ch >= 0xE0 && ch < 0xF0 ? 3 : ch >= 0xF0 && ch < 0xF5 ? 4 : 0;
957
+ if (len && i + (size_t)len <= flen) {
958
+ uint32_t cp = ch & (0x7F >> len);
959
+ int ok = 1;
960
+ for (int j = 1; j < len && ok; j++) {
961
+ unsigned char cb = (unsigned char)fmt[i + j];
962
+ if ((cb & 0xC0) != 0x80) ok = 0;
963
+ else cp = (cp << 6) | (cb & 0x3F);
964
+ }
965
+ if (ok) {
966
+ o += (size_t)snprintf(msg + o, cap - o, cp > 0xFFFF ? "\\u{%X}" : "\\u%04X", (unsigned)cp);
967
+ i += (size_t)len - 1;
968
+ continue;
969
+ }
970
+ }
971
+ o += (size_t)snprintf(msg + o, cap - o, "\\x%02X", ch);
972
+ }
973
+ msg[o++] = '\'';
974
+ msg[o] = 0;
975
+ char *m = sp_str_alloc(o);
976
+ memcpy(m, msg, o + 1);
977
+ sp_str_set_len(m, o);
978
+ free(msg);
979
+ sp_raise_cls("ArgumentError", m);
980
+ }
981
+ sp_PolyArray *sp_str_unpack_off(const char *str, const char *fmt, sp_int byteoff);
982
+ sp_PolyArray *sp_str_unpack(const char *str, const char *fmt) {SP_GC_ROOT_STR(str);SP_GC_ROOT_STR(fmt); return sp_str_unpack_off(str, fmt, 0); }
983
+
984
+ /* String#unpack(fmt, offset: n): decode starting at byte offset n. A negative
985
+ offset or one past the end raises ArgumentError, matching MRI. */
986
+ sp_PolyArray *sp_str_unpack_off(const char *str, const char *fmt, sp_int byteoff) {
987
+ if (!str) sp_nil_recv("unpack");
988
+ /* Root the source string across every allocation below: `str` is very often a
989
+ fresh, otherwise-unrooted substring (`data[4, 4].unpack1('V')` in doom's
990
+ binary WAD parsing). sp_PolyArray_new and the per-element boxes/pushes can
991
+ each trigger a GC that would sweep the input out from under us (a
992
+ use-after-free read of the freed bytes). Root the result array too. */
993
+ SP_GC_ROOT_STR(str);
994
+ /* `fmt` is walked (via `p`) across the very same allocations, so it is just as
995
+ exposed as `str`: a fresh, unrooted format string (e.g. `unpack1("V" * n)`)
996
+ could be swept mid-parse, leaving `p` dangling. Root it too. NUL-safe. */
997
+ SP_GC_ROOT_STR(fmt);
998
+ sp_PolyArray *out = sp_PolyArray_new();
999
+ SP_GC_ROOT(out);
1000
+ if (!str || !fmt) return out;
1001
+ /* sp_ext_str_byte_len honors the heap-string header so embedded
1002
+ NULs (binary data) don't truncate the source. */
1003
+ size_t slen = sp_str_byte_len(str);
1004
+ if (byteoff < 0) sp_raise_cls("ArgumentError", "offset can't be negative");
1005
+ if ((size_t)byteoff > slen) sp_raise_cls("ArgumentError", "offset outside of string");
1006
+ size_t off = (size_t)byteoff;
1007
+ const char *p = fmt;
1008
+ /* the format's own length: a NUL byte in it is an unknown directive, not
1009
+ its end */
1010
+ size_t flen = sp_str_byte_len(fmt);
1011
+ const char *pend = fmt + flen;
1012
+ while (p < pend) {
1013
+ char spec = *p++;
1014
+ spec = pk_native_spec(spec, p);
1015
+ if (spec == ' ' || spec == '\t' || spec == '\n' || spec == '\v' || spec == '\f' || spec == '\r') continue;
1016
+ /* `#` comments to the end of the line */
1017
+ if (spec == '#') { while (p < pend && *p != '\n') p++; continue; }
1018
+ /* `%` prefixes a checksum request, which CRuby 4 no longer accepts (#3553) */
1019
+ if (spec == '%') sp_raise_cls("ArgumentError", "% is not supported");
1020
+ int big = 0;
1021
+ int64_t count = pk_parse_count_mods(&p, &big);
1022
+ /* X moves the read position back, @ moves it to an absolute offset; both
1023
+ were dropped, so every following directive read from the wrong place */
1024
+ if (spec == 'X') {
1025
+ /* `X*` backs up by what remains past the position, as CRuby counts it */
1026
+ size_t back = count < 0 ? slen - off : (size_t)count;
1027
+ if (back > off) sp_raise_cls("ArgumentError", "X outside of string");
1028
+ off -= back;
1029
+ continue;
1030
+ }
1031
+ if (spec == '@') {
1032
+ size_t abs = count < 0 ? slen : (size_t)count;
1033
+ if (abs > slen) sp_raise_cls("ArgumentError", "@ outside of string");
1034
+ off = abs;
1035
+ continue;
1036
+ }
1037
+ size_t fsize = 0;
1038
+ switch (spec) {
1039
+ case 'C': case 'c': case 'x': fsize = 1; break;
1040
+ case 'n': case 'v': case 's': case 'S': fsize = 2; break;
1041
+ case 'N': case 'V': case 'l': case 'L': fsize = 4; break;
1042
+ case 'f': case 'F': case 'e': case 'g': fsize = 4; break;
1043
+ case 'q': case 'Q': fsize = 8; break;
1044
+ case 'd': case 'D': case 'E': case 'G': fsize = 8; break;
1045
+ /* the native int, intptr and pointer sizes */
1046
+ case 'i': case 'I': fsize = 4; break;
1047
+ case 'j': case 'J': case 'p': case 'P': fsize = (int)sizeof(intptr_t); break;
1048
+ default: fsize = 0; break;
1049
+ }
1050
+ if (spec == 'a' || spec == 'A' || spec == 'Z') {
1051
+ size_t take;
1052
+ if (count < 0) {
1053
+ take = slen - off;
1054
+ }
1055
+ else {
1056
+ take = (size_t)count;
1057
+ if (off + take > slen) take = slen - off;
1058
+ }
1059
+ const char *src = str + off;
1060
+ if (spec == 'Z' && count < 0) {
1061
+ size_t z = 0;
1062
+ while (off + z < slen && src[z]) z++;
1063
+ char *s = sp_str_alloc(z);
1064
+ memcpy(s, src, z); s[z] = 0; sp_str_set_len(s, z);
1065
+ sp_str_mark_binary(s); /* unpack answers BYTES here: ASCII-8BIT, as CRuby (the b/B/h/H arms build ASCII TEXT and stay on the text side) */
1066
+ sp_PolyArray_push(out, sp_box_str(s));
1067
+ off += z;
1068
+ if (off < slen && str[off] == 0) off++;
1069
+ }
1070
+ else {
1071
+ char *s = sp_str_alloc(take);
1072
+ memcpy(s, src, take); s[take] = 0;
1073
+ size_t real = take;
1074
+ if (spec == 'A') {
1075
+ while (real > 0 && (s[real - 1] == ' ' || s[real - 1] == 0)) real--;
1076
+ s[real] = 0;
1077
+ }
1078
+ else if (spec == 'Z') {
1079
+ size_t z = 0;
1080
+ while (z < take && s[z]) z++;
1081
+ s[z] = 0;
1082
+ real = z;
1083
+ }
1084
+ sp_str_set_len(s, real);
1085
+ sp_str_mark_binary(s); /* unpack answers BYTES here: ASCII-8BIT, as CRuby (the b/B/h/H arms build ASCII TEXT and stay on the text side) */
1086
+ sp_PolyArray_push(out, sp_box_str(s));
1087
+ off += take;
1088
+ }
1089
+ continue;
1090
+ }
1091
+ /* H/h: hex-nibble string (H high nibble first, h low first). `count` is a
1092
+ nibble (hex-digit) count; `*` takes every remaining nibble. */
1093
+ if (spec == 'H' || spec == 'h') {
1094
+ size_t avail = (slen - off) * 2;
1095
+ size_t n = count < 0 ? avail : (size_t)count;
1096
+ if (n > avail) n = avail;
1097
+ char *s = sp_str_alloc(n);
1098
+ for (size_t i = 0; i < n; i++) {
1099
+ unsigned char byte = (unsigned char)str[off + i / 2];
1100
+ int nib = (spec == 'H') ? ((i & 1) ? (byte & 15) : (byte >> 4))
1101
+ : ((i & 1) ? (byte >> 4) : (byte & 15));
1102
+ s[i] = "0123456789abcdef"[nib];
1103
+ }
1104
+ s[n] = 0; sp_str_set_len(s, n);
1105
+ sp_PolyArray_push(out, sp_box_str(s));
1106
+ off += (n + 1) / 2;
1107
+ continue;
1108
+ }
1109
+ /* B/b: bit string (B MSB first, b LSB first). `count` is a bit count. */
1110
+ if (spec == 'B' || spec == 'b') {
1111
+ size_t avail = (slen - off) * 8;
1112
+ size_t n = count < 0 ? avail : (size_t)count;
1113
+ if (n > avail) n = avail;
1114
+ char *s = sp_str_alloc(n);
1115
+ for (size_t i = 0; i < n; i++) {
1116
+ unsigned char byte = (unsigned char)str[off + i / 8];
1117
+ int bit = (spec == 'B') ? ((byte >> (7 - (i & 7))) & 1) : ((byte >> (i & 7)) & 1);
1118
+ s[i] = (char)('0' + bit);
1119
+ }
1120
+ s[n] = 0; sp_str_set_len(s, n);
1121
+ sp_PolyArray_push(out, sp_box_str(s));
1122
+ off += (n + 7) / 8;
1123
+ continue;
1124
+ }
1125
+ /* m: base64, M: quoted-printable. Both decode the whole remaining input into
1126
+ one string (the count/`0` modifier only tweaks strictness, not framing). */
1127
+ if (spec == 'm' || spec == 'M') {
1128
+ size_t avail = slen - off;
1129
+ /* `m0` is strict RFC 4648: reject any non-alphabet byte. */
1130
+ char *s = (spec == 'm') ? uk_b64_decode_strict(str + off, avail, count == 0)
1131
+ : uk_qp_decode(str + off, avail);
1132
+ sp_str_mark_binary(s); /* unpack answers BYTES here: ASCII-8BIT, as CRuby (the b/B/h/H arms build ASCII TEXT and stay on the text side) */
1133
+ sp_PolyArray_push(out, sp_box_str(s));
1134
+ off = slen;
1135
+ continue;
1136
+ }
1137
+ /* u: uuencode. Each line begins with a length byte ((c-0x20)&0x3F decoded
1138
+ bytes) followed by 4-char groups that each yield 3 bytes; a zero length
1139
+ (or end of input) terminates. */
1140
+ if (spec == 'u') {
1141
+ char *s = sp_str_alloc(slen - off); /* decoded <= input */
1142
+ size_t o = 0, i = off;
1143
+ while (i < slen) {
1144
+ int L = (((unsigned char)str[i++]) - 0x20) & 0x3F;
1145
+ if (L == 0) break;
1146
+ int produced = 0;
1147
+ while (produced < L && i < slen) {
1148
+ int c[4];
1149
+ for (int j = 0; j < 4; j++) c[j] = i < slen ? ((((unsigned char)str[i++]) - 0x20) & 0x3F) : 0;
1150
+ if (produced < L) { s[o++] = (char)((c[0] << 2) | (c[1] >> 4)); produced++; }
1151
+ if (produced < L) { s[o++] = (char)((c[1] << 4) | (c[2] >> 2)); produced++; }
1152
+ if (produced < L) { s[o++] = (char)((c[2] << 6) | c[3]); produced++; }
1153
+ }
1154
+ while (i < slen && str[i] != '\n') i++; /* skip to end of line */
1155
+ if (i < slen) i++;
1156
+ }
1157
+ s[o] = 0; sp_str_set_len(s, o);
1158
+ sp_str_mark_binary(s); /* unpack answers BYTES here: ASCII-8BIT, as CRuby (the b/B/h/H arms build ASCII TEXT and stay on the text side) */
1159
+ sp_PolyArray_push(out, sp_box_str(s));
1160
+ off = slen;
1161
+ continue;
1162
+ }
1163
+ /* U: UTF-8 codepoints as integers. `count` codepoints, `*` all remaining. */
1164
+ if (spec == 'U') {
1165
+ int64_t got = 0;
1166
+ while ((count < 0 || got < count) && off < slen) {
1167
+ unsigned char b0 = (unsigned char)str[off];
1168
+ int len = b0 < 0x80 ? 1 : (b0 >> 5) == 0x6 ? 2 : (b0 >> 4) == 0xE ? 3 : (b0 >> 3) == 0x1E ? 4 : 0;
1169
+ /* A lead byte with no valid length, or a truncated / mis-continued
1170
+ sequence, is malformed UTF-8 -- ArgumentError, as in MRI. */
1171
+ if (len == 0 || off + (size_t)len > slen)
1172
+ sp_raise_cls("ArgumentError", "malformed UTF-8 character");
1173
+ int64_t cp = len == 1 ? b0 : (b0 & (0x7F >> len));
1174
+ for (int j = 1; j < len; j++) {
1175
+ unsigned char cb = (unsigned char)str[off + j];
1176
+ if ((cb & 0xC0) != 0x80) sp_raise_cls("ArgumentError", "malformed UTF-8 character");
1177
+ cp = (cp << 6) | (cb & 0x3F);
1178
+ }
1179
+ sp_PolyArray_push(out, sp_box_int((sp_int)cp));
1180
+ off += len; got++;
1181
+ }
1182
+ continue;
1183
+ }
1184
+ /* w: BER-compressed integers (base-128, high bit = continuation). */
1185
+ if (spec == 'w') {
1186
+ int64_t got = 0;
1187
+ while ((count < 0 || got < count) && off < slen) {
1188
+ int64_t v = 0;
1189
+ while (off < slen) {
1190
+ unsigned char c = (unsigned char)str[off++];
1191
+ v = (v << 7) | (c & 0x7F);
1192
+ if (!(c & 0x80)) break;
1193
+ }
1194
+ sp_PolyArray_push(out, pk_box_i64(v));
1195
+ got++;
1196
+ }
1197
+ continue;
1198
+ }
1199
+ if (fsize == 0) uk_unknown_directive(spec, fmt, flen);
1200
+ /* `*` count is exactly what remains; an explicit count that runs off the end
1201
+ pads the remaining slots with nil (MRI: "a".unpack("CC") == [97, nil]). */
1202
+ int star = count < 0;
1203
+ if (star) count = (slen - off) / fsize;
1204
+ for (int64_t k = 0; k < count; k++) {
1205
+ if (off + fsize > slen) {
1206
+ if (spec == 'x') sp_raise_cls("ArgumentError", "x outside of string");
1207
+ sp_PolyArray_push(out, sp_box_nil());
1208
+ continue;
1209
+ }
1210
+ const unsigned char *u = (const unsigned char *)(str + off);
1211
+ if (pk_is_flt_spec(spec)) {
1212
+ sp_PolyArray_push(out, sp_box_float((sp_float)uk_get_flt(spec, u)));
1213
+ off += fsize;
1214
+ continue;
1215
+ }
1216
+ int64_t v = 0;
1217
+ switch (spec) {
1218
+ case 'C': v = u[0]; break;
1219
+ case 'c': v = (int8_t)u[0]; break;
1220
+ case 'n': v = ((int64_t)u[0] << 8) | u[1]; break;
1221
+ case 'N': v = ((int64_t)u[0] << 24) | ((int64_t)u[1] << 16) | ((int64_t)u[2] << 8) | u[3]; break;
1222
+ case 'v': v = ((int64_t)u[1] << 8) | u[0]; break;
1223
+ case 'V': v = ((int64_t)u[3] << 24) | ((int64_t)u[2] << 16) | ((int64_t)u[1] << 8) | u[0]; break;
1224
+ case 's': v = (int16_t)pk_get_int(u, 2, big); break;
1225
+ case 'S': v = (uint16_t)pk_get_int(u, 2, big); break;
1226
+ case 'l': v = (int32_t)pk_get_int(u, 4, big); break;
1227
+ case 'L': v = (uint32_t)pk_get_int(u, 4, big); break;
1228
+ case 'q': v = (int64_t)pk_get_int(u, 8, big); break;
1229
+ case 'i': v = (int32_t)pk_get_int(u, 4, big); break;
1230
+ case 'I': v = (uint32_t)pk_get_int(u, 4, big); break;
1231
+ case 'j': v = (int64_t)(intptr_t)pk_get_int(u, (int)sizeof(intptr_t), big); break;
1232
+ /* a pointer CRuby's own pack did not make: nil for NULL, else it has
1233
+ no object to answer */
1234
+ case 'p': case 'P':
1235
+ if (pk_get_int(u, (int)sizeof(intptr_t), 0)) sp_raise_cls("ArgumentError", "no associated pointer");
1236
+ off += fsize;
1237
+ sp_PolyArray_push(out, sp_box_nil());
1238
+ continue;
1239
+ /* unsigned, so it does not share the signed boxing below */
1240
+ case 'J':
1241
+ case 'Q': {
1242
+ uint64_t uv = pk_get_int(u, spec == 'J' ? (int)sizeof(intptr_t) : 8, big);
1243
+ off += fsize;
1244
+ sp_PolyArray_push(out, pk_box_u64(uv));
1245
+ continue;
1246
+ }
1247
+ case 'x': break;
1248
+ }
1249
+ off += fsize;
1250
+ if (spec != 'x') sp_PolyArray_push(out, pk_box_i64(v));
1251
+ }
1252
+ }
1253
+ return out;
1254
+ }