teptris 0.2.42-arm-linux → 0.2.48-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/README.md +1 -1
- data/ext/teptris_ext/engine/src/include/teptris/teptris.h +13 -0
- data/ext/teptris_ext/engine/src/include/teptris/version.h +2 -2
- data/ext/teptris_ext/engine/src/teptris/api.c +53 -0
- data/ext/teptris_ext/materialize.c +481 -21
- data/lib/teptris/3.3/teptris_ext.so +0 -0
- data/lib/teptris/3.4/teptris_ext.so +0 -0
- data/lib/teptris/4.0/teptris_ext.so +0 -0
- data/lib/teptris/toml.rb +61 -0
- data/lib/teptris/version.rb +1 -1
- metadata +6 -4
- data/lib/teptris/3.2/teptris_ext.so +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37c4b4597737ec0da5db36671c56a9d8f8654de5fae121cc460032a6c9ddad40
|
|
4
|
+
data.tar.gz: 02e41f249fde41675bcf6d67103dfde81573957a4166961678cee168f39f0fa3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c21eed21fd021093018a1d6d1cb42d8483b5c31a9d9a3fa4cd02ddcc4f579f56060471437b9cf87f14498a3128b433d54de646b19cb447d30adaf31f196161b
|
|
7
|
+
data.tar.gz: 4849d8e86c3a4a057b871e55f45cd08ab83bada7c85e6367016d685121627bba3c7e6090e38bfc755f5a4e3b23dbfa49670e17add5b02c395d1ea30c7cd1aa03
|
|
@@ -91,6 +91,19 @@ TEPTRIS_API teptris_status teptris_parse(const char *data, size_t len,
|
|
|
91
91
|
const teptris_options *opts,
|
|
92
92
|
teptris_document **out);
|
|
93
93
|
|
|
94
|
+
/* Parse N TOML documents in a single call. Caller pre-allocates
|
|
95
|
+
* `out_docs[N]` and `out_statuses[N]` and frees each doc with
|
|
96
|
+
* teptris_document_free. Per-document parse failures are reported
|
|
97
|
+
* through out_statuses[i] (out_docs[i] is set so the caller can read
|
|
98
|
+
* the error via teptris_document_error and free). TEPTRIS_ERR_ALLOC
|
|
99
|
+
* means no documents were created — caller must not free. The `opts`
|
|
100
|
+
* pointer is shared across the batch. n == 0 is TEPTRIS_OK and a
|
|
101
|
+
* no-op. */
|
|
102
|
+
TEPTRIS_API teptris_status teptris_parse_batch(
|
|
103
|
+
const char *const *data, const size_t *lens, size_t n,
|
|
104
|
+
const teptris_options *opts,
|
|
105
|
+
teptris_document **out_docs, teptris_status *out_statuses);
|
|
106
|
+
|
|
94
107
|
/* Memory: frees every allocation reachable from the document. */
|
|
95
108
|
TEPTRIS_API void teptris_document_free(teptris_document *doc);
|
|
96
109
|
|
|
@@ -34,6 +34,59 @@ teptris_status teptris_parse(const char *data, size_t len,
|
|
|
34
34
|
return st;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
teptris_status teptris_parse_batch(const char *const *data, const size_t *lens,
|
|
38
|
+
size_t n, const teptris_options *opts,
|
|
39
|
+
teptris_document **out_docs,
|
|
40
|
+
teptris_status *out_statuses)
|
|
41
|
+
{
|
|
42
|
+
if (n == 0) {
|
|
43
|
+
return TEPTRIS_OK;
|
|
44
|
+
}
|
|
45
|
+
if (data == NULL || lens == NULL || out_docs == NULL ||
|
|
46
|
+
out_statuses == NULL) {
|
|
47
|
+
return TEPTRIS_ERR_ARG;
|
|
48
|
+
}
|
|
49
|
+
/* Zero the per-doc outputs up front: on the alloc-fail path every
|
|
50
|
+
* entry must look like "no doc to free" so callers don't double-free
|
|
51
|
+
* a partial batch. */
|
|
52
|
+
for (size_t i = 0; i < n; i++) {
|
|
53
|
+
out_docs[i] = NULL;
|
|
54
|
+
out_statuses[i] = TEPTRIS_OK;
|
|
55
|
+
}
|
|
56
|
+
uint32_t max_depth =
|
|
57
|
+
(opts != NULL && opts->max_depth != 0) ? opts->max_depth : 512;
|
|
58
|
+
for (size_t i = 0; i < n; i++) {
|
|
59
|
+
teptris_document *doc = calloc(1, sizeof(teptris_document));
|
|
60
|
+
if (doc == NULL) {
|
|
61
|
+
/* Free any documents we already created; statuses for the
|
|
62
|
+
* failed slot onward were zero-initialized, so out_statuses
|
|
63
|
+
* stays consistent. */
|
|
64
|
+
for (size_t j = 0; j < i; j++) {
|
|
65
|
+
teptris_arena_destroy(&out_docs[j]->arena);
|
|
66
|
+
free(out_docs[j]);
|
|
67
|
+
out_docs[j] = NULL;
|
|
68
|
+
}
|
|
69
|
+
return TEPTRIS_ERR_ALLOC;
|
|
70
|
+
}
|
|
71
|
+
teptris_arena_init(&doc->arena);
|
|
72
|
+
doc->max_depth = max_depth;
|
|
73
|
+
doc->err.status = TEPTRIS_OK;
|
|
74
|
+
doc->err.message = "";
|
|
75
|
+
teptris_status st = teptris_parser_run(doc, data[i], lens[i]);
|
|
76
|
+
if (st != TEPTRIS_OK && doc->err.status == TEPTRIS_OK) {
|
|
77
|
+
doc->err.status = st;
|
|
78
|
+
if (st == TEPTRIS_ERR_ALLOC) {
|
|
79
|
+
snprintf(doc->err_msg, sizeof(doc->err_msg),
|
|
80
|
+
"out of memory");
|
|
81
|
+
}
|
|
82
|
+
doc->err.message = doc->err_msg;
|
|
83
|
+
}
|
|
84
|
+
out_docs[i] = doc;
|
|
85
|
+
out_statuses[i] = st;
|
|
86
|
+
}
|
|
87
|
+
return TEPTRIS_OK;
|
|
88
|
+
}
|
|
89
|
+
|
|
37
90
|
void teptris_document_free(teptris_document *doc)
|
|
38
91
|
{
|
|
39
92
|
if (doc == NULL) {
|
|
@@ -21,18 +21,25 @@ static int64_t days_from_civil(int32_t y, uint8_t m, uint8_t d) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/* exact (no double) Time materialization: nsec stays nsec. Offset
|
|
24
|
-
* datetimes
|
|
25
|
-
* Time.local with Rational seconds (
|
|
26
|
-
*
|
|
24
|
+
* datetimes carry a fixed utc offset via rb_time_num_new; local
|
|
25
|
+
* datetimes go through Time.local with Rational seconds (both treat
|
|
26
|
+
* the epoch value as wall-clock for locals / instant+offset for
|
|
27
|
+
* fixed zones). No struct timespec crosses the Ruby boundary:
|
|
28
|
+
* armhf glibc rubies build with _TIME_BITS=64 (16-byte timespec)
|
|
29
|
+
* while an ext compiled without it lays out 8 bytes — the struct
|
|
30
|
+
* APIs then read (load) or write (dump) past each side's layout. */
|
|
27
31
|
static VALUE time_from_dt(const teptris_datetime *d, bool has_offset) {
|
|
28
32
|
if (has_offset) {
|
|
29
33
|
int64_t days = days_from_civil(d->year, d->month, d->day);
|
|
30
34
|
int64_t secs =
|
|
31
35
|
days * 86400 + d->hour * 3600 + d->minute * 60 + d->second;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
VALUE num = LL2NUM(secs - d->offset_seconds);
|
|
37
|
+
if (d->nanosecond != 0) {
|
|
38
|
+
num = rb_funcall(num, '+', 1,
|
|
39
|
+
rb_Rational(LL2NUM((int64_t)d->nanosecond),
|
|
40
|
+
LL2NUM(1000000000)));
|
|
41
|
+
}
|
|
42
|
+
return rb_time_num_new(num, INT2FIX(d->offset_seconds));
|
|
36
43
|
}
|
|
37
44
|
VALUE args[6] = {INT2FIX(d->year), INT2FIX(d->month), INT2FIX(d->day),
|
|
38
45
|
INT2FIX(d->hour), INT2FIX(d->minute),
|
|
@@ -51,7 +58,19 @@ static VALUE dt_string(const teptris_datetime *d, int kind) {
|
|
|
51
58
|
const char *head = (kind == TEPTRIS_TIME_LOCAL) ? "" : "";
|
|
52
59
|
(void)head;
|
|
53
60
|
if (kind == TEPTRIS_TIME_LOCAL) {
|
|
54
|
-
|
|
61
|
+
/* tomlib's contract keeps the fraction at millisecond
|
|
62
|
+
* precision (00:32:00.999); drop it entirely when zero.
|
|
63
|
+
* The engine preserves full nanoseconds — the binding
|
|
64
|
+
* truncates to the reference shape. */
|
|
65
|
+
if (d->nanosecond != 0) {
|
|
66
|
+
unsigned ms = (d->nanosecond + 500000u) / 1000000u;
|
|
67
|
+
if (ms == 1000u) ms = 999u; /* rounding overflow guard */
|
|
68
|
+
l = snprintf(buf, sizeof buf, "%02u:%02u:%02u.%03u",
|
|
69
|
+
d->hour, d->minute, d->second, ms);
|
|
70
|
+
} else {
|
|
71
|
+
l = snprintf(buf, sizeof buf, "%02u:%02u:%02u",
|
|
72
|
+
d->hour, d->minute, d->second);
|
|
73
|
+
}
|
|
55
74
|
} else {
|
|
56
75
|
l = snprintf(buf, sizeof buf, "%04d-%02u-%02uT%02u:%02u:%02u",
|
|
57
76
|
d->year, d->month, d->day, d->hour, d->minute, d->second);
|
|
@@ -70,7 +89,87 @@ static VALUE dt_string(const teptris_datetime *d, int kind) {
|
|
|
70
89
|
return rb_enc_str_new(buf, l, rb_utf8_encoding());
|
|
71
90
|
}
|
|
72
91
|
|
|
73
|
-
|
|
92
|
+
/* ---- per-load table-key cache ------------------------------------------------
|
|
93
|
+
* TOML keys repeat within a document and across a batch; a fresh String
|
|
94
|
+
* per occurrence costs an allocation and a full Hash re-hash each time.
|
|
95
|
+
* This cache maps key CONTENT to one frozen String, shared everywhere:
|
|
96
|
+
* repeated keys become a single GC-pinned object with its hash already
|
|
97
|
+
* computed. It is document-local (dies with the load call), capped at
|
|
98
|
+
* KC_CAP entries so unique-key corpora pay only a cheap failed probe
|
|
99
|
+
* after the cap, and never touches the VM's global fstring table (the
|
|
100
|
+
* intern-always attempt regressed there — teptris-ruby#122: unique
|
|
101
|
+
* keys make the intern table pure overhead). Strings are frozen so the
|
|
102
|
+
* sharing cannot alias a mutation. NULL cache = fresh strings (the
|
|
103
|
+
* low-volume schema-RAW path). */
|
|
104
|
+
|
|
105
|
+
#define KC_CAP 4096
|
|
106
|
+
#define KC_SLOTS (KC_CAP * 2) /* stays <=50% full: probes stay ~2 */
|
|
107
|
+
|
|
108
|
+
typedef struct {
|
|
109
|
+
VALUE str; /* Qfalse = empty slot */
|
|
110
|
+
} kc_slot;
|
|
111
|
+
|
|
112
|
+
typedef struct {
|
|
113
|
+
VALUE keep; /* Ruby Array pinning every cached String */
|
|
114
|
+
kc_slot *slots;
|
|
115
|
+
size_t used;
|
|
116
|
+
} keycache;
|
|
117
|
+
|
|
118
|
+
static uint64_t kc_hash(const char *p, size_t len)
|
|
119
|
+
{
|
|
120
|
+
uint64_t h = 1469598103934665603ULL;
|
|
121
|
+
for (size_t i = 0; i < len; i++) {
|
|
122
|
+
h ^= (unsigned char)p[i];
|
|
123
|
+
h *= 1099511628211ULL;
|
|
124
|
+
}
|
|
125
|
+
return h;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static void kc_init(keycache *kc)
|
|
129
|
+
{
|
|
130
|
+
kc->keep = rb_ary_new_capa(64);
|
|
131
|
+
kc->slots = calloc(KC_SLOTS, sizeof(kc_slot)); /* zero = Qfalse empty */
|
|
132
|
+
if (kc->slots == NULL) {
|
|
133
|
+
/* no cache is a correctness-preserving fallback */
|
|
134
|
+
kc->keep = Qnil;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
kc->used = 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static void kc_free(keycache *kc)
|
|
141
|
+
{
|
|
142
|
+
free(kc->slots);
|
|
143
|
+
kc->slots = NULL;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static VALUE kc_key(keycache *kc, const char *p, size_t len)
|
|
147
|
+
{
|
|
148
|
+
if (kc == NULL || kc->slots == NULL) {
|
|
149
|
+
return rb_enc_str_new(p, (long)len, rb_utf8_encoding());
|
|
150
|
+
}
|
|
151
|
+
size_t mask = KC_SLOTS - 1;
|
|
152
|
+
size_t slot = (size_t)kc_hash(p, len) & mask;
|
|
153
|
+
while (kc->slots[slot].str != Qfalse) {
|
|
154
|
+
VALUE s = kc->slots[slot].str;
|
|
155
|
+
if ((size_t)RSTRING_LEN(s) == len &&
|
|
156
|
+
memcmp(RSTRING_PTR(s), p, len) == 0) {
|
|
157
|
+
return s; /* hit: one shared object, hash already cached */
|
|
158
|
+
}
|
|
159
|
+
slot = (slot + 1) & mask;
|
|
160
|
+
}
|
|
161
|
+
VALUE fresh = rb_enc_str_new(p, (long)len, rb_utf8_encoding());
|
|
162
|
+
rb_obj_freeze(fresh);
|
|
163
|
+
rb_ary_push(kc->keep, fresh); /* GC pin before it enters the slots */
|
|
164
|
+
if (kc->used < KC_CAP) {
|
|
165
|
+
kc->slots[slot].str = fresh;
|
|
166
|
+
kc->used++;
|
|
167
|
+
}
|
|
168
|
+
return fresh;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static VALUE obj_from_node(const teptris_node *n, unsigned flags,
|
|
172
|
+
keycache *kc) {
|
|
74
173
|
switch (teptris_node_kind(n)) {
|
|
75
174
|
case TEPTRIS_TABLE: {
|
|
76
175
|
size_t len = teptris_node_table_length(n);
|
|
@@ -78,9 +177,8 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
|
|
|
78
177
|
for (size_t i = 0; i < len; i++) {
|
|
79
178
|
teptris_view key;
|
|
80
179
|
const teptris_node *v = teptris_node_table_at(n, i, &key);
|
|
81
|
-
rb_hash_aset(h,
|
|
82
|
-
|
|
83
|
-
obj_from_node(v, flags));
|
|
180
|
+
rb_hash_aset(h, kc_key(kc, key.ptr, (size_t)key.len),
|
|
181
|
+
obj_from_node(v, flags, kc));
|
|
84
182
|
}
|
|
85
183
|
return h;
|
|
86
184
|
}
|
|
@@ -88,7 +186,7 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
|
|
|
88
186
|
size_t len = teptris_node_array_length(n);
|
|
89
187
|
VALUE a = rb_ary_new_capa((long)len);
|
|
90
188
|
for (size_t i = 0; i < len; i++)
|
|
91
|
-
rb_ary_push(a, obj_from_node(teptris_node_array_at(n, i), flags));
|
|
189
|
+
rb_ary_push(a, obj_from_node(teptris_node_array_at(n, i), flags, kc));
|
|
92
190
|
return a;
|
|
93
191
|
}
|
|
94
192
|
case TEPTRIS_STRING: {
|
|
@@ -157,7 +255,10 @@ static VALUE ext_load(int argc, VALUE *argv, VALUE self) {
|
|
|
157
255
|
if (RTEST(fd)) flags |= FMT_FORBID_DATE;
|
|
158
256
|
}
|
|
159
257
|
teptris_document *doc = parse_or_raise(str);
|
|
160
|
-
|
|
258
|
+
keycache kc;
|
|
259
|
+
kc_init(&kc);
|
|
260
|
+
VALUE out = obj_from_node(teptris_document_root(doc), flags, &kc);
|
|
261
|
+
kc_free(&kc);
|
|
161
262
|
teptris_document_free(doc);
|
|
162
263
|
return out;
|
|
163
264
|
}
|
|
@@ -168,7 +269,7 @@ static VALUE ext_version(VALUE self) {
|
|
|
168
269
|
|
|
169
270
|
/* ------------------------------------------------------------------ dump */
|
|
170
271
|
|
|
171
|
-
static ID id_utc_offset, id_to_s;
|
|
272
|
+
static ID id_utc_offset, id_to_s, id_to_i, id_nsec;
|
|
172
273
|
|
|
173
274
|
static void dump_check(teptris_status st) {
|
|
174
275
|
if (st == TEPTRIS_OK) return;
|
|
@@ -194,12 +295,12 @@ static void civil_from_days(int64_t z, int32_t *y, uint8_t *m, uint8_t *d) {
|
|
|
194
295
|
*d = (uint8_t)dd;
|
|
195
296
|
}
|
|
196
297
|
|
|
197
|
-
/*
|
|
198
|
-
*
|
|
298
|
+
/* the instant and its subsecond part come back as Integers (to_i
|
|
299
|
+
* floors, nsec stays in [0, 1e9) — same split the timespec gave);
|
|
300
|
+
* the civil fields are integer math on epoch + offset */
|
|
199
301
|
static void fill_dt_from_time(VALUE v, teptris_datetime *dt) {
|
|
200
|
-
struct timespec ts = rb_time_timespec(v);
|
|
201
302
|
dt->offset_seconds = NUM2INT(rb_funcall(v, id_utc_offset, 0));
|
|
202
|
-
int64_t secs = (
|
|
303
|
+
int64_t secs = NUM2LL(rb_funcall(v, id_to_i, 0)) + dt->offset_seconds;
|
|
203
304
|
int64_t days = secs / 86400;
|
|
204
305
|
int32_t rem = (int32_t)(secs % 86400);
|
|
205
306
|
if (rem < 0) { /* floor division: keep rem in [0, 86400) */
|
|
@@ -210,7 +311,7 @@ static void fill_dt_from_time(VALUE v, teptris_datetime *dt) {
|
|
|
210
311
|
dt->hour = (uint8_t)(rem / 3600);
|
|
211
312
|
dt->minute = (uint8_t)((rem / 60) % 60);
|
|
212
313
|
dt->second = (uint8_t)(rem % 60);
|
|
213
|
-
dt->nanosecond = (uint32_t)
|
|
314
|
+
dt->nanosecond = (uint32_t)NUM2LL(rb_funcall(v, id_nsec, 0));
|
|
214
315
|
}
|
|
215
316
|
|
|
216
317
|
static void dump_scalar(teptris_builder *b, const char *key, size_t klen,
|
|
@@ -507,7 +608,7 @@ static VALUE asm_rows(const teptris_plan *p, const teptris_plan_result *res,
|
|
|
507
608
|
case TEPTRIS_PLAN_RAW_RESULT: {
|
|
508
609
|
const teptris_node *raw = teptris_plan_result_raw_at(res, i);
|
|
509
610
|
if (raw != NULL) {
|
|
510
|
-
val = obj_from_node(raw, 0);
|
|
611
|
+
val = obj_from_node(raw, 0, NULL);
|
|
511
612
|
}
|
|
512
613
|
break;
|
|
513
614
|
}
|
|
@@ -590,9 +691,366 @@ static VALUE ext_plan_emit(VALUE self, VALUE rb_plan, VALUE toml)
|
|
|
590
691
|
return out;
|
|
591
692
|
}
|
|
592
693
|
|
|
694
|
+
|
|
695
|
+
/* ---- lazy mode (#79) ----------------------------------------------
|
|
696
|
+
* load_lazy returns a LazyValue wrapping the parsed tree: scalar
|
|
697
|
+
* access materializes scalars only, tables/arrays stay wrapped until
|
|
698
|
+
* walked. The document and the borrowed-input String live in a
|
|
699
|
+
* single owner object every wrapper references, so GC lifetime is
|
|
700
|
+
* one edge deep. Free order is GC-driven via TypedData dfree. */
|
|
701
|
+
|
|
702
|
+
static VALUE cLazyOwner, cLazyValue;
|
|
703
|
+
|
|
704
|
+
typedef struct {
|
|
705
|
+
teptris_document *doc;
|
|
706
|
+
VALUE input; /* the borrowed views' buffer */
|
|
707
|
+
} lazy_owner;
|
|
708
|
+
|
|
709
|
+
typedef struct {
|
|
710
|
+
VALUE owner; /* LazyOwner: keeps doc + input alive */
|
|
711
|
+
const teptris_node *node;
|
|
712
|
+
} lazy_value;
|
|
713
|
+
|
|
714
|
+
static void lazy_owner_free(void *p) {
|
|
715
|
+
lazy_owner *o = (lazy_owner *)p;
|
|
716
|
+
if (o->doc != NULL) teptris_document_free(o->doc);
|
|
717
|
+
xfree(o);
|
|
718
|
+
}
|
|
719
|
+
static size_t lazy_owner_size(const void *p) { return sizeof(lazy_owner); }
|
|
720
|
+
static const rb_data_type_t lazy_owner_type = {
|
|
721
|
+
"TeptrisExt/LazyOwner",
|
|
722
|
+
{NULL, lazy_owner_free, lazy_owner_size, NULL, 0},
|
|
723
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY};
|
|
724
|
+
|
|
725
|
+
static void lazy_value_mark(void *p) {
|
|
726
|
+
lazy_value *v = (lazy_value *)p;
|
|
727
|
+
rb_gc_mark(v->owner);
|
|
728
|
+
}
|
|
729
|
+
static void lazy_value_free(void *p) { xfree(p); }
|
|
730
|
+
static size_t lazy_value_size(const void *p) { return sizeof(lazy_value); }
|
|
731
|
+
static const rb_data_type_t lazy_value_type = {
|
|
732
|
+
"TeptrisExt/LazyValue",
|
|
733
|
+
{lazy_value_mark, lazy_value_free, lazy_value_size, NULL, 0},
|
|
734
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY};
|
|
735
|
+
|
|
736
|
+
static VALUE lazy_wrap(VALUE owner, const teptris_node *n) {
|
|
737
|
+
lazy_value *v;
|
|
738
|
+
VALUE obj = TypedData_Make_Struct(cLazyValue, lazy_value,
|
|
739
|
+
&lazy_value_type, v);
|
|
740
|
+
v->owner = owner;
|
|
741
|
+
v->node = n;
|
|
742
|
+
return obj;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
static lazy_value *lazy_self(VALUE self) {
|
|
746
|
+
lazy_value *v;
|
|
747
|
+
TypedData_Get_Struct(self, lazy_value, &lazy_value_type, v);
|
|
748
|
+
return v;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
static VALUE ext_load_lazy(VALUE self, VALUE toml) {
|
|
752
|
+
StringValue(toml);
|
|
753
|
+
lazy_owner *o;
|
|
754
|
+
VALUE owner = TypedData_Make_Struct(cLazyOwner, lazy_owner,
|
|
755
|
+
&lazy_owner_type, o);
|
|
756
|
+
o->doc = NULL;
|
|
757
|
+
o->input = rb_str_new_frozen(toml); /* borrowed views: own a copy */
|
|
758
|
+
teptris_status st = teptris_parse(RSTRING_PTR(o->input),
|
|
759
|
+
(size_t)RSTRING_LEN(o->input),
|
|
760
|
+
NULL, &o->doc);
|
|
761
|
+
if (st != TEPTRIS_OK) {
|
|
762
|
+
const teptris_error *e = teptris_document_error(o->doc);
|
|
763
|
+
VALUE ex = rb_exc_new(eParseError, e->message,
|
|
764
|
+
(long)strlen(e->message));
|
|
765
|
+
rb_iv_set(ex, "@line", SIZET2NUM(e->line));
|
|
766
|
+
rb_iv_set(ex, "@column", SIZET2NUM(e->column));
|
|
767
|
+
rb_exc_raise(ex); /* owner GC-frees the failed doc */
|
|
768
|
+
}
|
|
769
|
+
return lazy_wrap(owner, teptris_document_root(o->doc));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
static VALUE lazy_kind(VALUE self) {
|
|
773
|
+
switch (teptris_node_kind(lazy_self(self)->node)) {
|
|
774
|
+
case TEPTRIS_TABLE: return ID2SYM(rb_intern("table"));
|
|
775
|
+
case TEPTRIS_ARRAY: return ID2SYM(rb_intern("array"));
|
|
776
|
+
default: return ID2SYM(rb_intern("scalar"));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
static VALUE lazy_size(VALUE self) {
|
|
781
|
+
const teptris_node *n = lazy_self(self)->node;
|
|
782
|
+
switch (teptris_node_kind(n)) {
|
|
783
|
+
case TEPTRIS_TABLE: return SIZET2NUM(teptris_node_table_length(n));
|
|
784
|
+
case TEPTRIS_ARRAY: return SIZET2NUM(teptris_node_array_length(n));
|
|
785
|
+
default: return Qnil;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/* table: iterate entries; array: iterate items; scalar: raise */
|
|
790
|
+
static VALUE lazy_each(VALUE self) {
|
|
791
|
+
RETURN_SIZED_ENUMERATOR(self, 0, 0, 0);
|
|
792
|
+
const teptris_node *n = lazy_self(self)->node;
|
|
793
|
+
switch (teptris_node_kind(n)) {
|
|
794
|
+
case TEPTRIS_TABLE: {
|
|
795
|
+
size_t len = teptris_node_table_length(n);
|
|
796
|
+
for (size_t i = 0; i < len; i++) {
|
|
797
|
+
teptris_view key;
|
|
798
|
+
const teptris_node *v = teptris_node_table_at(n, i, &key);
|
|
799
|
+
VALUE kv = rb_ary_new_capa(2);
|
|
800
|
+
rb_ary_push(kv, rb_enc_str_new(key.ptr, (long)key.len,
|
|
801
|
+
rb_utf8_encoding()));
|
|
802
|
+
rb_ary_push(kv, lazy_wrap(lazy_self(self)->owner, v));
|
|
803
|
+
rb_yield(kv);
|
|
804
|
+
}
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
case TEPTRIS_ARRAY: {
|
|
808
|
+
size_t len = teptris_node_array_length(n);
|
|
809
|
+
for (size_t i = 0; i < len; i++)
|
|
810
|
+
rb_yield(lazy_wrap(lazy_self(self)->owner,
|
|
811
|
+
teptris_node_array_at(n, i)));
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
default:
|
|
815
|
+
rb_raise(eError, "scalar node has nothing to iterate");
|
|
816
|
+
}
|
|
817
|
+
return self;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/* [] is polymorphic by node kind: tables take string keys (hash
|
|
821
|
+
* index), arrays take integer indices (contiguous array) - both O(1)
|
|
822
|
+
* per access, which is the whole point of the lazy mode */
|
|
823
|
+
static VALUE lazy_get(VALUE self, VALUE key) {
|
|
824
|
+
lazy_value *v = lazy_self(self);
|
|
825
|
+
const teptris_node *child = NULL;
|
|
826
|
+
switch (teptris_node_kind(v->node)) {
|
|
827
|
+
case TEPTRIS_TABLE: {
|
|
828
|
+
const char *k;
|
|
829
|
+
long kl;
|
|
830
|
+
if (RB_TYPE_P(key, T_STRING)) {
|
|
831
|
+
k = RSTRING_PTR(key); kl = RSTRING_LEN(key);
|
|
832
|
+
} else {
|
|
833
|
+
key = rb_funcall(key, rb_intern("to_s"), 0);
|
|
834
|
+
k = RSTRING_PTR(key); kl = RSTRING_LEN(key);
|
|
835
|
+
}
|
|
836
|
+
child = teptris_node_table_get(v->node, k, (size_t)kl);
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
case TEPTRIS_ARRAY: {
|
|
840
|
+
long i = NUM2LONG(key);
|
|
841
|
+
if (i < 0) i += (long)teptris_node_array_length(v->node);
|
|
842
|
+
child = teptris_node_array_at(v->node, (size_t)i);
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
default:
|
|
846
|
+
rb_raise(eError, "scalar node is not subscriptable");
|
|
847
|
+
}
|
|
848
|
+
if (child == NULL) return Qnil;
|
|
849
|
+
return lazy_wrap(v->owner, child);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
static VALUE lazy_dig(int argc, VALUE *argv, VALUE self) {
|
|
853
|
+
VALUE cur = self;
|
|
854
|
+
for (long i = 0; i < argc; i++) {
|
|
855
|
+
VALUE nxt = rb_funcall(cur, rb_intern("[]"), 1, argv[i]);
|
|
856
|
+
if (nxt == Qnil || nxt == Qundef) return Qnil;
|
|
857
|
+
cur = nxt;
|
|
858
|
+
}
|
|
859
|
+
return cur;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/* full materialization: the eager path, owned */
|
|
863
|
+
static VALUE lazy_to(VALUE self) {
|
|
864
|
+
lazy_value *v = lazy_self(self);
|
|
865
|
+
keycache kc;
|
|
866
|
+
kc_init(&kc);
|
|
867
|
+
VALUE out = obj_from_node(v->node, 0, &kc);
|
|
868
|
+
kc_free(&kc);
|
|
869
|
+
return out;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/* ---------------------------------------------------------------- batch --
|
|
873
|
+
* Many-small batch path (teptris-ruby#108 ask 3, complements the C
|
|
874
|
+
* `teptris_parse_batch`): accept an Array of TOML strings + an options
|
|
875
|
+
* hash, parse every entry in one C call (one opts struct, one
|
|
876
|
+
* per-doc-status report), and return one Ruby Hash per document. The
|
|
877
|
+
* options bag is parsed ONCE and shared across the batch — per-doc
|
|
878
|
+
* safe_load/datetime_policy work happens in C, not in N Ruby frames.
|
|
879
|
+
* On the alloc-fail path the C call leaves no partial docs; the Ruby
|
|
880
|
+
* caller sees a Teptris::Error. Per-doc parse failures are collected
|
|
881
|
+
* alongside the successes in a parallel array (the lazy variant
|
|
882
|
+
* raises on the first failure so callers can `rescue` without losing
|
|
883
|
+
* the partial batch). */
|
|
884
|
+
|
|
885
|
+
/* Combined scratch layout — one alloc, four arrays packed end-to-end.
|
|
886
|
+
* Splitting into four mallocs showed up as the dominant cost in the
|
|
887
|
+
* per-doc benchmark for tiny docs (4× malloc/free per call, 16–64 KB
|
|
888
|
+
* of scratch per call). One alloc amortizes the heap and the cache. */
|
|
889
|
+
typedef struct {
|
|
890
|
+
const char **data;
|
|
891
|
+
size_t *lens;
|
|
892
|
+
teptris_document **docs;
|
|
893
|
+
teptris_status *statuses;
|
|
894
|
+
} batch_scratch;
|
|
895
|
+
|
|
896
|
+
static void batch_scratch_free(batch_scratch *s) {
|
|
897
|
+
free(s->data);
|
|
898
|
+
free(s);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
static batch_scratch *batch_scratch_alloc(long n) {
|
|
902
|
+
batch_scratch *s = (batch_scratch *)malloc(sizeof(*s));
|
|
903
|
+
if (s == NULL) return NULL;
|
|
904
|
+
size_t bytes =
|
|
905
|
+
(size_t)n * (sizeof(const char *) + sizeof(size_t) +
|
|
906
|
+
sizeof(teptris_document *) + sizeof(teptris_status));
|
|
907
|
+
void *blob = malloc(bytes);
|
|
908
|
+
if (blob == NULL) {
|
|
909
|
+
free(s);
|
|
910
|
+
return NULL;
|
|
911
|
+
}
|
|
912
|
+
char *p = (char *)blob;
|
|
913
|
+
s->data = (const char **)p;
|
|
914
|
+
p += (size_t)n * sizeof(const char *);
|
|
915
|
+
s->lens = (size_t *)p;
|
|
916
|
+
p += (size_t)n * sizeof(size_t);
|
|
917
|
+
s->docs = (teptris_document **)p;
|
|
918
|
+
p += (size_t)n * sizeof(teptris_document *);
|
|
919
|
+
s->statuses = (teptris_status *)p;
|
|
920
|
+
return s;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
static unsigned flags_from_opts(VALUE opts) {
|
|
924
|
+
unsigned flags = 0;
|
|
925
|
+
if (NIL_P(opts)) return 0;
|
|
926
|
+
VALUE sd = rb_hash_aref(opts, ID2SYM(rb_intern("string_datetimes")));
|
|
927
|
+
VALUE ft = rb_hash_aref(opts, ID2SYM(rb_intern("forbid_time")));
|
|
928
|
+
VALUE fd = rb_hash_aref(opts, ID2SYM(rb_intern("forbid_date")));
|
|
929
|
+
if (RTEST(sd)) flags |= FMT_STRING_DATES;
|
|
930
|
+
if (RTEST(ft)) flags |= FMT_FORBID_TIME;
|
|
931
|
+
if (RTEST(fd)) flags |= FMT_FORBID_DATE;
|
|
932
|
+
return flags;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/* Parse-only batch (returns an Array of Lazy wrappers, one per doc):
|
|
936
|
+
* eager parse, deferred materialization. Same per-doc error semantics
|
|
937
|
+
* as ext_load_lazy. */
|
|
938
|
+
static VALUE ext_load_lazy_batch(VALUE self, VALUE strings) {
|
|
939
|
+
Check_Type(strings, T_ARRAY);
|
|
940
|
+
long n = RARRAY_LEN(strings);
|
|
941
|
+
if (n == 0) return rb_ary_new();
|
|
942
|
+
batch_scratch *s = batch_scratch_alloc(n);
|
|
943
|
+
if (s == NULL) rb_raise(eError, "out of memory batching %ld documents", n);
|
|
944
|
+
for (long i = 0; i < n; i++) {
|
|
945
|
+
VALUE str = rb_ary_entry(strings, i);
|
|
946
|
+
StringValue(str); /* raises TypeError if not coercible */
|
|
947
|
+
s->data[i] = RSTRING_PTR(str);
|
|
948
|
+
s->lens[i] = (size_t)RSTRING_LEN(str);
|
|
949
|
+
}
|
|
950
|
+
teptris_status batch =
|
|
951
|
+
teptris_parse_batch(s->data, s->lens, (size_t)n, NULL,
|
|
952
|
+
s->docs, s->statuses);
|
|
953
|
+
if (batch != TEPTRIS_OK) {
|
|
954
|
+
batch_scratch_free(s);
|
|
955
|
+
rb_raise(eError, "batch parse failed (status %d)", (int)batch);
|
|
956
|
+
}
|
|
957
|
+
long first_fail = -1;
|
|
958
|
+
for (long i = 0; i < n; i++) {
|
|
959
|
+
if (s->statuses[i] != TEPTRIS_OK && first_fail == -1) first_fail = i;
|
|
960
|
+
}
|
|
961
|
+
if (first_fail != -1) {
|
|
962
|
+
const teptris_error *e = teptris_document_error(s->docs[first_fail]);
|
|
963
|
+
VALUE ex =
|
|
964
|
+
rb_exc_new(eParseError, e->message, (long)strlen(e->message));
|
|
965
|
+
rb_iv_set(ex, "@line", SIZET2NUM(e->line));
|
|
966
|
+
rb_iv_set(ex, "@column", SIZET2NUM(e->column));
|
|
967
|
+
for (long i = 0; i < n; i++) teptris_document_free(s->docs[i]);
|
|
968
|
+
batch_scratch_free(s);
|
|
969
|
+
rb_exc_raise(ex);
|
|
970
|
+
}
|
|
971
|
+
VALUE out = rb_ary_new_capa(n);
|
|
972
|
+
for (long i = 0; i < n; i++) {
|
|
973
|
+
lazy_owner *o;
|
|
974
|
+
VALUE owner = TypedData_Make_Struct(cLazyOwner, lazy_owner,
|
|
975
|
+
&lazy_owner_type, o);
|
|
976
|
+
o->doc = s->docs[i];
|
|
977
|
+
o->input = rb_ary_entry(strings, i);
|
|
978
|
+
rb_ary_push(out, lazy_wrap(owner, teptris_document_root(s->docs[i])));
|
|
979
|
+
}
|
|
980
|
+
batch_scratch_free(s);
|
|
981
|
+
return out;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/* Eager batch: returns Array of Hashes with the same datetime contract
|
|
985
|
+
* as ext_load. opts is parsed once; safe_load semantics apply uniformly.
|
|
986
|
+
* The caller's input Array is read-only; its strings stay referenced
|
|
987
|
+
* for the duration of the C call (no separate keeper Array). */
|
|
988
|
+
static VALUE ext_load_batch(int argc, VALUE *argv, VALUE self) {
|
|
989
|
+
VALUE strings, opts;
|
|
990
|
+
rb_scan_args(argc, argv, "11", &strings, &opts);
|
|
991
|
+
Check_Type(strings, T_ARRAY);
|
|
992
|
+
long n = RARRAY_LEN(strings);
|
|
993
|
+
if (n == 0) return rb_ary_new();
|
|
994
|
+
unsigned flags = flags_from_opts(opts);
|
|
995
|
+
batch_scratch *s = batch_scratch_alloc(n);
|
|
996
|
+
if (s == NULL) rb_raise(eError, "out of memory batching %ld documents", n);
|
|
997
|
+
for (long i = 0; i < n; i++) {
|
|
998
|
+
VALUE str = rb_ary_entry(strings, i);
|
|
999
|
+
StringValue(str);
|
|
1000
|
+
s->data[i] = RSTRING_PTR(str);
|
|
1001
|
+
s->lens[i] = (size_t)RSTRING_LEN(str);
|
|
1002
|
+
}
|
|
1003
|
+
teptris_status batch =
|
|
1004
|
+
teptris_parse_batch(s->data, s->lens, (size_t)n, NULL,
|
|
1005
|
+
s->docs, s->statuses);
|
|
1006
|
+
if (batch != TEPTRIS_OK) {
|
|
1007
|
+
batch_scratch_free(s);
|
|
1008
|
+
rb_raise(eError, "batch parse failed (status %d)", (int)batch);
|
|
1009
|
+
}
|
|
1010
|
+
long first_fail = -1;
|
|
1011
|
+
for (long i = 0; i < n; i++) {
|
|
1012
|
+
if (s->statuses[i] != TEPTRIS_OK && first_fail == -1) first_fail = i;
|
|
1013
|
+
}
|
|
1014
|
+
if (first_fail != -1) {
|
|
1015
|
+
const teptris_error *e = teptris_document_error(s->docs[first_fail]);
|
|
1016
|
+
VALUE ex =
|
|
1017
|
+
rb_exc_new(eParseError, e->message, (long)strlen(e->message));
|
|
1018
|
+
rb_iv_set(ex, "@line", SIZET2NUM(e->line));
|
|
1019
|
+
rb_iv_set(ex, "@column", SIZET2NUM(e->column));
|
|
1020
|
+
for (long i = 0; i < n; i++) teptris_document_free(s->docs[i]);
|
|
1021
|
+
batch_scratch_free(s);
|
|
1022
|
+
rb_exc_raise(ex);
|
|
1023
|
+
}
|
|
1024
|
+
VALUE out = rb_ary_new_capa(n);
|
|
1025
|
+
keycache kc;
|
|
1026
|
+
kc_init(&kc); /* one cache across the batch: keys repeat across docs */
|
|
1027
|
+
for (long i = 0; i < n; i++) {
|
|
1028
|
+
const teptris_node *root = teptris_document_root(s->docs[i]);
|
|
1029
|
+
rb_ary_store(out, i, obj_from_node(root, flags, &kc));
|
|
1030
|
+
teptris_document_free(s->docs[i]);
|
|
1031
|
+
}
|
|
1032
|
+
kc_free(&kc);
|
|
1033
|
+
batch_scratch_free(s);
|
|
1034
|
+
return out;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
593
1037
|
void Init_teptris_ext(void) {
|
|
594
1038
|
VALUE m = rb_define_module("TeptrisExt");
|
|
595
1039
|
rb_define_module_function(m, "load", ext_load, -1);
|
|
1040
|
+
rb_define_module_function(m, "load_lazy", ext_load_lazy, 1);
|
|
1041
|
+
rb_define_module_function(m, "load_batch", ext_load_batch, -1);
|
|
1042
|
+
rb_define_module_function(m, "load_lazy_batch", ext_load_lazy_batch, 1);
|
|
1043
|
+
cLazyOwner = rb_define_class_under(m, "LazyOwner", rb_cObject);
|
|
1044
|
+
cLazyValue = rb_define_class_under(m, "LazyValue", rb_cObject);
|
|
1045
|
+
rb_define_method(cLazyValue, "kind", lazy_kind, 0);
|
|
1046
|
+
rb_define_method(cLazyValue, "size", lazy_size, 0);
|
|
1047
|
+
rb_define_method(cLazyValue, "each", lazy_each, 0);
|
|
1048
|
+
rb_define_method(cLazyValue, "[]", lazy_get, 1);
|
|
1049
|
+
rb_define_method(cLazyValue, "at", lazy_get, 1);
|
|
1050
|
+
rb_define_method(cLazyValue, "dig", lazy_dig, -1);
|
|
1051
|
+
rb_define_method(cLazyValue, "to_h", lazy_to, 0);
|
|
1052
|
+
rb_define_method(cLazyValue, "to_a", lazy_to, 0);
|
|
1053
|
+
rb_define_method(cLazyValue, "value", lazy_to, 0);
|
|
596
1054
|
rb_define_module_function(m, "dump", ext_dump, 1);
|
|
597
1055
|
rb_define_module_function(m, "engine_version", ext_version, 0);
|
|
598
1056
|
rb_define_module_function(m, "plan_build", ext_plan_build, 2);
|
|
@@ -605,4 +1063,6 @@ void Init_teptris_ext(void) {
|
|
|
605
1063
|
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
|
606
1064
|
id_utc_offset = rb_intern("utc_offset");
|
|
607
1065
|
id_to_s = rb_intern("to_s");
|
|
1066
|
+
id_to_i = rb_intern("to_i");
|
|
1067
|
+
id_nsec = rb_intern("nsec");
|
|
608
1068
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/teptris/toml.rb
CHANGED
|
@@ -36,6 +36,42 @@ module Teptris
|
|
|
36
36
|
TeptrisExt.load(toml, opts)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Lazy load: returns a wrapper that parses eagerly (one pass)
|
|
40
|
+
# but materializes host objects only along the paths actually
|
|
41
|
+
# accessed. Same datetime contract as load; to_h/to_a flatten to
|
|
42
|
+
# the eager shape.
|
|
43
|
+
def load_lazy(toml)
|
|
44
|
+
raise ArgumentError, "input must be a String" unless toml.is_a?(String)
|
|
45
|
+
|
|
46
|
+
TeptrisExt.load_lazy(toml)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Many-small batch path (teptris-ruby#108 ask 3): parse N inputs
|
|
50
|
+
# in one C call, share the safe_load/datetime_policy options
|
|
51
|
+
# struct across the batch, return one eager Hash per document.
|
|
52
|
+
# Same datetime contract as load. A failing document raises
|
|
53
|
+
# Teptris::ParseError carrying the first failure's line/column.
|
|
54
|
+
def load_batch(tomls, safe_load: nil, datetime_policy: :native)
|
|
55
|
+
_validate_batch!(tomls, safe_load, datetime_policy)
|
|
56
|
+
TeptrisExt.load_batch(tomls, _safe_load_opts(safe_load, datetime_policy))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Lazy twin of load_batch: parse N inputs eagerly, hand back an
|
|
60
|
+
# Array of Lazy wrappers — materialization happens only on access.
|
|
61
|
+
# Same per-doc error semantics as load_lazy (first failure raises).
|
|
62
|
+
def load_lazy_batch(tomls)
|
|
63
|
+
_validate_batch!(tomls, nil, :native)
|
|
64
|
+
TeptrisExt.load_lazy_batch(tomls)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Many-small path with file paths: read each file, then call
|
|
68
|
+
# load_batch. Symmetric with load_file's single-doc shape.
|
|
69
|
+
def load_files(paths, safe_load: nil, datetime_policy: :native)
|
|
70
|
+
_validate_batch!(paths, safe_load, datetime_policy, kind: :paths)
|
|
71
|
+
load_batch(paths.map { |p| File.read(p) },
|
|
72
|
+
safe_load: safe_load, datetime_policy: datetime_policy)
|
|
73
|
+
end
|
|
74
|
+
|
|
39
75
|
# One-pass schema materialization over a compiled
|
|
40
76
|
# Teptris::Descriptor: unplanned keys are never materialized
|
|
41
77
|
# (teptris#46).
|
|
@@ -61,6 +97,31 @@ module Teptris
|
|
|
61
97
|
def dump(obj)
|
|
62
98
|
TeptrisExt.dump(obj)
|
|
63
99
|
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def _validate_batch!(input, safe_load, datetime_policy, kind: :strings)
|
|
104
|
+
unless input.is_a?(Array)
|
|
105
|
+
raise ArgumentError, "expected Array of #{kind}, got #{input.class}"
|
|
106
|
+
end
|
|
107
|
+
if !safe_load.nil? && !safe_load.is_a?(Array) && !safe_load.is_a?(Symbol)
|
|
108
|
+
raise ArgumentError, "safe_load must be nil, an Array, or a Symbol"
|
|
109
|
+
end
|
|
110
|
+
unless %i[native string].include?(datetime_policy)
|
|
111
|
+
raise ArgumentError, "datetime_policy must be :native or :string"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def _safe_load_opts(safe_load, datetime_policy)
|
|
116
|
+
opts = {}
|
|
117
|
+
opts[:string_datetimes] = true if datetime_policy == :string
|
|
118
|
+
if safe_load && datetime_policy == :native
|
|
119
|
+
permitted = Array(safe_load) + [:__scalar]
|
|
120
|
+
opts[:forbid_time] = true unless permitted.include?(Time)
|
|
121
|
+
opts[:forbid_date] = true unless permitted.include?(Date)
|
|
122
|
+
end
|
|
123
|
+
opts.empty? ? nil : opts
|
|
124
|
+
end
|
|
64
125
|
end
|
|
65
126
|
end
|
|
66
127
|
end
|
data/lib/teptris/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: teptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.48
|
|
5
5
|
platform: arm-linux
|
|
6
6
|
authors:
|
|
7
7
|
- teptris contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: A native C extension over libteptris, a C99 TOML 1.1 parser/emitter.
|
|
14
14
|
API shape mirrors the tomlib gem.
|
|
@@ -51,7 +51,9 @@ files:
|
|
|
51
51
|
- ext/teptris_ext/extconf.rb
|
|
52
52
|
- ext/teptris_ext/materialize.c
|
|
53
53
|
- lib/teptris.rb
|
|
54
|
-
- lib/teptris/3.
|
|
54
|
+
- lib/teptris/3.3/teptris_ext.so
|
|
55
|
+
- lib/teptris/3.4/teptris_ext.so
|
|
56
|
+
- lib/teptris/4.0/teptris_ext.so
|
|
55
57
|
- lib/teptris/descriptor.rb
|
|
56
58
|
- lib/teptris/error.rb
|
|
57
59
|
- lib/teptris/toml.rb
|
|
@@ -75,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
75
77
|
- !ruby/object:Gem::Version
|
|
76
78
|
version: '0'
|
|
77
79
|
requirements: []
|
|
78
|
-
rubygems_version: 3.
|
|
80
|
+
rubygems_version: 3.5.22
|
|
79
81
|
signing_key:
|
|
80
82
|
specification_version: 4
|
|
81
83
|
summary: TOML for Ruby at libteptris speed (native C extension)
|
|
Binary file
|