teptris 0.2.43-aarch64-linux → 0.2.45-aarch64-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac18ac8a6cebdd4a8c472afa9a733942405a254240b3b275773d26024c5a38de
4
- data.tar.gz: 3cec224be95f07f9c7da0d2c45ca7696fff224485d5a34c68b285cdb13acb922
3
+ metadata.gz: 1764c42e95d80a0e2f467eff03795b059907ea88e2fec262f27cd6662c781e1b
4
+ data.tar.gz: c232f96abc11a711763e570d9395a9580be9a3c60ed1edd7206adbb1f84d8d09
5
5
  SHA512:
6
- metadata.gz: 8693b62b569b6c5d58da145c9401d7564aa088a8dfd93c096c1c68cbdb6ec75006e3a7218aa698824fff4877928a4660ccafb70fd0c610ed23aa316c87e18761
7
- data.tar.gz: e061031c34434624d8029eafc6b6822e3d29a79fb9e2e6f5e3db8db475f7d68cca49a343be73121b200004e0ba78da8dafb4bed7e1d3883600d067e161e4fd6e
6
+ metadata.gz: 29b39762ba7081b0c3e4c84cdec49e9ab8cbc97ce67399cbb402543221f1e10c922a57d497d6936441cacc96b920b29e1f2af864d3183ac5d6d44490154ee4da
7
+ data.tar.gz: 62285a34f34dca9a84d7667ee60ac3a37b1592a8284c1deccf10687cc6e60aa974262b6f9f5b578fd277bc5c9c75bc7907a850fd65a185bf8515ecaff0d20875
@@ -1,4 +1,4 @@
1
- # libteptris 0.1.24 — vendored source
1
+ # libteptris 0.1.26 — vendored source
2
2
 
3
3
  This directory carries the COMPLETE C source of the engine this
4
4
  gem's extension was built from (the packaging doctrine: compiled
@@ -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
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  #define TEPTRIS_VERSION_MAJOR 0
5
5
  #define TEPTRIS_VERSION_MINOR 1
6
- #define TEPTRIS_VERSION_PATCH 24
7
- #define TEPTRIS_VERSION_STRING "0.1.24"
6
+ #define TEPTRIS_VERSION_PATCH 25
7
+ #define TEPTRIS_VERSION_STRING "0.1.25"
8
8
 
9
9
  #endif /* TEPTRIS_VERSION_H */
@@ -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) {
@@ -51,7 +51,19 @@ static VALUE dt_string(const teptris_datetime *d, int kind) {
51
51
  const char *head = (kind == TEPTRIS_TIME_LOCAL) ? "" : "";
52
52
  (void)head;
53
53
  if (kind == TEPTRIS_TIME_LOCAL) {
54
- l = snprintf(buf, sizeof buf, "%02u:%02u:%02u", d->hour, d->minute, d->second);
54
+ /* tomlib's contract keeps the fraction at millisecond
55
+ * precision (00:32:00.999); drop it entirely when zero.
56
+ * The engine preserves full nanoseconds — the binding
57
+ * truncates to the reference shape. */
58
+ if (d->nanosecond != 0) {
59
+ unsigned ms = (d->nanosecond + 500000u) / 1000000u;
60
+ if (ms == 1000u) ms = 999u; /* rounding overflow guard */
61
+ l = snprintf(buf, sizeof buf, "%02u:%02u:%02u.%03u",
62
+ d->hour, d->minute, d->second, ms);
63
+ } else {
64
+ l = snprintf(buf, sizeof buf, "%02u:%02u:%02u",
65
+ d->hour, d->minute, d->second);
66
+ }
55
67
  } else {
56
68
  l = snprintf(buf, sizeof buf, "%04d-%02u-%02uT%02u:%02u:%02u",
57
69
  d->year, d->month, d->day, d->hour, d->minute, d->second);
@@ -590,9 +602,359 @@ static VALUE ext_plan_emit(VALUE self, VALUE rb_plan, VALUE toml)
590
602
  return out;
591
603
  }
592
604
 
605
+
606
+ /* ---- lazy mode (#79) ----------------------------------------------
607
+ * load_lazy returns a LazyValue wrapping the parsed tree: scalar
608
+ * access materializes scalars only, tables/arrays stay wrapped until
609
+ * walked. The document and the borrowed-input String live in a
610
+ * single owner object every wrapper references, so GC lifetime is
611
+ * one edge deep. Free order is GC-driven via TypedData dfree. */
612
+
613
+ static VALUE cLazyOwner, cLazyValue;
614
+
615
+ typedef struct {
616
+ teptris_document *doc;
617
+ VALUE input; /* the borrowed views' buffer */
618
+ } lazy_owner;
619
+
620
+ typedef struct {
621
+ VALUE owner; /* LazyOwner: keeps doc + input alive */
622
+ const teptris_node *node;
623
+ } lazy_value;
624
+
625
+ static void lazy_owner_free(void *p) {
626
+ lazy_owner *o = (lazy_owner *)p;
627
+ if (o->doc != NULL) teptris_document_free(o->doc);
628
+ xfree(o);
629
+ }
630
+ static size_t lazy_owner_size(const void *p) { return sizeof(lazy_owner); }
631
+ static const rb_data_type_t lazy_owner_type = {
632
+ "TeptrisExt/LazyOwner",
633
+ {NULL, lazy_owner_free, lazy_owner_size, NULL, 0},
634
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY};
635
+
636
+ static void lazy_value_mark(void *p) {
637
+ lazy_value *v = (lazy_value *)p;
638
+ rb_gc_mark(v->owner);
639
+ }
640
+ static void lazy_value_free(void *p) { xfree(p); }
641
+ static size_t lazy_value_size(const void *p) { return sizeof(lazy_value); }
642
+ static const rb_data_type_t lazy_value_type = {
643
+ "TeptrisExt/LazyValue",
644
+ {lazy_value_mark, lazy_value_free, lazy_value_size, NULL, 0},
645
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY};
646
+
647
+ static VALUE lazy_wrap(VALUE owner, const teptris_node *n) {
648
+ lazy_value *v;
649
+ VALUE obj = TypedData_Make_Struct(cLazyValue, lazy_value,
650
+ &lazy_value_type, v);
651
+ v->owner = owner;
652
+ v->node = n;
653
+ return obj;
654
+ }
655
+
656
+ static lazy_value *lazy_self(VALUE self) {
657
+ lazy_value *v;
658
+ TypedData_Get_Struct(self, lazy_value, &lazy_value_type, v);
659
+ return v;
660
+ }
661
+
662
+ static VALUE ext_load_lazy(VALUE self, VALUE toml) {
663
+ StringValue(toml);
664
+ lazy_owner *o;
665
+ VALUE owner = TypedData_Make_Struct(cLazyOwner, lazy_owner,
666
+ &lazy_owner_type, o);
667
+ o->doc = NULL;
668
+ o->input = rb_str_new_frozen(toml); /* borrowed views: own a copy */
669
+ teptris_status st = teptris_parse(RSTRING_PTR(o->input),
670
+ (size_t)RSTRING_LEN(o->input),
671
+ NULL, &o->doc);
672
+ if (st != TEPTRIS_OK) {
673
+ const teptris_error *e = teptris_document_error(o->doc);
674
+ VALUE ex = rb_exc_new(eParseError, e->message,
675
+ (long)strlen(e->message));
676
+ rb_iv_set(ex, "@line", SIZET2NUM(e->line));
677
+ rb_iv_set(ex, "@column", SIZET2NUM(e->column));
678
+ rb_exc_raise(ex); /* owner GC-frees the failed doc */
679
+ }
680
+ return lazy_wrap(owner, teptris_document_root(o->doc));
681
+ }
682
+
683
+ static VALUE lazy_kind(VALUE self) {
684
+ switch (teptris_node_kind(lazy_self(self)->node)) {
685
+ case TEPTRIS_TABLE: return ID2SYM(rb_intern("table"));
686
+ case TEPTRIS_ARRAY: return ID2SYM(rb_intern("array"));
687
+ default: return ID2SYM(rb_intern("scalar"));
688
+ }
689
+ }
690
+
691
+ static VALUE lazy_size(VALUE self) {
692
+ const teptris_node *n = lazy_self(self)->node;
693
+ switch (teptris_node_kind(n)) {
694
+ case TEPTRIS_TABLE: return SIZET2NUM(teptris_node_table_length(n));
695
+ case TEPTRIS_ARRAY: return SIZET2NUM(teptris_node_array_length(n));
696
+ default: return Qnil;
697
+ }
698
+ }
699
+
700
+ /* table: iterate entries; array: iterate items; scalar: raise */
701
+ static VALUE lazy_each(VALUE self) {
702
+ RETURN_SIZED_ENUMERATOR(self, 0, 0, 0);
703
+ const teptris_node *n = lazy_self(self)->node;
704
+ switch (teptris_node_kind(n)) {
705
+ case TEPTRIS_TABLE: {
706
+ size_t len = teptris_node_table_length(n);
707
+ for (size_t i = 0; i < len; i++) {
708
+ teptris_view key;
709
+ const teptris_node *v = teptris_node_table_at(n, i, &key);
710
+ VALUE kv = rb_ary_new_capa(2);
711
+ rb_ary_push(kv, rb_enc_str_new(key.ptr, (long)key.len,
712
+ rb_utf8_encoding()));
713
+ rb_ary_push(kv, lazy_wrap(lazy_self(self)->owner, v));
714
+ rb_yield(kv);
715
+ }
716
+ break;
717
+ }
718
+ case TEPTRIS_ARRAY: {
719
+ size_t len = teptris_node_array_length(n);
720
+ for (size_t i = 0; i < len; i++)
721
+ rb_yield(lazy_wrap(lazy_self(self)->owner,
722
+ teptris_node_array_at(n, i)));
723
+ break;
724
+ }
725
+ default:
726
+ rb_raise(eError, "scalar node has nothing to iterate");
727
+ }
728
+ return self;
729
+ }
730
+
731
+ /* [] is polymorphic by node kind: tables take string keys (hash
732
+ * index), arrays take integer indices (contiguous array) - both O(1)
733
+ * per access, which is the whole point of the lazy mode */
734
+ static VALUE lazy_get(VALUE self, VALUE key) {
735
+ lazy_value *v = lazy_self(self);
736
+ const teptris_node *child = NULL;
737
+ switch (teptris_node_kind(v->node)) {
738
+ case TEPTRIS_TABLE: {
739
+ const char *k;
740
+ long kl;
741
+ if (RB_TYPE_P(key, T_STRING)) {
742
+ k = RSTRING_PTR(key); kl = RSTRING_LEN(key);
743
+ } else {
744
+ key = rb_funcall(key, rb_intern("to_s"), 0);
745
+ k = RSTRING_PTR(key); kl = RSTRING_LEN(key);
746
+ }
747
+ child = teptris_node_table_get(v->node, k, (size_t)kl);
748
+ break;
749
+ }
750
+ case TEPTRIS_ARRAY: {
751
+ long i = NUM2LONG(key);
752
+ if (i < 0) i += (long)teptris_node_array_length(v->node);
753
+ child = teptris_node_array_at(v->node, (size_t)i);
754
+ break;
755
+ }
756
+ default:
757
+ rb_raise(eError, "scalar node is not subscriptable");
758
+ }
759
+ if (child == NULL) return Qnil;
760
+ return lazy_wrap(v->owner, child);
761
+ }
762
+
763
+ static VALUE lazy_dig(int argc, VALUE *argv, VALUE self) {
764
+ VALUE cur = self;
765
+ for (long i = 0; i < argc; i++) {
766
+ VALUE nxt = rb_funcall(cur, rb_intern("[]"), 1, argv[i]);
767
+ if (nxt == Qnil || nxt == Qundef) return Qnil;
768
+ cur = nxt;
769
+ }
770
+ return cur;
771
+ }
772
+
773
+ /* full materialization: the eager path, owned */
774
+ static VALUE lazy_to(VALUE self) {
775
+ lazy_value *v = lazy_self(self);
776
+ return obj_from_node(v->node, 0);
777
+ }
778
+
779
+ /* ---------------------------------------------------------------- batch --
780
+ * Many-small batch path (teptris-ruby#108 ask 3, complements the C
781
+ * `teptris_parse_batch`): accept an Array of TOML strings + an options
782
+ * hash, parse every entry in one C call (one opts struct, one
783
+ * per-doc-status report), and return one Ruby Hash per document. The
784
+ * options bag is parsed ONCE and shared across the batch — per-doc
785
+ * safe_load/datetime_policy work happens in C, not in N Ruby frames.
786
+ * On the alloc-fail path the C call leaves no partial docs; the Ruby
787
+ * caller sees a Teptris::Error. Per-doc parse failures are collected
788
+ * alongside the successes in a parallel array (the lazy variant
789
+ * raises on the first failure so callers can `rescue` without losing
790
+ * the partial batch). */
791
+
792
+ /* Combined scratch layout — one alloc, four arrays packed end-to-end.
793
+ * Splitting into four mallocs showed up as the dominant cost in the
794
+ * per-doc benchmark for tiny docs (4× malloc/free per call, 16–64 KB
795
+ * of scratch per call). One alloc amortizes the heap and the cache. */
796
+ typedef struct {
797
+ const char **data;
798
+ size_t *lens;
799
+ teptris_document **docs;
800
+ teptris_status *statuses;
801
+ } batch_scratch;
802
+
803
+ static void batch_scratch_free(batch_scratch *s) {
804
+ free(s->data);
805
+ free(s);
806
+ }
807
+
808
+ static batch_scratch *batch_scratch_alloc(long n) {
809
+ batch_scratch *s = (batch_scratch *)malloc(sizeof(*s));
810
+ if (s == NULL) return NULL;
811
+ size_t bytes =
812
+ (size_t)n * (sizeof(const char *) + sizeof(size_t) +
813
+ sizeof(teptris_document *) + sizeof(teptris_status));
814
+ void *blob = malloc(bytes);
815
+ if (blob == NULL) {
816
+ free(s);
817
+ return NULL;
818
+ }
819
+ char *p = (char *)blob;
820
+ s->data = (const char **)p;
821
+ p += (size_t)n * sizeof(const char *);
822
+ s->lens = (size_t *)p;
823
+ p += (size_t)n * sizeof(size_t);
824
+ s->docs = (teptris_document **)p;
825
+ p += (size_t)n * sizeof(teptris_document *);
826
+ s->statuses = (teptris_status *)p;
827
+ return s;
828
+ }
829
+
830
+ static unsigned flags_from_opts(VALUE opts) {
831
+ unsigned flags = 0;
832
+ if (NIL_P(opts)) return 0;
833
+ VALUE sd = rb_hash_aref(opts, ID2SYM(rb_intern("string_datetimes")));
834
+ VALUE ft = rb_hash_aref(opts, ID2SYM(rb_intern("forbid_time")));
835
+ VALUE fd = rb_hash_aref(opts, ID2SYM(rb_intern("forbid_date")));
836
+ if (RTEST(sd)) flags |= FMT_STRING_DATES;
837
+ if (RTEST(ft)) flags |= FMT_FORBID_TIME;
838
+ if (RTEST(fd)) flags |= FMT_FORBID_DATE;
839
+ return flags;
840
+ }
841
+
842
+ /* Parse-only batch (returns an Array of Lazy wrappers, one per doc):
843
+ * eager parse, deferred materialization. Same per-doc error semantics
844
+ * as ext_load_lazy. */
845
+ static VALUE ext_load_lazy_batch(VALUE self, VALUE strings) {
846
+ Check_Type(strings, T_ARRAY);
847
+ long n = RARRAY_LEN(strings);
848
+ if (n == 0) return rb_ary_new();
849
+ batch_scratch *s = batch_scratch_alloc(n);
850
+ if (s == NULL) rb_raise(eError, "out of memory batching %ld documents", n);
851
+ for (long i = 0; i < n; i++) {
852
+ VALUE str = rb_ary_entry(strings, i);
853
+ StringValue(str); /* raises TypeError if not coercible */
854
+ s->data[i] = RSTRING_PTR(str);
855
+ s->lens[i] = (size_t)RSTRING_LEN(str);
856
+ }
857
+ teptris_status batch =
858
+ teptris_parse_batch(s->data, s->lens, (size_t)n, NULL,
859
+ s->docs, s->statuses);
860
+ if (batch != TEPTRIS_OK) {
861
+ batch_scratch_free(s);
862
+ rb_raise(eError, "batch parse failed (status %d)", (int)batch);
863
+ }
864
+ long first_fail = -1;
865
+ for (long i = 0; i < n; i++) {
866
+ if (s->statuses[i] != TEPTRIS_OK && first_fail == -1) first_fail = i;
867
+ }
868
+ if (first_fail != -1) {
869
+ const teptris_error *e = teptris_document_error(s->docs[first_fail]);
870
+ VALUE ex =
871
+ rb_exc_new(eParseError, e->message, (long)strlen(e->message));
872
+ rb_iv_set(ex, "@line", SIZET2NUM(e->line));
873
+ rb_iv_set(ex, "@column", SIZET2NUM(e->column));
874
+ for (long i = 0; i < n; i++) teptris_document_free(s->docs[i]);
875
+ batch_scratch_free(s);
876
+ rb_exc_raise(ex);
877
+ }
878
+ VALUE out = rb_ary_new_capa(n);
879
+ for (long i = 0; i < n; i++) {
880
+ lazy_owner *o;
881
+ VALUE owner = TypedData_Make_Struct(cLazyOwner, lazy_owner,
882
+ &lazy_owner_type, o);
883
+ o->doc = s->docs[i];
884
+ o->input = rb_ary_entry(strings, i);
885
+ rb_ary_push(out, lazy_wrap(owner, teptris_document_root(s->docs[i])));
886
+ }
887
+ batch_scratch_free(s);
888
+ return out;
889
+ }
890
+
891
+ /* Eager batch: returns Array of Hashes with the same datetime contract
892
+ * as ext_load. opts is parsed once; safe_load semantics apply uniformly.
893
+ * The caller's input Array is read-only; its strings stay referenced
894
+ * for the duration of the C call (no separate keeper Array). */
895
+ static VALUE ext_load_batch(int argc, VALUE *argv, VALUE self) {
896
+ VALUE strings, opts;
897
+ rb_scan_args(argc, argv, "11", &strings, &opts);
898
+ Check_Type(strings, T_ARRAY);
899
+ long n = RARRAY_LEN(strings);
900
+ if (n == 0) return rb_ary_new();
901
+ unsigned flags = flags_from_opts(opts);
902
+ batch_scratch *s = batch_scratch_alloc(n);
903
+ if (s == NULL) rb_raise(eError, "out of memory batching %ld documents", n);
904
+ for (long i = 0; i < n; i++) {
905
+ VALUE str = rb_ary_entry(strings, i);
906
+ StringValue(str);
907
+ s->data[i] = RSTRING_PTR(str);
908
+ s->lens[i] = (size_t)RSTRING_LEN(str);
909
+ }
910
+ teptris_status batch =
911
+ teptris_parse_batch(s->data, s->lens, (size_t)n, NULL,
912
+ s->docs, s->statuses);
913
+ if (batch != TEPTRIS_OK) {
914
+ batch_scratch_free(s);
915
+ rb_raise(eError, "batch parse failed (status %d)", (int)batch);
916
+ }
917
+ long first_fail = -1;
918
+ for (long i = 0; i < n; i++) {
919
+ if (s->statuses[i] != TEPTRIS_OK && first_fail == -1) first_fail = i;
920
+ }
921
+ if (first_fail != -1) {
922
+ const teptris_error *e = teptris_document_error(s->docs[first_fail]);
923
+ VALUE ex =
924
+ rb_exc_new(eParseError, e->message, (long)strlen(e->message));
925
+ rb_iv_set(ex, "@line", SIZET2NUM(e->line));
926
+ rb_iv_set(ex, "@column", SIZET2NUM(e->column));
927
+ for (long i = 0; i < n; i++) teptris_document_free(s->docs[i]);
928
+ batch_scratch_free(s);
929
+ rb_exc_raise(ex);
930
+ }
931
+ VALUE out = rb_ary_new_capa(n);
932
+ for (long i = 0; i < n; i++) {
933
+ const teptris_node *root = teptris_document_root(s->docs[i]);
934
+ rb_ary_store(out, i, obj_from_node(root, flags));
935
+ teptris_document_free(s->docs[i]);
936
+ }
937
+ batch_scratch_free(s);
938
+ return out;
939
+ }
940
+
593
941
  void Init_teptris_ext(void) {
594
942
  VALUE m = rb_define_module("TeptrisExt");
595
943
  rb_define_module_function(m, "load", ext_load, -1);
944
+ rb_define_module_function(m, "load_lazy", ext_load_lazy, 1);
945
+ rb_define_module_function(m, "load_batch", ext_load_batch, -1);
946
+ rb_define_module_function(m, "load_lazy_batch", ext_load_lazy_batch, 1);
947
+ cLazyOwner = rb_define_class_under(m, "LazyOwner", rb_cObject);
948
+ cLazyValue = rb_define_class_under(m, "LazyValue", rb_cObject);
949
+ rb_define_method(cLazyValue, "kind", lazy_kind, 0);
950
+ rb_define_method(cLazyValue, "size", lazy_size, 0);
951
+ rb_define_method(cLazyValue, "each", lazy_each, 0);
952
+ rb_define_method(cLazyValue, "[]", lazy_get, 1);
953
+ rb_define_method(cLazyValue, "at", lazy_get, 1);
954
+ rb_define_method(cLazyValue, "dig", lazy_dig, -1);
955
+ rb_define_method(cLazyValue, "to_h", lazy_to, 0);
956
+ rb_define_method(cLazyValue, "to_a", lazy_to, 0);
957
+ rb_define_method(cLazyValue, "value", lazy_to, 0);
596
958
  rb_define_module_function(m, "dump", ext_dump, 1);
597
959
  rb_define_module_function(m, "engine_version", ext_version, 0);
598
960
  rb_define_module_function(m, "plan_build", ext_plan_build, 2);
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
@@ -1,3 +1,3 @@
1
1
  module Teptris
2
- VERSION = "0.2.43"
2
+ VERSION = "0.2.45"
3
3
  end
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.43
4
+ version: 0.2.45
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - teptris contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-21 00:00:00.000000000 Z
11
+ date: 2026-09-22 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.