teptris 0.3.4.1 → 0.3.6.2
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 +17 -0
- data/ext/teptris_ext/engine/src/include/teptris/version.h +1 -1
- data/ext/teptris_ext/engine/src/teptris/emit/emit_shared.h +419 -0
- data/ext/teptris_ext/engine/src/teptris/emit/emitter.c +1 -404
- data/ext/teptris_ext/engine/src/teptris/emit/writer.c +575 -0
- data/ext/teptris_ext/materialize.c +125 -21
- data/lib/teptris/toml.rb +18 -0
- data/lib/teptris/version.rb +1 -1
- data/sig/teptris.rbs +6 -0
- metadata +4 -2
|
@@ -9,7 +9,15 @@
|
|
|
9
9
|
#define FMT_FORBID_TIME 2
|
|
10
10
|
#define FMT_FORBID_DATE 4
|
|
11
11
|
|
|
12
|
-
static VALUE eParseError, eError, cDate;
|
|
12
|
+
static VALUE eParseError, eError, cDate, cDoc;
|
|
13
|
+
/* rb_raise's varargs formatting segfaults in its format dispatch on
|
|
14
|
+
* windows x64-mingw-ucrt ruby 4.0 (diag #158/#160: the identical raise
|
|
15
|
+
* crashes across PGO on/off while direct exception construction runs
|
|
16
|
+
* clean, and every rb_exc_new site has always been fine) — build the
|
|
17
|
+
* message with snprintf and raise via rb_exc_new. */
|
|
18
|
+
static void raise_teptris(const char *msg) {
|
|
19
|
+
rb_exc_raise(rb_exc_new(eError, msg, (long)strlen(msg)));
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
/* civil date -> days since epoch (Hinnant, inverse of the dump side) */
|
|
15
23
|
static int64_t days_from_civil(int32_t y, uint8_t m, uint8_t d) {
|
|
@@ -223,14 +231,14 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags,
|
|
|
223
231
|
if (flags & FMT_STRING_DATES) return dt_string(&d, kind);
|
|
224
232
|
if (kind == TEPTRIS_DATETIME_OFFSET || kind == TEPTRIS_DATETIME_LOCAL) {
|
|
225
233
|
if (flags & FMT_FORBID_TIME)
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
raise_teptris("value materializes Time, which is not in "
|
|
235
|
+
"permitted_classes (use datetime_policy: :string)");
|
|
228
236
|
return time_from_dt(&d, kind == TEPTRIS_DATETIME_OFFSET);
|
|
229
237
|
}
|
|
230
238
|
if (kind == TEPTRIS_DATE_LOCAL) {
|
|
231
239
|
if (flags & FMT_FORBID_DATE)
|
|
232
|
-
|
|
233
|
-
|
|
240
|
+
raise_teptris("value materializes Date, which is not in "
|
|
241
|
+
"permitted_classes (use datetime_policy: :string)");
|
|
234
242
|
return rb_funcall(cDate, rb_intern("new"), 3,
|
|
235
243
|
INT2FIX(d.year), INT2FIX(d.month), INT2FIX(d.day));
|
|
236
244
|
}
|
|
@@ -297,6 +305,66 @@ static VALUE ext_load(int argc, VALUE *argv, VALUE self) {
|
|
|
297
305
|
return out;
|
|
298
306
|
}
|
|
299
307
|
|
|
308
|
+
/* ---------------- native-doc API: the retained DOM ----------------
|
|
309
|
+
* load_doc parses into an engine document the wrapper OWNS, together
|
|
310
|
+
* with a private copy of the input - the doc's keys/values are
|
|
311
|
+
* zero-copy views into that copy, so both live and die together.
|
|
312
|
+
* dump_doc serializes with the C emitter: the roundtrip
|
|
313
|
+
* dump_doc(load_doc(s)) never materializes Ruby objects. */
|
|
314
|
+
|
|
315
|
+
static void dump_check(teptris_status st);
|
|
316
|
+
|
|
317
|
+
typedef struct {
|
|
318
|
+
teptris_document *doc;
|
|
319
|
+
char *buf;
|
|
320
|
+
} doc_owner;
|
|
321
|
+
|
|
322
|
+
static void doc_owner_free(void *p) {
|
|
323
|
+
doc_owner *o = (doc_owner *)p;
|
|
324
|
+
if (o->doc != NULL) teptris_document_free(o->doc);
|
|
325
|
+
if (o->buf != NULL) xfree(o->buf);
|
|
326
|
+
xfree(o);
|
|
327
|
+
}
|
|
328
|
+
static size_t doc_owner_size(const void *p) { return sizeof(doc_owner); }
|
|
329
|
+
static const rb_data_type_t doc_owner_type = {
|
|
330
|
+
"TeptrisExt/Doc",
|
|
331
|
+
{0, doc_owner_free, doc_owner_size, NULL, 0},
|
|
332
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY};
|
|
333
|
+
|
|
334
|
+
static VALUE ext_load_doc(VALUE self, VALUE str) {
|
|
335
|
+
StringValue(str);
|
|
336
|
+
long len = RSTRING_LEN(str);
|
|
337
|
+
char *cbuf = ALLOC_N(char, (size_t)len + 1);
|
|
338
|
+
memcpy(cbuf, RSTRING_PTR(str), (size_t)len);
|
|
339
|
+
teptris_document *doc = NULL;
|
|
340
|
+
struct gvl_parse_arg arg = {cbuf, (size_t)len, &doc, TEPTRIS_OK};
|
|
341
|
+
rb_thread_call_without_gvl(parse_without_gvl, &arg, RUBY_UBF_IO, NULL);
|
|
342
|
+
if (arg.st != TEPTRIS_OK) {
|
|
343
|
+
const teptris_error *e = teptris_document_error(doc);
|
|
344
|
+
VALUE ex = rb_exc_new(eParseError, e->message, (long)strlen(e->message));
|
|
345
|
+
rb_iv_set(ex, "@line", SIZET2NUM(e->line));
|
|
346
|
+
rb_iv_set(ex, "@column", SIZET2NUM(e->column));
|
|
347
|
+
xfree(cbuf);
|
|
348
|
+
teptris_document_free(doc);
|
|
349
|
+
rb_exc_raise(ex);
|
|
350
|
+
}
|
|
351
|
+
doc_owner *o = ALLOC(doc_owner);
|
|
352
|
+
o->doc = doc;
|
|
353
|
+
o->buf = cbuf;
|
|
354
|
+
return TypedData_Wrap_Struct(cDoc, &doc_owner_type, o);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
static VALUE ext_dump_doc(VALUE self, VALUE rbdoc) {
|
|
358
|
+
doc_owner *o;
|
|
359
|
+
TypedData_Get_Struct(rbdoc, doc_owner, &doc_owner_type, o);
|
|
360
|
+
char *buf = NULL;
|
|
361
|
+
size_t len = 0;
|
|
362
|
+
dump_check(teptris_document_emit(o->doc, &buf, &len));
|
|
363
|
+
VALUE out = rb_utf8_str_new(buf, (long)len);
|
|
364
|
+
free(buf);
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
|
|
300
368
|
static VALUE ext_version(VALUE self) {
|
|
301
369
|
return rb_str_new_cstr(teptris_version_string());
|
|
302
370
|
}
|
|
@@ -305,11 +373,15 @@ static VALUE ext_version(VALUE self) {
|
|
|
305
373
|
|
|
306
374
|
static ID id_utc_offset, id_to_s, id_to_i, id_nsec;
|
|
307
375
|
|
|
376
|
+
|
|
308
377
|
static void dump_check(teptris_status st) {
|
|
309
378
|
if (st == TEPTRIS_OK) return;
|
|
310
379
|
if (st == TEPTRIS_ERR_ALLOC)
|
|
311
|
-
|
|
312
|
-
|
|
380
|
+
raise_teptris("out of memory while dumping");
|
|
381
|
+
char msg[128];
|
|
382
|
+
snprintf(msg, sizeof msg, "cannot dump value (%s)",
|
|
383
|
+
teptris_status_string(st));
|
|
384
|
+
raise_teptris(msg);
|
|
313
385
|
}
|
|
314
386
|
|
|
315
387
|
/* days since 1970-01-01 -> proleptic-Gregorian y/m/d (Hinnant) */
|
|
@@ -384,7 +456,9 @@ static void dump_scalar(teptris_builder *b, const char *key, size_t klen,
|
|
|
384
456
|
TEPTRIS_DATE_LOCAL, &dt));
|
|
385
457
|
return;
|
|
386
458
|
}
|
|
387
|
-
|
|
459
|
+
char msg[192];
|
|
460
|
+
snprintf(msg, sizeof msg, "cannot dump %s", rb_obj_classname(v));
|
|
461
|
+
raise_teptris(msg);
|
|
388
462
|
}
|
|
389
463
|
}
|
|
390
464
|
|
|
@@ -446,21 +520,28 @@ static VALUE dump_with(VALUE obj,
|
|
|
446
520
|
rb_obj_classname(obj));
|
|
447
521
|
teptris_builder *b = teptris_builder_new();
|
|
448
522
|
if (b == NULL)
|
|
449
|
-
|
|
523
|
+
raise_teptris("out of memory while dumping");
|
|
450
524
|
rb_hash_foreach(obj, table_each, (VALUE)b);
|
|
451
525
|
teptris_document *doc = NULL;
|
|
452
526
|
teptris_status st = teptris_builder_finish(b, &doc);
|
|
453
527
|
if (st != TEPTRIS_OK) {
|
|
454
528
|
teptris_builder_free(b);
|
|
455
|
-
|
|
529
|
+
char msg[128];
|
|
530
|
+
snprintf(msg, sizeof msg, "cannot dump (%s)",
|
|
531
|
+
teptris_status_string(st));
|
|
532
|
+
raise_teptris(msg);
|
|
456
533
|
}
|
|
457
534
|
char *buf = NULL;
|
|
458
535
|
size_t len = 0;
|
|
459
536
|
st = emit(doc, &buf, &len);
|
|
460
537
|
teptris_document_free(doc);
|
|
461
538
|
teptris_builder_free(b); /* doc already transferred/freed */
|
|
462
|
-
if (st != TEPTRIS_OK)
|
|
463
|
-
|
|
539
|
+
if (st != TEPTRIS_OK) {
|
|
540
|
+
char msg[128];
|
|
541
|
+
snprintf(msg, sizeof msg, "dump failed (%s)",
|
|
542
|
+
teptris_status_string(st));
|
|
543
|
+
raise_teptris(msg);
|
|
544
|
+
}
|
|
464
545
|
VALUE out = rb_enc_str_new(buf, (long)len, rb_utf8_encoding());
|
|
465
546
|
free(buf);
|
|
466
547
|
return out;
|
|
@@ -674,7 +755,7 @@ static VALUE ext_plan_build(VALUE self, VALUE rows, VALUE first_row)
|
|
|
674
755
|
long nrows = RARRAY_LEN(rows);
|
|
675
756
|
long nplans = RARRAY_LEN(first_row) - 1;
|
|
676
757
|
if (nplans < 1 || nrows < 1) {
|
|
677
|
-
|
|
758
|
+
raise_teptris("plan needs >= 1 plan and >= 1 row");
|
|
678
759
|
}
|
|
679
760
|
teptris_plan_row *crows = calloc((size_t)nrows, sizeof(*crows));
|
|
680
761
|
/* bytes-vs-count: this must be (nplans+1) * sizeof(uint32_t) — the
|
|
@@ -684,7 +765,7 @@ static VALUE ext_plan_build(VALUE self, VALUE rows, VALUE first_row)
|
|
|
684
765
|
if (crows == NULL || cfirst == NULL) {
|
|
685
766
|
free(crows);
|
|
686
767
|
free(cfirst);
|
|
687
|
-
|
|
768
|
+
raise_teptris("out of memory building plan");
|
|
688
769
|
}
|
|
689
770
|
for (long i = 0; i < nrows; i++) {
|
|
690
771
|
VALUE row = rb_ary_entry(rows, i);
|
|
@@ -705,7 +786,9 @@ static VALUE ext_plan_build(VALUE self, VALUE rows, VALUE first_row)
|
|
|
705
786
|
free(crows);
|
|
706
787
|
free(cfirst);
|
|
707
788
|
if (plan == NULL) {
|
|
708
|
-
|
|
789
|
+
char msg[96];
|
|
790
|
+
snprintf(msg, sizeof msg, "invalid plan spec (status %d)", (int)st);
|
|
791
|
+
raise_teptris(msg);
|
|
709
792
|
}
|
|
710
793
|
return TypedData_Wrap_Struct(rb_cObject, &plan_type, plan);
|
|
711
794
|
}
|
|
@@ -730,7 +813,9 @@ static VALUE ext_plan_emit(VALUE self, VALUE rb_plan, VALUE toml)
|
|
|
730
813
|
teptris_plan_walk(plan, teptris_document_root(doc), &st);
|
|
731
814
|
if (res == NULL) {
|
|
732
815
|
teptris_document_free(doc);
|
|
733
|
-
|
|
816
|
+
char msg[96];
|
|
817
|
+
snprintf(msg, sizeof msg, "plan walk failed (status %d)", (int)st);
|
|
818
|
+
raise_teptris(msg);
|
|
734
819
|
}
|
|
735
820
|
VALUE out = asm_rows(plan, res, 0);
|
|
736
821
|
teptris_plan_result_free(res);
|
|
@@ -866,7 +951,7 @@ static VALUE lazy_each(VALUE self) {
|
|
|
866
951
|
break;
|
|
867
952
|
}
|
|
868
953
|
default:
|
|
869
|
-
|
|
954
|
+
raise_teptris("scalar node has nothing to iterate");
|
|
870
955
|
}
|
|
871
956
|
return self;
|
|
872
957
|
}
|
|
@@ -897,7 +982,7 @@ static VALUE lazy_get(VALUE self, VALUE key) {
|
|
|
897
982
|
break;
|
|
898
983
|
}
|
|
899
984
|
default:
|
|
900
|
-
|
|
985
|
+
raise_teptris("scalar node is not subscriptable");
|
|
901
986
|
}
|
|
902
987
|
if (child == NULL) return Qnil;
|
|
903
988
|
return lazy_wrap(v->owner, child);
|
|
@@ -1011,7 +1096,12 @@ static VALUE ext_load_lazy_batch(VALUE self, VALUE strings) {
|
|
|
1011
1096
|
long n = RARRAY_LEN(strings);
|
|
1012
1097
|
if (n == 0) return rb_ary_new();
|
|
1013
1098
|
batch_scratch *s = batch_scratch_alloc(n);
|
|
1014
|
-
if (s == NULL)
|
|
1099
|
+
if (s == NULL) {
|
|
1100
|
+
char msg[96];
|
|
1101
|
+
snprintf(msg, sizeof msg,
|
|
1102
|
+
"out of memory batching %ld documents", n);
|
|
1103
|
+
raise_teptris(msg);
|
|
1104
|
+
}
|
|
1015
1105
|
/* the batch parse is pure C: pin every input (one tmp lock per
|
|
1016
1106
|
* string - repeated objects dup; frozen strings cannot take the
|
|
1017
1107
|
* lock, yet compaction can move them) and release the GVL for the
|
|
@@ -1163,7 +1253,12 @@ static VALUE ext_load_batch(int argc, VALUE *argv, VALUE self) {
|
|
|
1163
1253
|
if (n == 0) return rb_ary_new();
|
|
1164
1254
|
unsigned flags = flags_from_opts(opts);
|
|
1165
1255
|
batch_scratch *s = batch_scratch_alloc(n);
|
|
1166
|
-
if (s == NULL)
|
|
1256
|
+
if (s == NULL) {
|
|
1257
|
+
char msg[96];
|
|
1258
|
+
snprintf(msg, sizeof msg,
|
|
1259
|
+
"out of memory batching %ld documents", n);
|
|
1260
|
+
raise_teptris(msg);
|
|
1261
|
+
}
|
|
1167
1262
|
for (long i = 0; i < n; i++) {
|
|
1168
1263
|
VALUE str = rb_ary_entry(strings, i);
|
|
1169
1264
|
StringValue(str);
|
|
@@ -1175,7 +1270,10 @@ static VALUE ext_load_batch(int argc, VALUE *argv, VALUE self) {
|
|
|
1175
1270
|
s->docs, s->statuses);
|
|
1176
1271
|
if (batch != TEPTRIS_OK) {
|
|
1177
1272
|
batch_scratch_free(s);
|
|
1178
|
-
|
|
1273
|
+
char msg[96];
|
|
1274
|
+
snprintf(msg, sizeof msg, "batch parse failed (status %d)",
|
|
1275
|
+
(int)batch);
|
|
1276
|
+
raise_teptris(msg);
|
|
1179
1277
|
}
|
|
1180
1278
|
long first_fail = -1;
|
|
1181
1279
|
for (long i = 0; i < n; i++) {
|
|
@@ -1209,7 +1307,13 @@ void Init_teptris_ext(void) {
|
|
|
1209
1307
|
* call state (key cache, arenas, types) is per-object or per-call */
|
|
1210
1308
|
rb_ext_ractor_safe(true);
|
|
1211
1309
|
VALUE m = rb_define_module("TeptrisExt");
|
|
1310
|
+
cDoc = rb_define_class_under(m, "Doc", rb_cObject);
|
|
1311
|
+
/* opaque handle: no Ruby-side allocation (silences the
|
|
1312
|
+
* allocator-undefine warning on reload) */
|
|
1313
|
+
rb_undef_alloc_func(cDoc);
|
|
1212
1314
|
rb_define_module_function(m, "load", ext_load, -1);
|
|
1315
|
+
rb_define_module_function(m, "load_doc", ext_load_doc, 1);
|
|
1316
|
+
rb_define_module_function(m, "dump_doc", ext_dump_doc, 1);
|
|
1213
1317
|
rb_define_module_function(m, "load_lazy", ext_load_lazy, 1);
|
|
1214
1318
|
rb_define_module_function(m, "load_batch", ext_load_batch, -1);
|
|
1215
1319
|
rb_define_module_function(m, "load_slice", ext_load_slice, -1);
|
data/lib/teptris/toml.rb
CHANGED
|
@@ -18,6 +18,24 @@ module Teptris::TOML
|
|
|
18
18
|
TeptrisExt.load(toml)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Parse into a RETAINED engine document (an opaque TeptrisExt::Doc
|
|
22
|
+
# handle; no Ruby objects are materialized). Pair with dump_doc for
|
|
23
|
+
# a roundtrip that runs entirely at C speed - reserialization of
|
|
24
|
+
# an unchanged document skips both Ruby-object walks. The doc owns
|
|
25
|
+
# a private copy of the input: its values are views into it, so
|
|
26
|
+
# mutating the source string afterwards is invisible (and the doc
|
|
27
|
+
# keeps the copy alive).
|
|
28
|
+
def load_doc(toml)
|
|
29
|
+
raise ArgumentError, "input must be a String" unless toml.is_a?(String)
|
|
30
|
+
|
|
31
|
+
TeptrisExt.load_doc(toml)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Emit a load_doc() document at C speed (canonical TOML).
|
|
35
|
+
def dump_doc(doc)
|
|
36
|
+
TeptrisExt.dump_doc(doc)
|
|
37
|
+
end
|
|
38
|
+
|
|
21
39
|
# Safe-load shaped entry point for frameworks: permitted_classes
|
|
22
40
|
# restricts datetime materialization (raise when a value would
|
|
23
41
|
# materialize an unpermitted class); datetime_policy :string keeps
|
data/lib/teptris/version.rb
CHANGED
data/sig/teptris.rbs
CHANGED
|
@@ -22,6 +22,8 @@ module Teptris
|
|
|
22
22
|
# Time for local datetimes, Date for dates, "HH:MM:SS" String for
|
|
23
23
|
# local times.
|
|
24
24
|
def self.load: (String toml) -> Hash[String, untyped]
|
|
25
|
+
def self.load_doc: (String toml) -> untyped
|
|
26
|
+
def self.dump_doc: (untyped doc) -> String
|
|
25
27
|
|
|
26
28
|
def self.load_file: (String path) -> Hash[String, untyped]
|
|
27
29
|
|
|
@@ -122,6 +124,10 @@ module TeptrisExt
|
|
|
122
124
|
|
|
123
125
|
def self.load_lazy_batch: (Array[String] tomls) -> Array[LazyValue]
|
|
124
126
|
|
|
127
|
+
def self.load_doc: (String toml) -> untyped
|
|
128
|
+
|
|
129
|
+
def self.dump_doc: (untyped doc) -> String
|
|
130
|
+
|
|
125
131
|
def self.dump: (untyped obj) -> String
|
|
126
132
|
|
|
127
133
|
def self.dump_json_natural: (untyped obj) -> String
|
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.3.
|
|
4
|
+
version: 0.3.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- teptris contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-21 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.
|
|
@@ -39,8 +39,10 @@ files:
|
|
|
39
39
|
- ext/teptris_ext/engine/src/teptris/common/simd.h
|
|
40
40
|
- ext/teptris_ext/engine/src/teptris/dom/dom.c
|
|
41
41
|
- ext/teptris_ext/engine/src/teptris/dom/dom.h
|
|
42
|
+
- ext/teptris_ext/engine/src/teptris/emit/emit_shared.h
|
|
42
43
|
- ext/teptris_ext/engine/src/teptris/emit/emitter.c
|
|
43
44
|
- ext/teptris_ext/engine/src/teptris/emit/emitter.h
|
|
45
|
+
- ext/teptris_ext/engine/src/teptris/emit/writer.c
|
|
44
46
|
- ext/teptris_ext/engine/src/teptris/memory/arena.c
|
|
45
47
|
- ext/teptris_ext/engine/src/teptris/memory/arena.h
|
|
46
48
|
- ext/teptris_ext/engine/src/teptris/parse/datetime.c
|