teptris 0.2.37-arm-linux → 0.2.39-arm-linux

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/ext/teptris_ext/engine/LICENSE.md +21 -0
  3. data/ext/teptris_ext/engine/src/include/teptris/plan.h +123 -0
  4. data/ext/teptris_ext/engine/src/include/teptris/teptris.h +208 -0
  5. data/ext/teptris_ext/engine/src/include/teptris/version.h +9 -0
  6. data/ext/teptris_ext/engine/src/teptris/api.c +362 -0
  7. data/ext/teptris_ext/engine/src/teptris/build.c +355 -0
  8. data/ext/teptris_ext/engine/src/teptris/common/chartype.c +70 -0
  9. data/ext/teptris_ext/engine/src/teptris/common/chartype.h +41 -0
  10. data/ext/teptris_ext/engine/src/teptris/common/ryu/common.h +114 -0
  11. data/ext/teptris_ext/engine/src/teptris/common/ryu/d2s.c +496 -0
  12. data/ext/teptris_ext/engine/src/teptris/common/ryu/d2s_full_table.h +367 -0
  13. data/ext/teptris_ext/engine/src/teptris/common/ryu/d2s_intrinsics.h +357 -0
  14. data/ext/teptris_ext/engine/src/teptris/common/ryu/digit_table.h +35 -0
  15. data/ext/teptris_ext/engine/src/teptris/common/ryu/ryu_d2s.h +39 -0
  16. data/ext/teptris_ext/engine/src/teptris/common/ryu/ryu_parse.h +45 -0
  17. data/ext/teptris_ext/engine/src/teptris/common/ryu/s2d.c +259 -0
  18. data/ext/teptris_ext/engine/src/teptris/common/simd.h +119 -0
  19. data/ext/teptris_ext/engine/src/teptris/dom/dom.c +192 -0
  20. data/ext/teptris_ext/engine/src/teptris/dom/dom.h +124 -0
  21. data/ext/teptris_ext/engine/src/teptris/emit/emitter.c +718 -0
  22. data/ext/teptris_ext/engine/src/teptris/emit/emitter.h +11 -0
  23. data/ext/teptris_ext/engine/src/teptris/memory/arena.c +104 -0
  24. data/ext/teptris_ext/engine/src/teptris/memory/arena.h +53 -0
  25. data/ext/teptris_ext/engine/src/teptris/parse/datetime.c +218 -0
  26. data/ext/teptris_ext/engine/src/teptris/parse/keys.c +121 -0
  27. data/ext/teptris_ext/engine/src/teptris/parse/parse.h +76 -0
  28. data/ext/teptris_ext/engine/src/teptris/parse/parser.c +931 -0
  29. data/ext/teptris_ext/engine/src/teptris/parse/scalars.c +940 -0
  30. data/ext/teptris_ext/engine/src/teptris/plan/plan.c +472 -0
  31. data/ext/teptris_ext/engine/src/teptris/version.c +6 -0
  32. data/ext/teptris_ext/extconf.rb +61 -0
  33. data/ext/teptris_ext/materialize.c +608 -0
  34. data/lib/teptris/version.rb +1 -1
  35. data/lib/teptris.rb +19 -11
  36. metadata +35 -3
  37. /data/lib/{teptris_ext.so → teptris/3.2/teptris_ext.so} +0 -0
@@ -0,0 +1,608 @@
1
+ #include <ruby.h>
2
+ #include <ruby/encoding.h>
3
+ #include "teptris/teptris.h"
4
+ #include "teptris/plan.h"
5
+
6
+ /* flags: bit0 = datetimes as strings, bit1 = forbid Time, bit2 = forbid Date */
7
+ #define FMT_STRING_DATES 1
8
+ #define FMT_FORBID_TIME 2
9
+ #define FMT_FORBID_DATE 4
10
+
11
+ static VALUE eParseError, eError, cDate;
12
+
13
+ /* civil date -> days since epoch (Hinnant, inverse of the dump side) */
14
+ static int64_t days_from_civil(int32_t y, uint8_t m, uint8_t d) {
15
+ y -= m <= 2;
16
+ int64_t era = (y >= 0 ? y : y - 399) / 400;
17
+ uint32_t yoe = (uint32_t)(y - era * 400);
18
+ uint32_t doy = (uint32_t)(153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1;
19
+ uint32_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
20
+ return era * 146097 + (int64_t)doe - 719468;
21
+ }
22
+
23
+ /* exact (no double) Time materialization: nsec stays nsec. Offset
24
+ * datetimes go through timespec+fixed-offset; local datetimes through
25
+ * Time.local with Rational seconds (rb_time_timespec_new's INT_MAX
26
+ * treats the timespec as an absolute instant, not wall-clock local). */
27
+ static VALUE time_from_dt(const teptris_datetime *d, bool has_offset) {
28
+ if (has_offset) {
29
+ int64_t days = days_from_civil(d->year, d->month, d->day);
30
+ int64_t secs =
31
+ days * 86400 + d->hour * 3600 + d->minute * 60 + d->second;
32
+ struct timespec ts;
33
+ ts.tv_sec = (time_t)(secs - d->offset_seconds);
34
+ ts.tv_nsec = (long)d->nanosecond;
35
+ return rb_time_timespec_new(&ts, d->offset_seconds);
36
+ }
37
+ VALUE args[6] = {INT2FIX(d->year), INT2FIX(d->month), INT2FIX(d->day),
38
+ INT2FIX(d->hour), INT2FIX(d->minute),
39
+ rb_Rational(LL2NUM((int64_t)d->second * 1000000000 +
40
+ d->nanosecond),
41
+ LL2NUM(1000000000))};
42
+ return rb_funcallv(rb_cTime, rb_intern("local"), 6, args);
43
+ }
44
+
45
+ static VALUE dt_string(const teptris_datetime *d, int kind) {
46
+ char buf[48];
47
+ int l;
48
+ if (kind == TEPTRIS_DATE_LOCAL) {
49
+ l = snprintf(buf, sizeof buf, "%04d-%02u-%02u", d->year, d->month, d->day);
50
+ } else {
51
+ const char *head = (kind == TEPTRIS_TIME_LOCAL) ? "" : "";
52
+ (void)head;
53
+ if (kind == TEPTRIS_TIME_LOCAL) {
54
+ l = snprintf(buf, sizeof buf, "%02u:%02u:%02u", d->hour, d->minute, d->second);
55
+ } else {
56
+ l = snprintf(buf, sizeof buf, "%04d-%02u-%02uT%02u:%02u:%02u",
57
+ d->year, d->month, d->day, d->hour, d->minute, d->second);
58
+ if (kind == TEPTRIS_DATETIME_OFFSET) {
59
+ if (d->offset_seconds == 0) {
60
+ buf[l++] = 'Z';
61
+ } else {
62
+ int off = d->offset_seconds, neg = off < 0;
63
+ if (neg) off = -off;
64
+ l += snprintf(buf + l, sizeof buf - l, "%c%02d:%02d",
65
+ neg ? '-' : '+', off / 3600, (off % 3600) / 60);
66
+ }
67
+ }
68
+ }
69
+ }
70
+ return rb_enc_str_new(buf, l, rb_utf8_encoding());
71
+ }
72
+
73
+ static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
74
+ switch (teptris_node_kind(n)) {
75
+ case TEPTRIS_TABLE: {
76
+ size_t len = teptris_node_table_length(n);
77
+ VALUE h = rb_hash_new();
78
+ for (size_t i = 0; i < len; i++) {
79
+ teptris_view key;
80
+ const teptris_node *v = teptris_node_table_at(n, i, &key);
81
+ rb_hash_aset(h,
82
+ rb_enc_str_new(key.ptr, (long)key.len, rb_utf8_encoding()),
83
+ obj_from_node(v, flags));
84
+ }
85
+ return h;
86
+ }
87
+ case TEPTRIS_ARRAY: {
88
+ size_t len = teptris_node_array_length(n);
89
+ VALUE a = rb_ary_new_capa((long)len);
90
+ for (size_t i = 0; i < len; i++)
91
+ rb_ary_push(a, obj_from_node(teptris_node_array_at(n, i), flags));
92
+ return a;
93
+ }
94
+ case TEPTRIS_STRING: {
95
+ teptris_view s;
96
+ teptris_node_string(n, &s);
97
+ return rb_enc_str_new(s.ptr, (long)s.len, rb_utf8_encoding());
98
+ }
99
+ case TEPTRIS_INTEGER: {
100
+ int64_t v = 0; teptris_node_integer(n, &v);
101
+ return LL2NUM(v);
102
+ }
103
+ case TEPTRIS_FLOAT: {
104
+ double v = 0; teptris_node_float(n, &v);
105
+ return DBL2NUM(v);
106
+ }
107
+ case TEPTRIS_BOOLEAN: {
108
+ bool v = false; teptris_node_boolean(n, &v);
109
+ return v ? Qtrue : Qfalse;
110
+ }
111
+ default: {
112
+ teptris_datetime d; teptris_node_datetime(n, &d);
113
+ int kind = teptris_node_kind(n);
114
+ if (flags & FMT_STRING_DATES) return dt_string(&d, kind);
115
+ if (kind == TEPTRIS_DATETIME_OFFSET || kind == TEPTRIS_DATETIME_LOCAL) {
116
+ if (flags & FMT_FORBID_TIME)
117
+ rb_raise(eError, "value materializes Time, which is not in "
118
+ "permitted_classes (use datetime_policy: :string)");
119
+ return time_from_dt(&d, kind == TEPTRIS_DATETIME_OFFSET);
120
+ }
121
+ if (kind == TEPTRIS_DATE_LOCAL) {
122
+ if (flags & FMT_FORBID_DATE)
123
+ rb_raise(eError, "value materializes Date, which is not in "
124
+ "permitted_classes (use datetime_policy: :string)");
125
+ return rb_funcall(cDate, rb_intern("new"), 3,
126
+ INT2FIX(d.year), INT2FIX(d.month), INT2FIX(d.day));
127
+ }
128
+ return dt_string(&d, kind); /* local time: canonical string */
129
+ }}
130
+ }
131
+
132
+ static teptris_document *parse_or_raise(VALUE str) {
133
+ teptris_document *doc = NULL;
134
+ teptris_status st = teptris_parse(RSTRING_PTR(str), (size_t)RSTRING_LEN(str), NULL, &doc);
135
+ if (st != TEPTRIS_OK) {
136
+ const teptris_error *e = teptris_document_error(doc);
137
+ VALUE ex = rb_exc_new(eParseError, e->message, (long)strlen(e->message));
138
+ rb_iv_set(ex, "@line", SIZET2NUM(e->line));
139
+ rb_iv_set(ex, "@column", SIZET2NUM(e->column));
140
+ teptris_document_free(doc);
141
+ rb_exc_raise(ex);
142
+ }
143
+ return doc;
144
+ }
145
+
146
+ static VALUE ext_load(int argc, VALUE *argv, VALUE self) {
147
+ VALUE str, opts;
148
+ rb_scan_args(argc, argv, "11", &str, &opts);
149
+ StringValue(str);
150
+ unsigned flags = 0;
151
+ if (!NIL_P(opts)) {
152
+ VALUE sd = rb_hash_aref(opts, ID2SYM(rb_intern("string_datetimes")));
153
+ VALUE ft = rb_hash_aref(opts, ID2SYM(rb_intern("forbid_time")));
154
+ VALUE fd = rb_hash_aref(opts, ID2SYM(rb_intern("forbid_date")));
155
+ if (RTEST(sd)) flags |= FMT_STRING_DATES;
156
+ if (RTEST(ft)) flags |= FMT_FORBID_TIME;
157
+ if (RTEST(fd)) flags |= FMT_FORBID_DATE;
158
+ }
159
+ teptris_document *doc = parse_or_raise(str);
160
+ VALUE out = obj_from_node(teptris_document_root(doc), flags);
161
+ teptris_document_free(doc);
162
+ return out;
163
+ }
164
+
165
+ static VALUE ext_version(VALUE self) {
166
+ return rb_str_new_cstr(teptris_version_string());
167
+ }
168
+
169
+ /* ------------------------------------------------------------------ dump */
170
+
171
+ static ID id_utc_offset, id_to_s;
172
+
173
+ static void dump_check(teptris_status st) {
174
+ if (st == TEPTRIS_OK) return;
175
+ if (st == TEPTRIS_ERR_ALLOC)
176
+ rb_raise(eError, "out of memory while dumping");
177
+ rb_raise(eError, "cannot dump value (%s)", teptris_status_string(st));
178
+ }
179
+
180
+ /* days since 1970-01-01 -> proleptic-Gregorian y/m/d (Hinnant) */
181
+ static void civil_from_days(int64_t z, int32_t *y, uint8_t *m, uint8_t *d) {
182
+ z += 719468;
183
+ int64_t era = (z >= 0 ? z : z - 146096) / 146097;
184
+ uint32_t doe = (uint32_t)(z - era * 146097);
185
+ uint32_t yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
186
+ int64_t yy = (int64_t)yoe + era * 400;
187
+ uint32_t doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
188
+ uint32_t mp = (5 * doy + 2) / 153;
189
+ uint32_t dd = doy - (153 * mp + 2) / 5 + 1;
190
+ uint32_t mm = mp < 10 ? mp + 3 : mp - 9;
191
+ if (mm <= 2) yy++;
192
+ *y = (int32_t)yy;
193
+ *m = (uint8_t)mm;
194
+ *d = (uint8_t)dd;
195
+ }
196
+
197
+ /* two crossings per Time: timespec + utc_offset; the civil fields come
198
+ * from integer math (epoch + offset -> y/m/d h:m:s + tv_nsec) */
199
+ static void fill_dt_from_time(VALUE v, teptris_datetime *dt) {
200
+ struct timespec ts = rb_time_timespec(v);
201
+ dt->offset_seconds = NUM2INT(rb_funcall(v, id_utc_offset, 0));
202
+ int64_t secs = (int64_t)ts.tv_sec + dt->offset_seconds;
203
+ int64_t days = secs / 86400;
204
+ int32_t rem = (int32_t)(secs % 86400);
205
+ if (rem < 0) { /* floor division: keep rem in [0, 86400) */
206
+ rem += 86400;
207
+ days -= 1;
208
+ }
209
+ civil_from_days(days, &dt->year, &dt->month, &dt->day);
210
+ dt->hour = (uint8_t)(rem / 3600);
211
+ dt->minute = (uint8_t)((rem / 60) % 60);
212
+ dt->second = (uint8_t)(rem % 60);
213
+ dt->nanosecond = (uint32_t)ts.tv_nsec;
214
+ }
215
+
216
+ static void dump_scalar(teptris_builder *b, const char *key, size_t klen,
217
+ VALUE v) {
218
+ switch (TYPE(v)) {
219
+ case T_STRING:
220
+ dump_check(teptris_builder_put_string(b, key, klen, RSTRING_PTR(v),
221
+ (size_t)RSTRING_LEN(v)));
222
+ return;
223
+ case T_TRUE:
224
+ case T_FALSE:
225
+ dump_check(teptris_builder_put_boolean(b, key, klen, RTEST(v)));
226
+ return;
227
+ case T_FIXNUM: /* T_INTEGER on 3.2+; T_FIXNUM spans every ruby we build */
228
+ case T_BIGNUM: /* NUM2LL raises RangeError outside int64 */
229
+ dump_check(teptris_builder_put_integer(b, key, klen, NUM2LL(v)));
230
+ return;
231
+ case T_FLOAT:
232
+ dump_check(teptris_builder_put_float(b, key, klen, RFLOAT_VALUE(v)));
233
+ return;
234
+ default:
235
+ if (rb_obj_is_kind_of(v, rb_cTime)) {
236
+ teptris_datetime dt = {0};
237
+ fill_dt_from_time(v, &dt);
238
+ dump_check(teptris_builder_put_datetime(
239
+ b, key, klen, TEPTRIS_DATETIME_OFFSET, &dt));
240
+ return;
241
+ }
242
+ if (rb_obj_is_kind_of(v, cDate)) {
243
+ /* DateTime is a Date subclass: dumps its date part, as the
244
+ * Ruby-era dump writers did. One crossing: jd -> civil. */
245
+ teptris_datetime dt = {0};
246
+ int64_t jd = NUM2LL(rb_funcall(v, rb_intern("jd"), 0));
247
+ civil_from_days(jd - 2440588, &dt.year, &dt.month, &dt.day);
248
+ dump_check(teptris_builder_put_datetime(b, key, klen,
249
+ TEPTRIS_DATE_LOCAL, &dt));
250
+ return;
251
+ }
252
+ rb_raise(eError, "cannot dump %s", rb_obj_classname(v));
253
+ }
254
+ }
255
+
256
+ static void build_value(teptris_builder *b, VALUE v);
257
+
258
+ static int table_each(VALUE key, VALUE val, VALUE data) {
259
+ teptris_builder *b = (teptris_builder *)data;
260
+ if (!RB_TYPE_P(key, T_STRING))
261
+ key = rb_funcall(key, id_to_s, 0);
262
+ const char *kp = RSTRING_PTR(key);
263
+ size_t kl = (size_t)RSTRING_LEN(key);
264
+ if (RB_TYPE_P(val, T_HASH)) {
265
+ dump_check(teptris_builder_open_table(b, kp, kl));
266
+ rb_hash_foreach(val, table_each, (VALUE)b);
267
+ dump_check(teptris_builder_close(b));
268
+ } else if (RB_TYPE_P(val, T_ARRAY)) {
269
+ long n = RARRAY_LEN(val);
270
+ bool all_hash = n > 0;
271
+ for (long i = 0; i < n; i++) {
272
+ if (!RB_TYPE_P(RARRAY_AREF(val, i), T_HASH)) {
273
+ all_hash = false;
274
+ break;
275
+ }
276
+ }
277
+ dump_check(all_hash ? teptris_builder_open_array(b, kp, kl)
278
+ : teptris_builder_open_inline_array(b, kp, kl));
279
+ for (long i = 0; i < n; i++)
280
+ build_value(b, RARRAY_AREF(val, i));
281
+ dump_check(teptris_builder_close(b));
282
+ } else {
283
+ dump_scalar(b, kp, kl, val);
284
+ }
285
+ return ST_CONTINUE;
286
+ }
287
+
288
+ /* element position: no key */
289
+ static void build_value(teptris_builder *b, VALUE v) {
290
+ if (RB_TYPE_P(v, T_HASH)) {
291
+ dump_check(teptris_builder_open_table(b, NULL, 0));
292
+ rb_hash_foreach(v, table_each, (VALUE)b);
293
+ dump_check(teptris_builder_close(b));
294
+ } else if (RB_TYPE_P(v, T_ARRAY)) {
295
+ long n = RARRAY_LEN(v);
296
+ /* element arrays are inline territory: the builder pins them */
297
+ dump_check(teptris_builder_open_array(b, NULL, 0));
298
+ for (long i = 0; i < n; i++)
299
+ build_value(b, RARRAY_AREF(v, i));
300
+ dump_check(teptris_builder_close(b));
301
+ } else {
302
+ dump_scalar(b, NULL, 0, v);
303
+ }
304
+ }
305
+
306
+ static VALUE ext_dump(VALUE self, VALUE obj) {
307
+ if (!RB_TYPE_P(obj, T_HASH))
308
+ rb_raise(rb_eArgError, "cannot dump %s (root must be a Hash)",
309
+ rb_obj_classname(obj));
310
+ teptris_builder *b = teptris_builder_new();
311
+ if (b == NULL)
312
+ rb_raise(eError, "out of memory while dumping");
313
+ rb_hash_foreach(obj, table_each, (VALUE)b);
314
+ teptris_document *doc = NULL;
315
+ teptris_status st = teptris_builder_finish(b, &doc);
316
+ if (st != TEPTRIS_OK) {
317
+ teptris_builder_free(b);
318
+ rb_raise(eError, "cannot dump (%s)", teptris_status_string(st));
319
+ }
320
+ char *buf = NULL;
321
+ size_t len = 0;
322
+ st = teptris_document_emit(doc, &buf, &len);
323
+ teptris_document_free(doc);
324
+ teptris_builder_free(b); /* doc already transferred/freed */
325
+ if (st != TEPTRIS_OK)
326
+ rb_raise(eError, "dump failed (%s)", teptris_status_string(st));
327
+ VALUE out = rb_enc_str_new(buf, (long)len, rb_utf8_encoding());
328
+ free(buf);
329
+ return out;
330
+ }
331
+
332
+ /* ------------------------------------------------- plan-walk (teptris#46) */
333
+
334
+ static void plan_free(void *p) { teptris_plan_free((teptris_plan *)p); }
335
+
336
+ static const rb_data_type_t plan_type = {
337
+ "Teptris/plan",
338
+ {0, plan_free, 0, {0, 0}},
339
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
340
+ };
341
+
342
+ /* Native assembly over a walked result: plan row metadata drives a
343
+ * recursive builder; RAW rows re-use obj_from_node; datetime scalars
344
+ * mirror obj_from_node's native policy (Time/Date/canonical time
345
+ * string). Unmatched rows materialize as nil. */
346
+ static VALUE asm_rows(const teptris_plan *p, const teptris_plan_result *res,
347
+ uint32_t plan_idx)
348
+ {
349
+ uint32_t n = teptris_plan_row_count(p, plan_idx);
350
+ VALUE h = rb_hash_new();
351
+ for (uint32_t i = 0; i < n; i++) {
352
+ VALUE k = rb_str_new_cstr(teptris_plan_row_name_at(p, plan_idx, i));
353
+ VALUE val = Qnil;
354
+ switch (teptris_plan_result_kind_at(res, i)) {
355
+ case TEPTRIS_PLAN_SCALAR_RESULT: {
356
+ teptris_datetime d;
357
+ switch (teptris_plan_result_value_kind_at(res, i)) {
358
+ case TEPTRIS_STRING: {
359
+ teptris_view v;
360
+ if (teptris_plan_result_string_at(res, i, &v) == TEPTRIS_OK) {
361
+ val = rb_enc_str_new(v.ptr, (long)v.len, rb_utf8_encoding());
362
+ }
363
+ break;
364
+ }
365
+ case TEPTRIS_INTEGER: {
366
+ int64_t iv;
367
+ if (teptris_plan_result_integer_at(res, i, &iv) == TEPTRIS_OK) {
368
+ val = LL2NUM(iv);
369
+ }
370
+ break;
371
+ }
372
+ case TEPTRIS_FLOAT: {
373
+ double dv;
374
+ if (teptris_plan_result_float_at(res, i, &dv) == TEPTRIS_OK) {
375
+ val = DBL2NUM(dv);
376
+ }
377
+ break;
378
+ }
379
+ case TEPTRIS_BOOLEAN: {
380
+ bool bv;
381
+ if (teptris_plan_result_boolean_at(res, i, &bv) == TEPTRIS_OK) {
382
+ val = bv ? Qtrue : Qfalse;
383
+ }
384
+ break;
385
+ }
386
+ case TEPTRIS_DATETIME_OFFSET:
387
+ case TEPTRIS_DATETIME_LOCAL:
388
+ if (teptris_plan_result_datetime_at(res, i, &d) == TEPTRIS_OK) {
389
+ val = time_from_dt(&d,
390
+ teptris_plan_result_value_kind_at(res, i) ==
391
+ TEPTRIS_DATETIME_OFFSET);
392
+ }
393
+ break;
394
+ case TEPTRIS_DATE_LOCAL:
395
+ if (teptris_plan_result_datetime_at(res, i, &d) == TEPTRIS_OK) {
396
+ val = rb_funcall(cDate, rb_intern("new"), 3,
397
+ INT2FIX(d.year), INT2FIX(d.month),
398
+ INT2FIX(d.day));
399
+ }
400
+ break;
401
+ case TEPTRIS_TIME_LOCAL:
402
+ if (teptris_plan_result_datetime_at(res, i, &d) == TEPTRIS_OK) {
403
+ val = dt_string(&d, TEPTRIS_TIME_LOCAL);
404
+ }
405
+ break;
406
+ default:
407
+ break;
408
+ }
409
+ break;
410
+ }
411
+ case TEPTRIS_PLAN_ARRAY: {
412
+ uint32_t n2 = teptris_plan_result_array_len_at(res, i);
413
+ val = rb_ary_new_capa(n2);
414
+ for (uint32_t j = 0; j < n2; j++) {
415
+ switch (teptris_plan_result_array_kind_at(res, i, j)) {
416
+ case TEPTRIS_PLAN_SCALAR_RESULT: {
417
+ teptris_datetime d;
418
+ switch (teptris_plan_result_array_value_kind_at(res, i,
419
+ j)) {
420
+ case TEPTRIS_STRING: {
421
+ teptris_view v;
422
+ if (teptris_plan_result_array_string_at(res, i, j, &v) ==
423
+ TEPTRIS_OK) {
424
+ rb_ary_push(val, rb_enc_str_new(
425
+ v.ptr, (long)v.len,
426
+ rb_utf8_encoding()));
427
+ }
428
+ break;
429
+ }
430
+ case TEPTRIS_INTEGER: {
431
+ int64_t iv;
432
+ if (teptris_plan_result_array_integer_at(res, i, j,
433
+ &iv) ==
434
+ TEPTRIS_OK) {
435
+ rb_ary_push(val, LL2NUM(iv));
436
+ }
437
+ break;
438
+ }
439
+ case TEPTRIS_FLOAT: {
440
+ double dv;
441
+ if (teptris_plan_result_array_float_at(res, i, j, &dv) ==
442
+ TEPTRIS_OK) {
443
+ rb_ary_push(val, DBL2NUM(dv));
444
+ }
445
+ break;
446
+ }
447
+ case TEPTRIS_BOOLEAN: {
448
+ bool bv;
449
+ if (teptris_plan_result_array_boolean_at(res, i, j,
450
+ &bv) ==
451
+ TEPTRIS_OK) {
452
+ rb_ary_push(val, bv ? Qtrue : Qfalse);
453
+ }
454
+ break;
455
+ }
456
+ case TEPTRIS_DATETIME_OFFSET:
457
+ case TEPTRIS_DATETIME_LOCAL:
458
+ case TEPTRIS_DATE_LOCAL:
459
+ case TEPTRIS_TIME_LOCAL:
460
+ if (teptris_plan_result_array_datetime_at(res, i, j,
461
+ &d) ==
462
+ TEPTRIS_OK) {
463
+ rb_ary_push(
464
+ val,
465
+ teptris_plan_result_array_value_kind_at(
466
+ res, i, j) == TEPTRIS_DATE_LOCAL
467
+ ? rb_funcall(cDate, rb_intern("new"), 3,
468
+ INT2FIX(d.year),
469
+ INT2FIX(d.month),
470
+ INT2FIX(d.day))
471
+ : dt_string(&d, TEPTRIS_TIME_LOCAL));
472
+ }
473
+ break;
474
+ default:
475
+ rb_ary_push(val, Qnil);
476
+ break;
477
+ }
478
+ break;
479
+ }
480
+ case TEPTRIS_PLAN_TABLE: {
481
+ teptris_plan_result *sub =
482
+ teptris_plan_result_array_entry_at(res, i, j);
483
+ if (sub != NULL) {
484
+ rb_ary_push(val, asm_rows(
485
+ p, sub,
486
+ teptris_plan_row_sub_at(p, plan_idx,
487
+ i)));
488
+ teptris_plan_result_view_free(sub);
489
+ }
490
+ break;
491
+ }
492
+ default:
493
+ rb_ary_push(val, Qnil);
494
+ break;
495
+ }
496
+ }
497
+ break;
498
+ }
499
+ case TEPTRIS_PLAN_TABLE: {
500
+ teptris_plan_result *sub = teptris_plan_result_row_view(res, i);
501
+ if (sub != NULL) {
502
+ val = asm_rows(p, sub, teptris_plan_row_sub_at(p, plan_idx, i));
503
+ teptris_plan_result_view_free(sub);
504
+ }
505
+ break;
506
+ }
507
+ case TEPTRIS_PLAN_RAW_RESULT: {
508
+ const teptris_node *raw = teptris_plan_result_raw_at(res, i);
509
+ if (raw != NULL) {
510
+ val = obj_from_node(raw, 0);
511
+ }
512
+ break;
513
+ }
514
+ default:
515
+ break;
516
+ }
517
+ rb_hash_aset(h, k, val);
518
+ }
519
+ return h;
520
+ }
521
+
522
+ static VALUE ext_plan_build(VALUE self, VALUE rows, VALUE first_row)
523
+ {
524
+ Check_Type(rows, T_ARRAY);
525
+ Check_Type(first_row, T_ARRAY);
526
+ long nrows = RARRAY_LEN(rows);
527
+ long nplans = RARRAY_LEN(first_row) - 1;
528
+ if (nplans < 1 || nrows < 1) {
529
+ rb_raise(eError, "plan needs >= 1 plan and >= 1 row");
530
+ }
531
+ teptris_plan_row *crows = calloc((size_t)nrows, sizeof(*crows));
532
+ /* bytes-vs-count: this must be (nplans+1) * sizeof(uint32_t) — the
533
+ * byte-sized malloc overflows the heap and musl's allocator segfaults
534
+ * where glibc/darwin absorb it */
535
+ uint32_t *cfirst = malloc(((size_t)nplans + 1) * sizeof(*cfirst));
536
+ if (crows == NULL || cfirst == NULL) {
537
+ free(crows);
538
+ free(cfirst);
539
+ rb_raise(eError, "out of memory building plan");
540
+ }
541
+ for (long i = 0; i < nrows; i++) {
542
+ VALUE row = rb_ary_entry(rows, i);
543
+ Check_Type(row, T_ARRAY);
544
+ VALUE name = rb_ary_entry(row, 0);
545
+ SafeStringValue(name);
546
+ crows[i].name = strdup(StringValueCStr(name));
547
+ crows[i].kind = (uint8_t)NUM2UINT(rb_ary_entry(row, 1));
548
+ crows[i].sub = (uint32_t)NUM2UINT(rb_ary_entry(row, 2));
549
+ }
550
+ for (long i = 0; i <= nplans; i++) {
551
+ cfirst[i] = (uint32_t)NUM2UINT(rb_ary_entry(first_row, i));
552
+ }
553
+ teptris_plan_spec spec = {TEPTRIS_PLAN_ABI_VERSION, (uint32_t)nplans,
554
+ crows, cfirst};
555
+ teptris_status st;
556
+ teptris_plan *plan = teptris_plan_build(&spec, &st);
557
+ free(crows);
558
+ free(cfirst);
559
+ if (plan == NULL) {
560
+ rb_raise(eError, "invalid plan spec (status %d)", (int)st);
561
+ }
562
+ return TypedData_Wrap_Struct(rb_cObject, &plan_type, plan);
563
+ }
564
+
565
+ static VALUE ext_plan_emit(VALUE self, VALUE rb_plan, VALUE toml)
566
+ {
567
+ teptris_plan *plan;
568
+ TypedData_Get_Struct(rb_plan, teptris_plan, &plan_type, plan);
569
+ StringValue(toml);
570
+ teptris_document *doc = NULL;
571
+ teptris_status st =
572
+ teptris_parse(RSTRING_PTR(toml), (size_t)RSTRING_LEN(toml), NULL, &doc);
573
+ if (st != TEPTRIS_OK) {
574
+ const teptris_error *e = teptris_document_error(doc);
575
+ VALUE ex = rb_exc_new(eParseError, e->message, (long)strlen(e->message));
576
+ rb_iv_set(ex, "@line", SIZET2NUM(e->line));
577
+ rb_iv_set(ex, "@column", SIZET2NUM(e->column));
578
+ teptris_document_free(doc);
579
+ rb_exc_raise(ex);
580
+ }
581
+ teptris_plan_result *res =
582
+ teptris_plan_walk(plan, teptris_document_root(doc), &st);
583
+ if (res == NULL) {
584
+ teptris_document_free(doc);
585
+ rb_raise(eError, "plan walk failed (status %d)", (int)st);
586
+ }
587
+ VALUE out = asm_rows(plan, res, 0);
588
+ teptris_plan_result_free(res);
589
+ teptris_document_free(doc);
590
+ return out;
591
+ }
592
+
593
+ void Init_teptris_ext(void) {
594
+ VALUE m = rb_define_module("TeptrisExt");
595
+ rb_define_module_function(m, "load", ext_load, -1);
596
+ rb_define_module_function(m, "dump", ext_dump, 1);
597
+ rb_define_module_function(m, "engine_version", ext_version, 0);
598
+ rb_define_module_function(m, "plan_build", ext_plan_build, 2);
599
+ rb_define_module_function(m, "plan_emit", ext_plan_emit, 2);
600
+ /* error/version load first (teptris.rb), then the ext bundle */
601
+ VALUE t = rb_const_get(rb_cObject, rb_intern("Teptris"));
602
+ eError = rb_const_get(t, rb_intern("Error"));
603
+ eParseError = rb_const_get(t, rb_intern("ParseError"));
604
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new_cstr("date"));
605
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
606
+ id_utc_offset = rb_intern("utc_offset");
607
+ id_to_s = rb_intern("to_s");
608
+ }
@@ -1,3 +1,3 @@
1
1
  module Teptris
2
- VERSION = "0.2.37"
2
+ VERSION = "0.2.39"
3
3
  end
data/lib/teptris.rb CHANGED
@@ -5,20 +5,28 @@ require_relative "teptris/error"
5
5
  require_relative "teptris/descriptor"
6
6
  require_relative "teptris/toml"
7
7
 
8
- # Windows ucrt rubies name their DLL per minor (x64-ucrt-ruby330.dll), so
9
- # the mingw gems ship one extension per ruby minor beside a shared
10
- # libteptris.dll (nokogiri's fat-gem layout). The native extension IS the
11
- # binding — no fallback, ever.
8
+ # Fat-gem layout on EVERY platform (nokogiri's shape): one extension
9
+ # per ruby minor under lib/teptris/<minor>/ — ucrt rubies bind a
10
+ # minor-named DLL, and a unix .so built for one minor fails dlopen on
11
+ # another (a 3.3-built ext hard-failed under ruby 4.0 with the gem
12
+ # already installed). The native extension IS the binding — no
13
+ # fallback, ever; minors without a prebuilt cell ship via the source
14
+ # gem, which compiles at install.
15
+ minor = RUBY_VERSION[/\A\d+\.\d+/]
12
16
  begin
13
- if RUBY_PLATFORM =~ /mingw/
14
- require "teptris/#{RUBY_VERSION[/\A\d+\.\d+/]}/teptris_ext"
15
- else
17
+ # fat prebuilt gems: lib/teptris/<minor>/teptris_ext
18
+ require "teptris/#{minor}/teptris_ext"
19
+ rescue LoadError
20
+ begin
21
+ # source-built installs: rubygems puts the compiled ext on the
22
+ # load path under its bare name (no <minor> dir exists)
16
23
  require "teptris_ext"
24
+ rescue LoadError
25
+ raise LoadError,
26
+ "teptris native extension for ruby #{RUBY_VERSION} not found " \
27
+ "(#{RUBY_PLATFORM}; no fallback by design — install the source " \
28
+ "gem: gem install teptris --platform ruby)"
17
29
  end
18
- rescue LoadError
19
- raise LoadError,
20
- "teptris native extension not available for #{RUBY_PLATFORM} " \
21
- "ruby #{RUBY_VERSION} (no fallback by design)"
22
30
  end
23
31
 
24
32
  module Teptris