yeptris 0.6.11.1 → 0.6.12.1

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: fab04bdd5f0236d59b5d139d410c095d9c99c8aacccaa18c464324c58b0eef33
4
- data.tar.gz: 9ba0ca96dd534843e8238921f3de634c7f18b051f90b3bde901be74b950a6403
3
+ metadata.gz: ffeaeb76cbc3a0a575cf5fce09e5ceff42ae233fa38125c4fb575eb15c2aafab
4
+ data.tar.gz: d6667ffc31e29fd13f97d7bd5378b6a3611c39f849f7a69075d9e90c6e262df6
5
5
  SHA512:
6
- metadata.gz: 2281939709069aaf555ed5df307cc613b304ad236d7c3fc13cca8a4fc28a0ee8ca35f41d7dd3b46c9db71a8a088d90565e4ccfd7aa25c4512a83d46b41bddd79
7
- data.tar.gz: 9731da1240594c945e1eba4f7eef86e3ec10556c23ab0c84617b2109c17c2c08e08d2c64c5cd558103c07abe2ef93da00735ca6c42703e61fbae4eac7a0ad64c
6
+ metadata.gz: 52298af429a38b41b55adf972d89156e5c214367debc1ccf618fd8e1a438414004861c8b6a7e9a1cc1e5235c68ba07a734ae580da668b3b7751b708102c9d46c
7
+ data.tar.gz: c40301fda3424c9ef0d3d314a71dbf05b9a50af488c12b470f14cb8523912416ceb762de6e73ca6b44a97ac04b549d565113b688333320dabe9f3a2861895e50
data/lib/yeptris/ffi.rb CHANGED
@@ -337,6 +337,7 @@ module Yeptris
337
337
  TAG_BOOL = 3
338
338
  TAG_NULL = 4
339
339
  TAG_TIMESTAMP = 5
340
+ TAG_BINARY = 8
340
341
 
341
342
  # the schema-descriptor API (issue #238; TODO.restructure/83)
342
343
  DESC_ABI = 1
@@ -37,6 +37,7 @@ module Yeptris
37
37
  TAG_BOOL = Yeptris::FFI::TAG_BOOL
38
38
  TAG_NULL = Yeptris::FFI::TAG_NULL
39
39
  TAG_TIMESTAMP = Yeptris::FFI::TAG_TIMESTAMP
40
+ TAG_BINARY = Yeptris::FFI::TAG_BINARY
40
41
  TAG_MERGE = 9 # YEPTRIS_TAG_MERGE (resolve.h): a plain '<<' key
41
42
 
42
43
  INF_WORDS = {
@@ -152,6 +153,8 @@ module Yeptris
152
153
  float_or_string(value)
153
154
  when TAG_TIMESTAMP
154
155
  parse_timestamp(value)
156
+ when TAG_BINARY
157
+ value.unpack1("m") # psych to_ruby: !binary -> decode64, ASCII-8BIT (#168)
155
158
  else
156
159
  value
157
160
  end
data/lib/yeptris/node.rb CHANGED
@@ -121,6 +121,7 @@ class Yeptris::Node
121
121
  Yeptris::FFI::TAG_BOOL => :bool,
122
122
  Yeptris::FFI::TAG_NULL => :null,
123
123
  Yeptris::FFI::TAG_TIMESTAMP => :timestamp,
124
+ Yeptris::FFI::TAG_BINARY => :binary,
124
125
  }.freeze
125
126
 
126
127
  def tag_id
@@ -351,6 +352,7 @@ class Yeptris::Node
351
352
  to_f
352
353
  end
353
354
  when :timestamp then ::Yeptris::Materializer.parse_timestamp(value)
355
+ when :binary then value.unpack1("m") # ASCII-8BIT bytes (#168)
354
356
  else
355
357
  # beyond int64 the C resolver leaves plain scalars :str; Psych
356
358
  # materializes Integer (issue #31) — rebuild from the text
@@ -22,6 +22,27 @@ end
22
22
  Object.class_eval { remove_const(:Psych) } if defined?(::Psych) && !::Psych.equal?(Yeptris::Psych)
23
23
  ::Psych = Yeptris::Psych
24
24
 
25
+ # #167: third-party gems feature-gate on Psych::VERSION at require
26
+ # time (mechanize 2.14.1's cookie-jar reads it in a class body). The
27
+ # face reports the STDLIB version it replaces: ORIGINAL's when psych
28
+ # was preloaded, else the default-gem spec's version (local lookup —
29
+ # requiring stdlib psych post-rebind is censored by the header).
30
+ unless Yeptris::Psych.const_defined?(:VERSION, false)
31
+ version =
32
+ if Yeptris::Psych.const_defined?(:ORIGINAL, false)
33
+ Yeptris::Psych::ORIGINAL::VERSION
34
+ else
35
+ spec = begin
36
+ ::Gem.loaded_specs["psych"] ||
37
+ ::Gem::Specification.find_by_name("psych", ::Gem::Requirement.default)
38
+ rescue ::StandardError, ::Gem::Exception, ::LoadError
39
+ nil
40
+ end
41
+ spec&.version&.to_s
42
+ end
43
+ Yeptris::Psych.const_set(:VERSION, version) if version
44
+ end
45
+
25
46
  # yaml-first boot order: ::YAML still references the ORIGINAL stdlib
26
47
  # module object, whose singleton methods route through stdlib's parse
27
48
  # stream — the UTF-8 tagging (and every other normalization) never
data/lib/yeptris/psych.rb CHANGED
@@ -165,7 +165,18 @@ module Yeptris
165
165
  def force_utf8_scalars(obj)
166
166
  case obj
167
167
  when ::String
168
- obj.force_encoding(::Encoding::UTF_8) if obj.encoding == ::Encoding::ASCII_8BIT && !obj.ascii_only?
168
+ # valid_encoding? on an ASCII-8BIT string is ALWAYS true
169
+ # (it is bytes) — the discriminator is whether the bytes
170
+ # READ as UTF-8: #135's case (UTF-8 scalars the C side
171
+ # materialized as BINARY) re-tags; DECODED !binary bytes
172
+ # that are not UTF-8 stay BINARY (#168). Binary content that
173
+ # happens to be valid UTF-8 re-tags — noted divergence from
174
+ # stdlib, which keeps BINARY; real payloads (zips, bodies)
175
+ # never are.
176
+ if obj.encoding == ::Encoding::ASCII_8BIT && !obj.ascii_only?
177
+ probe = obj.dup.force_encoding(::Encoding::UTF_8)
178
+ obj.force_encoding(::Encoding::UTF_8) if probe.valid_encoding?
179
+ end
169
180
  obj
170
181
  when ::Hash
171
182
  obj.each { |k, v| force_utf8_scalars(k); force_utf8_scalars(v) }
@@ -307,7 +318,9 @@ module Yeptris
307
318
  when :scalar, :mapping, :sequence
308
319
  tag = node.tag
309
320
  unless tag.nil?
310
- name = tag.split(":").last
321
+ # both tag spellings pass: the URI form's last segment and
322
+ # the short "!binary" shorthand (#168's cassettes carry it)
323
+ name = tag.split(":").last.sub(/\A!/, "")
311
324
  unless %w[str int float bool null timestamp seq map merge value
312
325
  binary].include?(name)
313
326
  raise DisallowedClass, name
data/lib/yeptris/yaml.rb CHANGED
@@ -192,7 +192,19 @@ module Yeptris
192
192
  obj.each { |e| place(e, emit, blob, off, seen) }
193
193
  emit.call(STOP, 0, 0, 0)
194
194
  end
195
- when String then scalar(obj, STYLE_BY_NAME[string_style(obj)], emit, blob, off)
195
+ when String
196
+ if obj.encoding == ::Encoding::ASCII_8BIT && !obj.ascii_only?
197
+ # #168: psych visit_String's binary branch — strict base64
198
+ # + the short !binary tag + literal style (Builder
199
+ # build_string carries the same branch; the tagged-literal
200
+ # writer keeps the block form)
201
+ scalar([obj].pack("m0"), STYLE_LIT, emit, blob, off)
202
+ emit.call(BUILD_TAG, 0, off[0], 7)
203
+ blob << "!binary"
204
+ off[0] += 7
205
+ else
206
+ scalar(obj, STYLE_BY_NAME[string_style(obj)], emit, blob, off)
207
+ end
196
208
  when Symbol then scalar(":#{obj}", STYLE_PLAIN, emit, blob, off)
197
209
  when Integer then scalar(obj.to_s, STYLE_PLAIN, emit, blob, off)
198
210
  when Float then scalar(float_text(obj), STYLE_PLAIN, emit, blob, off)
@@ -351,6 +363,17 @@ module Yeptris
351
363
  # visit_String matrix) serves both builders; this side carries
352
364
  # the style onto the per-node DOM.
353
365
  def build_string(doc, s)
366
+ # psych 5.5 visit_String's binary branch, verbatim: an
367
+ # ASCII-8BIT string with non-ASCII bytes emits as strict base64
368
+ # (pack('m0'), never wrapped) under the short !binary tag with
369
+ # literal style — the writer keeps the block form for tagged
370
+ # literals (#168). BOTH dump paths converge here (the top-level
371
+ # scalar fast route and the YAMLTree visitor).
372
+ if s.encoding == ::Encoding::ASCII_8BIT && !s.ascii_only?
373
+ bin = new_scalar(doc, [s].pack("m0"), :force_lit)
374
+ bin.set_tag("!binary")
375
+ return bin
376
+ end
354
377
  case BulkBuilder.string_style(s)
355
378
  when :single then new_scalar(doc, s, :force_str_sq)
356
379
  when :double then new_scalar(doc, s, :force_str)
data/lib/yeptris.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Yeptris
4
4
  # The gem's version lives in the parent namespace's file — the last
5
5
  # internal require (yeptris/version) retired with it.
6
- VERSION = "0.6.11.1".freeze
6
+ VERSION = "0.6.12.1".freeze
7
7
  # The error hierarchy lives in THIS file (the parent namespace's
8
8
  # own file): nested constants do not trigger a parent-constant
9
9
  # autoload, and the law forbids internal requires — defining the
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.11
6
+ VERSION 0.6.12
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -221,12 +221,16 @@ YEPTRIS_API YeptrisStatus yeptris_document_build(YeptrisDocument handle,
221
221
  int depth = 0;
222
222
  uint32_t root = UINT32_MAX;
223
223
  uint32_t root_closed = 0;
224
+ uint32_t root_tagged = 0; /* one trailing TAG on the closed root (#168) */
224
225
  uint32_t pending_key = UINT32_MAX; /* map frame's buffered key */
225
226
  uint32_t last_id = UINT32_MAX; /* TAG applies here (#300) */
226
227
  YeptrisStatus rc = YEPTRIS_OK;
227
228
  for (size_t i = 0; i < count && rc == YEPTRIS_OK; i++) {
228
229
  const YeptrisBuildEntry* e = &entries[i];
229
- if (root_closed) {
230
+ /* A closed SCALAR root admits exactly one trailing TAG —
231
+ * psych's binary root is `--- !binary |-` (#168); everything
232
+ * after (more ops, a second tag) still rejects */
233
+ if (root_closed && !(e->op == YEPTRIS_BUILD_TAG && !root_tagged && last_id == root)) {
230
234
  rc = YEPTRIS_ERROR_PARSE; /* the document ended; more entries */
231
235
  break;
232
236
  }
@@ -283,6 +287,7 @@ YEPTRIS_API YeptrisStatus yeptris_document_build(YeptrisDocument handle,
283
287
  rc = YEPTRIS_ERROR_MEMORY;
284
288
  break;
285
289
  }
290
+ root_tagged = root_closed;
286
291
  continue;
287
292
  default:
288
293
  rc = YEPTRIS_ERROR_PARSE; /* unknown op */
@@ -480,17 +480,11 @@ yep_dom* yep_dom_create(const yep_allocator* sys) {
480
480
  memset(d, 0, sizeof(*d));
481
481
  d->sys = sys;
482
482
  d->pool = pool;
483
- d->handles = yep_hpool_create(sys);
484
- if (d->handles == NULL) {
485
- yep_pool_destroy(pool);
486
- yep_free(sys, d);
487
- return NULL;
488
- }
483
+ /* #157: the handle pool is lazy — two allocations and a mutex per
484
+ * document that decode-only consumers (CBOR load, serialize, free)
485
+ * never touch; yep_dom_handles() materializes it on first use */
486
+ d->handles = NULL;
489
487
  if (yep_mutex_init(&d->midx.mu) != 0) {
490
- yep_free(d->sys, d->anchor_nodes);
491
- yep_free(d->sys, d->mut_att);
492
- yep_free(d->sys, d->mut_depth);
493
- yep_hpool_destroy(d->handles);
494
488
  yep_pool_destroy(pool);
495
489
  yep_free(sys, d);
496
490
  return NULL;
@@ -499,6 +493,25 @@ yep_dom* yep_dom_create(const yep_allocator* sys) {
499
493
  return d;
500
494
  }
501
495
 
496
+ /* Lazy handle pool, mirroring the map-index's discipline (mapindex.c):
497
+ * EVERY access rides the dom's lazy-init mutex — read-only sharing
498
+ * means multiple threads may race the FIRST handle creation on a
499
+ * shared document (Threads.ReadOnlySharing; the TSAN catch on the
500
+ * first cut). The uncontended lock costs less than the hpool's own
501
+ * per-allocation lock that follows. */
502
+ struct yep_hpool* yep_dom_handles(yep_dom* d) {
503
+ if (d == NULL || !d->midx.mu_ready) {
504
+ return NULL;
505
+ }
506
+ yep_mutex_lock(&d->midx.mu);
507
+ if (d->handles == NULL) {
508
+ d->handles = yep_hpool_create(d->sys);
509
+ }
510
+ struct yep_hpool* h = d->handles;
511
+ yep_mutex_unlock(&d->midx.mu);
512
+ return h;
513
+ }
514
+
502
515
  void yep_dom_destroy(yep_dom* d) {
503
516
  if (d == NULL) {
504
517
  return;
@@ -197,6 +197,8 @@ void dom_mut_set_depth(yep_dom* d, uint32_t id, uint16_t depth);
197
197
 
198
198
  /* thread-safe handle arena (see hpool.c) */
199
199
  struct yep_hpool* yep_hpool_create(const yep_allocator* sys);
200
+ /* Lazy handle-pool acquisition (#157): NULL dom or OOM stays NULL. */
201
+ struct yep_hpool* yep_dom_handles(yep_dom* d);
200
202
  void yep_hpool_destroy(struct yep_hpool* p);
201
203
  void* yep_hpool_alloc(struct yep_hpool* p, size_t size, size_t align);
202
204
 
@@ -313,7 +313,8 @@ static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
313
313
  enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL };
314
314
  #define YEP_SC_MEMO_MIN 32u /* bytes; derive-below, memoize-above */
315
315
 
316
- static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in) {
316
+ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
317
+ int has_tag) {
317
318
  int multiline = (len > 0 && memchr(p, '\n', len) != NULL);
318
319
  int blockable = 0;
319
320
  if (multiline && !as_key) {
@@ -343,6 +344,13 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
343
344
  if (len == 0) {
344
345
  return YEP_SC_DQ;
345
346
  }
347
+ /* A TAGGED block scalar keeps the block form even single-line
348
+ * (psych's !binary emits `!binary |-` + one base64 line; an
349
+ * untagged single-line literal re-emits plain — libyaml parity,
350
+ * the #290 family) */
351
+ if (has_tag) {
352
+ return YEP_SC_LITERAL;
353
+ }
346
354
  sty = 1;
347
355
  }
348
356
  switch (sty) {
@@ -388,14 +396,14 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
388
396
  * memoized long scalars, in visit order either way. */
389
397
  if (w->sc_dec != NULL && len >= YEP_SC_MEMO_MIN) {
390
398
  if (w->dry) {
391
- route = sc_route(w, p, len, as_key, n->style);
399
+ route = sc_route(w, p, len, as_key, n->style, n->tag.len > 0);
392
400
  w->sc_dec[w->sc_i] = route;
393
401
  } else {
394
402
  route = w->sc_dec[w->sc_i];
395
403
  }
396
404
  w->sc_i++;
397
405
  } else {
398
- route = sc_route(w, p, len, as_key, n->style);
406
+ route = sc_route(w, p, len, as_key, n->style, n->tag.len > 0);
399
407
  }
400
408
  switch (route) {
401
409
  case YEP_SC_NOTHING:
@@ -40,6 +40,7 @@
40
40
 
41
41
  #include <yeptris/error.h>
42
42
  #include <yeptris/marshal.h>
43
+ #include <yeptris/resolve.h> /* YEPTRIS_TAG_BINARY (#168) */
43
44
 
44
45
  typedef struct {
45
46
  const char* name; /* into the value arena */
@@ -487,6 +488,9 @@ static int pre_scan(const yep_value_ctx* c, uint64_t** counts_out, size_t* nopen
487
488
  case YEP_V_TIMESTAMP:
488
489
  return -2;
489
490
  case YEP_V_STR:
491
+ if (vals[i].tag_id == YEPTRIS_TAG_BINARY) {
492
+ return -2; /* !binary: the walk base64-decodes (#168) */
493
+ }
490
494
  if (vals[i].is_key == 1 && vals[i].len == 2 && c->arena[vals[i].off] == '<' &&
491
495
  c->arena[vals[i].off + 1] == '<') {
492
496
  return -2; /* merge key: the walk resolves these */
@@ -45,6 +45,15 @@ void* yep_pool_alloc(yep_pool* pool, size_t size, size_t align) {
45
45
  }
46
46
 
47
47
  yep_block* b = pool->head;
48
+ if (b == NULL) { /* the lazy first block (#157) */
49
+ b = yep_block_new(pool->sys, pool->block_size);
50
+ if (b == NULL) {
51
+ return NULL;
52
+ }
53
+ b->next = NULL;
54
+ pool->head = b;
55
+ pool->blocks = 1;
56
+ }
48
57
  uintptr_t base = (uintptr_t)b + sizeof(yep_block);
49
58
  uintptr_t start = yep_round_up(base + b->used, align);
50
59
 
@@ -88,12 +97,10 @@ yep_pool* yep_pool_create(const yep_allocator* sys, size_t block_size) {
88
97
  }
89
98
  p->sys = sys;
90
99
  p->block_size = block_size;
91
- p->head = yep_block_new(sys, block_size);
92
- p->blocks = 1;
93
- if (p->head == NULL) {
94
- yep_free(sys, p);
95
- return NULL;
96
- }
100
+ /* #157: the first block is lazy — one fewer eager allocation per
101
+ * document; pool_alloc materializes it on first use */
102
+ p->head = NULL;
103
+ p->blocks = 0;
97
104
  return p;
98
105
  }
99
106
 
@@ -27,6 +27,7 @@
27
27
  #include "resolve/resolver.h"
28
28
  #include "scan/json.h"
29
29
  #include "scan/scan.h"
30
+ #include <yeptris/resolve.h> /* YEPTRIS_TAG_* names (#168) */
30
31
 
31
32
  #define YEP_MAX_DEPTH 1000
32
33
  #define YEP_MAX_FOLD_LINES 8192
@@ -158,7 +159,15 @@ static int emit_now(yep_engine* e, const yep_event* ev) {
158
159
  /* Resolution happens exactly here: every scalar path flows
159
160
  * through this choke point (the resolver is the typing SSOT) */
160
161
  if (ev->tag.len > 0) {
161
- ((yep_event*)ev)->tag_id = yep_tag_from_uri((const char*)ev->tag.p, ev->tag.len);
162
+ yep_tag_id tid = yep_tag_from_uri((const char*)ev->tag.p, ev->tag.len);
163
+ /* psych's own dumped form is the LOCAL !binary shorthand,
164
+ * and stdlib to_ruby accepts it beside the core URI (#168)
165
+ * — compat schemas resolve it to BINARY as well */
166
+ if (tid == YEPTRIS_TAG_CUSTOM && ev->tag.len == 7 &&
167
+ memcmp(ev->tag.p, "!binary", 7) == 0 && e->resolver == yep_resolver_compat11()) {
168
+ tid = YEPTRIS_TAG_BINARY;
169
+ }
170
+ ((yep_event*)ev)->tag_id = tid;
162
171
  } else if (ev->implicit && e->resolver != NULL) {
163
172
  ((yep_event*)ev)->tag_id = e->resolver->resolve(
164
173
  e->resolver->ctx, (const char*)ev->value.p, (uint32_t)ev->value.len);
@@ -375,7 +375,7 @@ YEPTRIS_API size_t yeptris_document_count(YeptrisDocument handle) {
375
375
  }
376
376
 
377
377
  yeptris_node* yep_handle_new(yeptris_document* doc, uint32_t id) {
378
- yeptris_node* n = yep_hpool_alloc(doc->dom->handles, sizeof(yeptris_node), 16);
378
+ yeptris_node* n = yep_hpool_alloc(yep_dom_handles(doc->dom), sizeof(yeptris_node), 16);
379
379
  if (n == NULL) {
380
380
  return NULL;
381
381
  }
@@ -552,7 +552,7 @@ static YeptrisNode wrap(yeptris_node* base, uint32_t id) {
552
552
  /* Handles live in the document's thread-safe arena: one
553
553
  * document_free reclaims every handle ever handed out (zero-leak
554
554
  * contract) while read-only sharing stays safe (TODO.impl/19) */
555
- yeptris_node* n = yep_hpool_alloc(base->doc->dom->handles, sizeof(yeptris_node), 16);
555
+ yeptris_node* n = yep_hpool_alloc(yep_dom_handles(base->doc->dom), sizeof(yeptris_node), 16);
556
556
  if (n == NULL) {
557
557
  return NULL;
558
558
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.11.1
4
+ version: 0.6.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.