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.
- checksums.yaml +4 -4
- data/ext/teptris_ext/engine/LICENSE.md +21 -0
- data/ext/teptris_ext/engine/src/include/teptris/plan.h +123 -0
- data/ext/teptris_ext/engine/src/include/teptris/teptris.h +208 -0
- data/ext/teptris_ext/engine/src/include/teptris/version.h +9 -0
- data/ext/teptris_ext/engine/src/teptris/api.c +362 -0
- data/ext/teptris_ext/engine/src/teptris/build.c +355 -0
- data/ext/teptris_ext/engine/src/teptris/common/chartype.c +70 -0
- data/ext/teptris_ext/engine/src/teptris/common/chartype.h +41 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/common.h +114 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/d2s.c +496 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/d2s_full_table.h +367 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/d2s_intrinsics.h +357 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/digit_table.h +35 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/ryu_d2s.h +39 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/ryu_parse.h +45 -0
- data/ext/teptris_ext/engine/src/teptris/common/ryu/s2d.c +259 -0
- data/ext/teptris_ext/engine/src/teptris/common/simd.h +119 -0
- data/ext/teptris_ext/engine/src/teptris/dom/dom.c +192 -0
- data/ext/teptris_ext/engine/src/teptris/dom/dom.h +124 -0
- data/ext/teptris_ext/engine/src/teptris/emit/emitter.c +718 -0
- data/ext/teptris_ext/engine/src/teptris/emit/emitter.h +11 -0
- data/ext/teptris_ext/engine/src/teptris/memory/arena.c +104 -0
- data/ext/teptris_ext/engine/src/teptris/memory/arena.h +53 -0
- data/ext/teptris_ext/engine/src/teptris/parse/datetime.c +218 -0
- data/ext/teptris_ext/engine/src/teptris/parse/keys.c +121 -0
- data/ext/teptris_ext/engine/src/teptris/parse/parse.h +76 -0
- data/ext/teptris_ext/engine/src/teptris/parse/parser.c +931 -0
- data/ext/teptris_ext/engine/src/teptris/parse/scalars.c +940 -0
- data/ext/teptris_ext/engine/src/teptris/plan/plan.c +472 -0
- data/ext/teptris_ext/engine/src/teptris/version.c +6 -0
- data/ext/teptris_ext/extconf.rb +61 -0
- data/ext/teptris_ext/materialize.c +608 -0
- data/lib/teptris/version.rb +1 -1
- data/lib/teptris.rb +19 -11
- metadata +35 -3
- /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
|
+
}
|
data/lib/teptris/version.rb
CHANGED
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
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|