yeptris 0.6.46.0-aarch64-linux → 0.6.47.0-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: 718e46973e793ac952083415cf2db939d5ded041f3e2fd9d70a443e277737309
4
- data.tar.gz: 4582829adb2a9c01499ea66ded38167c18a8af3614102b30c80fdc2be12d3dc5
3
+ metadata.gz: 653e8a27e48a658a1232063a9031730f764203d2ff7b15a24158882d4536c1db
4
+ data.tar.gz: 90bc2c8ee8e376f1ec2373365fa6f18c27c188e0c60244cd10c05c8206d13536
5
5
  SHA512:
6
- metadata.gz: 5ed411a26c351d89f4ada220ec97787cdcd057e456997d160158618ce00c7cae0c1ae58b77bdcc9028d38bee446cfcea425c9ee17c0cbfcf768e52b2d35230ea
7
- data.tar.gz: b7fdd69efe879e33304172ef2921a6aa36720eaf81d06bdd6ca0096a1fac3d546fa01a5d31c0516b50cbb83aeff3f620a991ce1fce08d50aba4ee9f4cdb32fbe
6
+ metadata.gz: d6c0ed641dfe83cd513d5c17b8f65a0cb83dc6152f61f46e4ffd4c43f60d15340d8bbbb47a58e43ce9c06bb3f03e9d01c28e12f27e49a52a865fae02927778a9
7
+ data.tar.gz: e4f90f9bfb449b0bd8493e4cb01190e236fef512d854d23004654f269498f7e01b057d2106cf9441cc6f053c1966da4c3d0120a70dabdf43169d80ccf8c0c658
@@ -11,8 +11,8 @@
11
11
 
12
12
  #define YEPTRIS_VERSION_MAJOR 0
13
13
  #define YEPTRIS_VERSION_MINOR 6
14
- #define YEPTRIS_VERSION_PATCH 46
15
- #define YEPTRIS_VERSION_STRING "0.6.46"
14
+ #define YEPTRIS_VERSION_PATCH 47
15
+ #define YEPTRIS_VERSION_STRING "0.6.47"
16
16
 
17
17
  #ifdef __cplusplus
18
18
  extern "C" {
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.46.0".freeze
6
+ VERSION = "0.6.47.0".freeze
7
7
 
8
8
  # Informational notices are OPT-IN (yeptris-ruby#217): consumers
9
9
  # embed yeptris inside CLIs that assert clean stderr, and a
data/libyeptris.so CHANGED
Binary file
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.46
6
+ VERSION 0.6.47
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -1390,17 +1390,19 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key)
1390
1390
  * reconstruction). The format facts mirror marshal.c's emission
1391
1391
  * contract (empirically pinned against Ruby 3.4): w_long small form
1392
1392
  * x==0 -> 0x00, |x| <= 122 -> sign*(|x|+5), size form tag +-k with
1393
- * LE payload (2^8k - m for negatives); strings carry 'I' ivar
1393
+ * LE payload (2^(8k) - m for negatives); strings carry 'I' ivar
1394
1394
  * wrappers; arrays/hashes/strings/floats register for '@' links.
1395
- * A JSON dump duplicates linked re-references (cycled or shared
1396
- * subtrees re-walk from the registered position, depth-guarded).
1395
+ *
1396
+ * '@' links MEMMOVE the registered object's emitted span — the first
1397
+ * walk's bytes are the cache; a lookup miss is a cycle (JSON cannot
1398
+ * express it, the binding falls back). No re-parse, no registration
1399
+ * on links: the object table stays exactly Ruby's stream order.
1400
+ *
1397
1401
  * The JSON-irrelevant constructs ('o' objects, 'S' structs, 'u'
1398
1402
  * user marshal, 'l' bignums beyond i64, non-finite floats) return
1399
1403
  * UNSUPPORTED — the binding falls back to its per-node walk.
1400
1404
  * ============================================================ */
1401
1405
 
1402
- #include <inttypes.h>
1403
-
1404
1406
  typedef struct {
1405
1407
  const char* p;
1406
1408
  size_t len;
@@ -1408,31 +1410,29 @@ typedef struct {
1408
1410
  } mj_stream;
1409
1411
 
1410
1412
  typedef struct {
1411
- uint32_t kind; /* 1 seq, 2 map */
1412
- uint32_t items;
1413
- } mj_frame;
1413
+ const char* p;
1414
+ uint32_t len;
1415
+ } mj_sym;
1416
+
1417
+ typedef struct {
1418
+ size_t pos; /* the stream position of the type byte */
1419
+ size_t out_off; /* where the emitted span starts in w's buffer */
1420
+ size_t out_len; /* the span's length */
1421
+ } mj_obj;
1414
1422
 
1415
1423
  typedef struct {
1416
1424
  yep_writer* w;
1417
1425
  mj_stream s;
1418
- mj_frame* frames;
1419
- int depth;
1420
- int compact;
1421
- /* the symbol table: ';' links resolve to these names */
1422
- struct {
1423
- const char* p;
1424
- uint32_t len;
1425
- }* syms;
1426
+ mj_sym* syms;
1426
1427
  size_t syms_n, syms_cap;
1427
- /* the object table: '@' links re-walk from these positions */
1428
- struct {
1429
- size_t pos;
1430
- uint32_t chain_pos;
1431
- }* objs;
1428
+ mj_obj* objs;
1432
1429
  size_t objs_n, objs_cap;
1433
- size_t* chain; /* the active positions (cycle guard) */
1434
- size_t chain_n, chain_cap;
1435
1430
  int failed; /* 0 ok, YEPTRIS_ERROR_UNSUPPORTED */
1431
+ int compact;
1432
+ int no_reg; /* the next mj_value entry skips registration — an
1433
+ * 'I'-wrapped construct registers ONCE (at the 'I');
1434
+ * its immediate inner must not add a second entry or
1435
+ * every later '@' link shifts */
1436
1436
  } mj_ctx;
1437
1437
 
1438
1438
  static int mj_fail(mj_ctx* c) {
@@ -1450,8 +1450,7 @@ static int mj_byte(mj_ctx* c, unsigned char* out) {
1450
1450
  /* the w_long inverse — mirrors m_w_long's encode exactly:
1451
1451
  * 0x00 -> 0; 1..4 -> positive size form k=b; 5..127 -> small +(
1452
1452
  * b-5); 129..250 -> small -(251-b); 251..255 -> negative size form
1453
- * k=256-b (payload = LE(2^(8k) - m)); 128/248..250 unreachable (the
1454
- * writer takes the small form first). */
1453
+ * k=256-b (payload = LE(2^(8k) - m)); 128 unreachable. */
1455
1454
  static int mj_long(mj_ctx* c, int64_t* out) {
1456
1455
  unsigned char b;
1457
1456
  if (mj_byte(c, &b) != 0)
@@ -1493,19 +1492,9 @@ static int mj_long(mj_ctx* c, int64_t* out) {
1493
1492
  }
1494
1493
  }
1495
1494
 
1496
- static const char* mj_sym_name(mj_ctx* c, int64_t idx, uint32_t* len_out) {
1497
- if (idx < 0 || (size_t)idx >= c->syms_n) {
1498
- mj_fail(c);
1499
- return NULL;
1500
- }
1501
- *len_out = c->syms[idx].len;
1502
- return c->syms[idx].p;
1503
- }
1504
-
1505
- static int mj_ivar_skip(mj_ctx* c);
1506
- static int mj_link_key(mj_ctx* c, int64_t idx);
1507
-
1495
+ static int mj_ivar_pairs(mj_ctx* c);
1508
1496
  static int mj_value(mj_ctx* c);
1497
+ static int mj_discard_value(mj_ctx* c);
1509
1498
 
1510
1499
  static int mj_string_body(mj_ctx* c, int64_t len) {
1511
1500
  if (len < 0 || (uint64_t)len > (uint64_t)(c->s.len - c->s.at))
@@ -1528,36 +1517,79 @@ static int mj_symbol(mj_ctx* c, int emit) {
1528
1517
  c->s.at += (size_t)len;
1529
1518
  if (c->syms_n == c->syms_cap) {
1530
1519
  c->syms_cap = c->syms_cap ? c->syms_cap * 2 : 16;
1531
- c->syms = (void*)realloc(c->syms, c->syms_cap * sizeof(*c->syms));
1520
+ c->syms = (mj_sym*)realloc(c->syms, c->syms_cap * sizeof(*c->syms));
1532
1521
  if (!c->syms)
1533
1522
  return mj_fail(c);
1534
1523
  }
1535
1524
  c->syms[c->syms_n].p = name;
1536
1525
  c->syms[c->syms_n].len = nlen;
1537
1526
  c->syms_n++;
1538
- if (emit) {
1527
+ if (emit)
1539
1528
  emit_dq_json(c->w, name, nlen);
1540
- }
1541
1529
  return 0;
1542
1530
  }
1543
1531
 
1544
- /* an ivar wrapper 'I': the value then long(count) then count pairs of
1545
- * (symbol, value) — read-and-discard (encodings and such) */
1546
- static int mj_ivar_skip(mj_ctx* c);
1532
+ /* the count + (symbol, value) pairs that follow an 'I' wrapper's
1533
+ * wrapped value. Parsed and DISCARDED — encoding/freeze ivars have
1534
+ * no JSON spelling; the symbol still enters the ';' table. */
1535
+ static int mj_ivar_pairs(mj_ctx* c) {
1536
+ int64_t count;
1537
+ if (mj_long(c, &count) != 0)
1538
+ return -1;
1539
+ for (int64_t i = 0; i < count; i++) {
1540
+ unsigned char nt;
1541
+ if (mj_byte(c, &nt) != 0)
1542
+ return -1;
1543
+ if (nt == ':') {
1544
+ if (mj_symbol(c, 0) != 0)
1545
+ return -1; /* the name: tabled, not emitted */
1546
+ } else if (nt == ';') {
1547
+ int64_t sidx;
1548
+ if (mj_long(c, &sidx) != 0)
1549
+ return -1;
1550
+ if (sidx < 0 || (size_t)sidx >= c->syms_n)
1551
+ return mj_fail(c);
1552
+ } else {
1553
+ return mj_fail(c); /* ivar names are symbols only */
1554
+ }
1555
+ if (mj_discard_value(c) != 0)
1556
+ return -1; /* the ivar value */
1557
+ }
1558
+ return 0;
1559
+ }
1547
1560
 
1561
+ /* stream-position-only walk: consumes one value, emits nothing */
1548
1562
  static int mj_discard_value(mj_ctx* c) {
1549
1563
  unsigned char t;
1550
- if (mj_byte(c, &t) != 0)
1564
+ int rc = mj_byte(c, &t);
1565
+ if (rc != 0)
1551
1566
  return -1;
1552
1567
  switch (t) {
1553
1568
  case '0':
1554
1569
  case 'T':
1555
1570
  case 'F':
1556
1571
  return 0;
1557
- case 'i': {
1572
+ case 'i':
1573
+ case ';':
1574
+ case '@': {
1558
1575
  int64_t v;
1559
1576
  return mj_long(c, &v);
1560
1577
  }
1578
+ case 'l': {
1579
+ unsigned char sign;
1580
+ int64_t nlimbs;
1581
+ if (mj_byte(c, &sign) != 0)
1582
+ return -1;
1583
+ if (sign != 0x2b && sign != 0x2d)
1584
+ return mj_fail(c);
1585
+ if (mj_long(c, &nlimbs) != 0)
1586
+ return -1;
1587
+ if (nlimbs < 0 || (uint64_t)(nlimbs * 2) > (uint64_t)(c->s.len - c->s.at))
1588
+ return mj_fail(c);
1589
+ c->s.at += (size_t)(nlimbs * 2);
1590
+ return 0;
1591
+ }
1592
+ case 'f':
1561
1593
  case '"': {
1562
1594
  int64_t len;
1563
1595
  if (mj_long(c, &len) != 0)
@@ -1567,73 +1599,105 @@ static int mj_discard_value(mj_ctx* c) {
1567
1599
  c->s.at += (size_t)len;
1568
1600
  return 0;
1569
1601
  }
1602
+ case 'I':
1603
+ if (mj_discard_value(c) != 0)
1604
+ return -1;
1605
+ return mj_ivar_pairs(c);
1570
1606
  case ':':
1571
1607
  return mj_symbol(c, 0);
1572
- case ';': {
1573
- int64_t idx;
1574
- if (mj_long(c, &idx) != 0)
1608
+ case '[': {
1609
+ int64_t count;
1610
+ if (mj_long(c, &count) != 0)
1575
1611
  return -1;
1576
- return mj_sym_name(c, idx, &(uint32_t){0}) ? 0 : -1;
1612
+ for (int64_t i = 0; i < count; i++) {
1613
+ if (mj_discard_value(c) != 0)
1614
+ return -1;
1615
+ }
1616
+ return 0;
1617
+ }
1618
+ case '{': {
1619
+ int64_t count;
1620
+ if (mj_long(c, &count) != 0)
1621
+ return -1;
1622
+ for (int64_t i = 0; i < count; i++) {
1623
+ if (mj_discard_value(c) != 0)
1624
+ return -1; /* key */
1625
+ if (mj_discard_value(c) != 0)
1626
+ return -1; /* value */
1627
+ }
1628
+ return 0;
1577
1629
  }
1578
- case 'I':
1579
- return mj_ivar_skip(c);
1580
1630
  default:
1581
- return mj_fail(c); /* ivars on JSON trees carry symbol/E
1582
- pairs only in practice */
1631
+ return mj_fail(c);
1583
1632
  }
1584
1633
  }
1585
1634
 
1586
- /* the count + (symbol, value) pairs that follow an 'I' wrapper's
1587
- * wrapped value */
1588
- static int mj_ivar_pairs(mj_ctx* c) {
1589
- int64_t count;
1590
- if (mj_long(c, &count) != 0)
1635
+ /* the memo'd span of a registered object, copied to the cursor. The
1636
+ * span lives earlier in the same buffer (append-only), so the copy
1637
+ * is memmove-safe. */
1638
+ static int mj_emit_memo(mj_ctx* c, const mj_obj* o) {
1639
+ if (getenv("MJ_DEBUG")) {
1640
+ char prev[13];
1641
+ uint32_t k = o->out_len < 12 ? (uint32_t)o->out_len : 12;
1642
+ memcpy(prev, c->w->p + o->out_off, k);
1643
+ prev[k] = '\0';
1644
+ fprintf(stderr, "memo off=%zu len=%zu: %s\n", o->out_off, o->out_len, prev);
1645
+ }
1646
+ if (o->out_len == 0)
1647
+ return mj_fail(c); /* not memoized yet: a cycle */
1648
+ wr_reserve(c->w, o->out_len);
1649
+ if (c->w->oom)
1591
1650
  return -1;
1592
- for (int64_t i = 0; i < count; i++) {
1593
- if (mj_discard_value(c) != 0)
1594
- return -1; /* the name symbol */
1595
- if (mj_discard_value(c) != 0)
1596
- return -1; /* the ivar value */
1597
- }
1651
+ memmove(c->w->p + c->w->len, c->w->p + o->out_off, o->out_len);
1652
+ wr_col_update(c->w, c->w->p + c->w->len, (uint32_t)o->out_len);
1653
+ c->w->len += o->out_len;
1654
+ /* the raw advance bypasses wr_put: hand back the reservation it
1655
+ * would have consumed, or the next write rides past cap */
1656
+ c->w->reserve -= o->out_len;
1657
+ c->w->last = c->w->p[c->w->len - 1];
1598
1658
  return 0;
1599
1659
  }
1600
1660
 
1601
- /* a standalone 'I' wrapper: the wrapped value, then the pairs */
1602
- static int mj_ivar_skip(mj_ctx* c) {
1603
- if (mj_discard_value(c) != 0)
1604
- return -1; /* the wrapped value */
1605
- return mj_ivar_pairs(c);
1606
- }
1607
-
1608
1661
  static int mj_value(mj_ctx* c) {
1609
1662
  unsigned char t;
1610
1663
  size_t tpos = c->s.at;
1664
+ int reg_idx = -1;
1665
+ size_t span_start = 0;
1666
+ int register_this = !c->no_reg;
1667
+ c->no_reg = 0;
1611
1668
  if (mj_byte(c, &t) != 0)
1612
1669
  return -1;
1613
- /* arrays, hashes, strings and floats register for '@' links (the
1614
- * marshal.c emission contract, 0-based stream order); integers,
1615
- * bignums, symbols and immediates never register */
1616
- if (t == '[' || t == '{' || t == '"' || t == 'f') {
1670
+ /* arrays, hashes, strings, floats, bignums and anything
1671
+ * 'I'-wrapped register — ONCE per construct, at the outer byte
1672
+ * (the wrapper's no_reg suppresses the inner) (0-based stream
1673
+ * order). Bignums hold a slot but Ruby never emits a link to one
1674
+ * (the second occurrence re-emits in full); the entry only keeps
1675
+ * the slot count aligned for later links. Integers, symbols and
1676
+ * the other immediates never register. */
1677
+ if (register_this && (t == '[' || t == '{' || t == '"' || t == 'f' || t == 'I' || t == 'l')) {
1617
1678
  if (c->objs_n == c->objs_cap) {
1618
1679
  c->objs_cap = c->objs_cap ? c->objs_cap * 2 : 16;
1619
- c->objs = (void*)realloc(c->objs, c->objs_cap * sizeof(*c->objs));
1680
+ c->objs = (mj_obj*)realloc(c->objs, c->objs_cap * sizeof(*c->objs));
1620
1681
  if (!c->objs)
1621
1682
  return mj_fail(c);
1622
1683
  }
1623
1684
  c->objs[c->objs_n].pos = tpos;
1624
- c->objs[c->objs_n].chain_pos = 0;
1685
+ c->objs[c->objs_n].out_off = c->w->len;
1686
+ c->objs[c->objs_n].out_len = 0;
1687
+ reg_idx = (int)c->objs_n;
1625
1688
  c->objs_n++;
1689
+ span_start = c->w->len;
1626
1690
  }
1627
1691
  switch (t) {
1628
1692
  case '0':
1629
1693
  wr_put(c->w, "null", 4);
1630
- return 0;
1694
+ break;
1631
1695
  case 'T':
1632
1696
  wr_put(c->w, "true", 4);
1633
- return 0;
1697
+ break;
1634
1698
  case 'F':
1635
1699
  wr_put(c->w, "false", 5);
1636
- return 0;
1700
+ break;
1637
1701
  case 'i': {
1638
1702
  int64_t v;
1639
1703
  if (mj_long(c, &v) != 0)
@@ -1641,60 +1705,34 @@ static int mj_value(mj_ctx* c) {
1641
1705
  char buf[24];
1642
1706
  int n = snprintf(buf, sizeof buf, "%lld", (long long)v);
1643
1707
  wr_put(c->w, buf, (uint32_t)n);
1644
- return 0;
1645
- }
1646
- case '"': {
1647
- int64_t len;
1648
- if (mj_long(c, &len) != 0)
1649
- return -1;
1650
- int rc = mj_string_body(c, len);
1651
- if (rc != 0)
1652
- return -1;
1653
- /* the 'I' wrapper follows when the string carries ivars: the
1654
- * value is already parsed — pairs only */
1655
- if (c->s.at < c->s.len && c->s.p[c->s.at] == 'I') {
1656
- c->s.at++;
1657
- if (mj_ivar_pairs(c) != 0)
1658
- return -1;
1659
- }
1660
- return 0;
1661
- }
1662
- case 'I': {
1663
- /* the wrapped value IS the payload: re-dispatch into it, then
1664
- * the ivar pairs ride */
1665
- if (mj_value(c) != 0)
1666
- return -1;
1667
- return mj_ivar_pairs(c);
1708
+ break;
1668
1709
  }
1669
1710
  case 'l': {
1670
- /* the bignum: sign byte + w_long(limb count) + LE 16-bit
1671
- * limbs; the i64-representable range emits, wider falls back */
1711
+ /* the bignum: sign byte '+'/'-' + w_long(byte count) + LE
1712
+ * bytes; the i64-representable range emits, wider falls back */
1672
1713
  unsigned char sign;
1673
1714
  if (mj_byte(c, &sign) != 0)
1674
1715
  return -1;
1675
1716
  if (sign != 0x2b && sign != 0x2d)
1676
- return mj_fail(c); /* + / - */
1677
- int64_t words;
1678
- if (mj_long(c, &words) != 0)
1679
- return -1;
1680
- if (words < 0 || words > 4)
1681
1717
  return mj_fail(c);
1718
+ int64_t nlimbs;
1719
+ if (mj_long(c, &nlimbs) != 0)
1720
+ return -1;
1721
+ if (nlimbs < 1 || nlimbs > 4)
1722
+ return mj_fail(c); /* beyond 64 bits: fall back */
1682
1723
  int64_t v = 0;
1683
- for (int64_t i = 0; i < words; i++) {
1684
- for (int b = 0; b < 2; b++) {
1685
- unsigned char d;
1686
- if (mj_byte(c, &d) != 0)
1687
- return -1;
1688
- v |= (int64_t)d << (16 * i + 8 * b);
1689
- }
1724
+ for (int64_t i = 0; i < nlimbs; i++) {
1725
+ unsigned char lo, hi;
1726
+ if (mj_byte(c, &lo) != 0 || mj_byte(c, &hi) != 0)
1727
+ return -1;
1728
+ v |= (int64_t)(lo | ((uint64_t)hi << 8)) << (16 * i);
1690
1729
  }
1691
- if (sign == 0x2d) {
1730
+ if (sign == 0x2d)
1692
1731
  v = -v;
1693
- }
1694
1732
  char buf[24];
1695
1733
  int n = snprintf(buf, sizeof buf, "%lld", (long long)v);
1696
1734
  wr_put(c->w, buf, (uint32_t)n);
1697
- return 0;
1735
+ break;
1698
1736
  }
1699
1737
  case 'f': {
1700
1738
  int64_t len;
@@ -1707,14 +1745,63 @@ static int mj_value(mj_ctx* c) {
1707
1745
  memcpy(tmp, c->s.p + c->s.at, tl);
1708
1746
  tmp[tl] = '\0';
1709
1747
  c->s.at += (size_t)len;
1710
- if (strcmp(tmp, "inf") == 0 || strcmp(tmp, "-inf") == 0 || strcmp(tmp, "nan") == 0) {
1748
+ if (strcmp(tmp, "inf") == 0 || strcmp(tmp, "-inf") == 0 || strcmp(tmp, "nan") == 0)
1711
1749
  return mj_fail(c); /* JSON has no inf/nan spelling */
1712
- }
1713
1750
  double d = strtod(tmp, NULL);
1714
1751
  char buf[40];
1715
1752
  int wl = yep_d2s_json(d, buf);
1716
1753
  wr_put(c->w, buf, (uint32_t)wl);
1717
- return 0;
1754
+ break;
1755
+ }
1756
+ case '"': {
1757
+ int64_t len;
1758
+ if (mj_long(c, &len) != 0)
1759
+ return -1;
1760
+ int rc = mj_string_body(c, len);
1761
+ if (rc != 0)
1762
+ return -1;
1763
+ break;
1764
+ }
1765
+ case 'I': {
1766
+ /* the wrapped value IS the payload: re-dispatch into it, then
1767
+ * the ivar pairs ride. The inner dispatch must not register —
1768
+ * the construct registered at the 'I' (the double entry
1769
+ * shifted every later '@' link) */
1770
+ c->no_reg = 1;
1771
+ if (mj_value(c) != 0) {
1772
+ c->no_reg = 0;
1773
+ return -1;
1774
+ }
1775
+ c->no_reg = 0;
1776
+ if (mj_ivar_pairs(c) != 0)
1777
+ return -1;
1778
+ break;
1779
+ }
1780
+ case ':': {
1781
+ /* a bare symbol VALUE: emitted as its name string */
1782
+ if (mj_symbol(c, 1) != 0)
1783
+ return -1;
1784
+ break;
1785
+ }
1786
+ case ';': {
1787
+ int64_t idx;
1788
+ if (mj_long(c, &idx) != 0)
1789
+ return -1;
1790
+ if (idx < 0 || (size_t)idx >= c->syms_n)
1791
+ return mj_fail(c);
1792
+ emit_dq_json(c->w, c->syms[idx].p, c->syms[idx].len);
1793
+ break;
1794
+ }
1795
+ case '@': {
1796
+ /* an object link: the registered span memmoves to the cursor */
1797
+ int64_t idx;
1798
+ if (mj_long(c, &idx) != 0)
1799
+ return -1;
1800
+ if (idx < 0 || (size_t)idx >= c->objs_n)
1801
+ return mj_fail(c);
1802
+ if (mj_emit_memo(c, &c->objs[idx]) != 0)
1803
+ return -1;
1804
+ break;
1718
1805
  }
1719
1806
  case '[':
1720
1807
  case '{': {
@@ -1723,45 +1810,53 @@ static int mj_value(mj_ctx* c) {
1723
1810
  return -1;
1724
1811
  if (count < 0)
1725
1812
  return mj_fail(c);
1726
- if (c->depth >= 512)
1727
- return mj_fail(c);
1728
- c->depth++;
1729
1813
  wr_byte(c->w, t == '[' ? '[' : '{');
1814
+ const char* sep = c->compact ? "," : ", ";
1815
+ uint32_t sep_len = c->compact ? 1u : 2u;
1730
1816
  for (int64_t i = 0; i < count; i++) {
1731
- if (i > 0) {
1732
- wr_put(c->w, c->compact ? "," : ", ", c->compact ? 1 : 2);
1733
- }
1734
- if (t == '{') {
1735
- /* the key: a string or a symbol — JSON keys are strings */
1817
+ if (i > 0)
1818
+ wr_put(c->w, sep, sep_len);
1819
+ if (t == '[') {
1820
+ if (mj_value(c) != 0)
1821
+ return -1;
1822
+ } else {
1823
+ /* the key: a string (with its 'I' wrapper) or a
1824
+ * symbol — JSON keys are strings */
1736
1825
  unsigned char kt;
1826
+ size_t ktpos = c->s.at;
1737
1827
  if (mj_byte(c, &kt) != 0)
1738
1828
  return -1;
1739
1829
  if (kt == 'I') {
1740
- /* an ivar-wrapped key: the inner value emits, then
1741
- * the pairs (the 'I' is consumed) */
1742
1830
  if (mj_value(c) != 0)
1743
1831
  return -1;
1744
1832
  if (mj_ivar_pairs(c) != 0)
1745
1833
  return -1;
1746
- } else if (kt == '@') {
1747
- /* a linked key: re-emit the target (#322's corpus
1748
- * links the users' repeated key strings) */
1749
- int64_t idx;
1750
- if (mj_long(c, &idx) != 0)
1751
- return -1;
1752
- if (mj_link_key(c, idx) != 0)
1753
- return -1;
1754
1834
  } else if (kt == '"') {
1835
+ /* the key registers like any string object: its
1836
+ * span is memo'd for later '@' key links */
1837
+ int kreg = (int)c->objs_n;
1838
+ size_t kspan = c->w->len;
1839
+ if (kreg >= 0) {
1840
+ if (c->objs_n == c->objs_cap) {
1841
+ c->objs_cap = c->objs_cap ? c->objs_cap * 2 : 16;
1842
+ c->objs = (mj_obj*)realloc(c->objs, c->objs_cap * sizeof(*c->objs));
1843
+ if (!c->objs)
1844
+ return mj_fail(c);
1845
+ }
1846
+ c->objs[c->objs_n].pos = ktpos;
1847
+ c->objs[c->objs_n].out_off = c->w->len;
1848
+ c->objs[c->objs_n].out_len = 0;
1849
+ c->objs_n++;
1850
+ }
1755
1851
  int64_t len;
1756
1852
  if (mj_long(c, &len) != 0)
1757
1853
  return -1;
1758
1854
  int rc = mj_string_body(c, len);
1759
1855
  if (rc != 0)
1760
1856
  return -1;
1761
- if (c->s.at < c->s.len && c->s.p[c->s.at] == 'I') {
1762
- c->s.at++;
1763
- if (mj_ivar_pairs(c) != 0)
1764
- return -1;
1857
+ if (kreg >= 0) {
1858
+ c->objs[kreg].out_off = kspan;
1859
+ c->objs[kreg].out_len = c->w->len - kspan;
1765
1860
  }
1766
1861
  } else if (kt == ':') {
1767
1862
  if (mj_symbol(c, 1) != 0)
@@ -1770,108 +1865,36 @@ static int mj_value(mj_ctx* c) {
1770
1865
  int64_t idx;
1771
1866
  if (mj_long(c, &idx) != 0)
1772
1867
  return -1;
1773
- uint32_t nlen;
1774
- const char* nm = mj_sym_name(c, idx, &nlen);
1775
- if (!nm)
1868
+ if (idx < 0 || (size_t)idx >= c->syms_n)
1869
+ return mj_fail(c);
1870
+ emit_dq_json(c->w, c->syms[idx].p, c->syms[idx].len);
1871
+ } else if (kt == '@') {
1872
+ int64_t idx;
1873
+ if (mj_long(c, &idx) != 0)
1874
+ return -1;
1875
+ if (idx < 0 || (size_t)idx >= c->objs_n)
1876
+ return mj_fail(c);
1877
+ if (mj_emit_memo(c, &c->objs[idx]) != 0)
1776
1878
  return -1;
1777
- emit_dq_json(c->w, nm, nlen);
1778
1879
  } else {
1779
1880
  return mj_fail(c);
1780
1881
  }
1781
- wr_put(c->w, c->compact ? ":" : ": ", c->compact ? 1 : 2);
1782
- if (mj_value(c) != 0)
1783
- return -1;
1784
- } else {
1882
+ wr_put(c->w, c->compact ? ":" : ": ", c->compact ? 1u : 2u);
1785
1883
  if (mj_value(c) != 0)
1786
1884
  return -1;
1787
1885
  }
1788
1886
  }
1789
- c->depth--;
1790
1887
  wr_byte(c->w, t == '[' ? ']' : '}');
1791
- return 0;
1792
- }
1793
- case ';': {
1794
- int64_t idx;
1795
- if (mj_long(c, &idx) != 0)
1796
- return -1;
1797
- uint32_t nlen;
1798
- const char* nm = mj_sym_name(c, idx, &nlen);
1799
- if (!nm)
1800
- return -1;
1801
- emit_dq_json(c->w, nm, nlen);
1802
- return 0;
1803
- }
1804
- case ':': {
1805
- /* a bare symbol VALUE: emitted as its name string */
1806
- if (mj_symbol(c, 1) != 0)
1807
- return -1;
1808
- return 0;
1809
- }
1810
- case '@': {
1811
- /* an object link: re-walk from the registered position */
1812
- int64_t idx;
1813
- if (mj_long(c, &idx) != 0)
1814
- return -1;
1815
- if (idx < 0 || (size_t)idx >= c->objs_n)
1816
- return mj_fail(c);
1817
- size_t save = c->s.at;
1818
- c->s.at = c->objs[idx].pos;
1819
- /* cycle guard: the link target is in the active chain */
1820
- for (size_t k = 0; k < c->chain_n; k++) {
1821
- if (c->chain[k] == c->objs[idx].pos) {
1822
- c->s.at = save;
1823
- return mj_fail(c);
1824
- }
1825
- }
1826
- if (c->chain_n == c->chain_cap) {
1827
- c->chain_cap = c->chain_cap ? c->chain_cap * 2 : 16;
1828
- c->chain = (size_t*)realloc(c->chain, c->chain_cap * sizeof(*c->chain));
1829
- if (!c->chain) {
1830
- c->s.at = save;
1831
- return mj_fail(c);
1832
- }
1833
- }
1834
- c->chain[c->chain_n++] = c->objs[idx].pos;
1835
- int rc = mj_value(c);
1836
- c->chain_n--;
1837
- c->s.at = save;
1838
- if (rc != 0)
1839
- return -1;
1840
- return 0;
1888
+ break;
1841
1889
  }
1842
1890
  default:
1843
1891
  return mj_fail(c);
1844
1892
  }
1845
- }
1846
-
1847
- /* an '@' object link in KEY position: the link's target re-emitted as
1848
- * the key string (Ruby links repeated key objects — the users' "id"
1849
- * keys, yeptris-ruby#322's corpus). The link consumes no stream
1850
- * bytes; the key walk resumes at the caller's cursor. */
1851
- static int mj_link_key(mj_ctx* c, int64_t idx) {
1852
- if (idx < 0 || (size_t)idx >= c->objs_n)
1853
- return mj_fail(c);
1854
- size_t save = c->s.at;
1855
- for (size_t k = 0; k < c->chain_n; k++) {
1856
- if (c->chain[k] == c->objs[idx].pos) {
1857
- c->s.at = save;
1858
- return mj_fail(c);
1859
- }
1860
- }
1861
- c->s.at = c->objs[idx].pos;
1862
- if (c->chain_n == c->chain_cap) {
1863
- c->chain_cap = c->chain_cap ? c->chain_cap * 2 : 16;
1864
- c->chain = (size_t*)realloc(c->chain, c->chain_cap * sizeof(*c->chain));
1865
- if (!c->chain) {
1866
- c->s.at = save;
1867
- return mj_fail(c);
1868
- }
1893
+ if (reg_idx >= 0) {
1894
+ c->objs[reg_idx].out_off = span_start;
1895
+ c->objs[reg_idx].out_len = c->w->len - span_start;
1869
1896
  }
1870
- c->chain[c->chain_n++] = c->objs[idx].pos;
1871
- int rc = mj_value(c);
1872
- c->chain_n--;
1873
- c->s.at = save;
1874
- return rc;
1897
+ return 0;
1875
1898
  }
1876
1899
 
1877
1900
  YEPTRIS_API YeptrisStatus yeptris_json_from_marshal(const char* m, size_t m_len, int compact,
@@ -1880,12 +1903,10 @@ YEPTRIS_API YeptrisStatus yeptris_json_from_marshal(const char* m, size_t m_len,
1880
1903
  return YEPTRIS_ERROR_ARG;
1881
1904
  *out = NULL;
1882
1905
  *out_len = 0;
1883
- if (m == NULL || m_len < 2 || (unsigned char)m[0] != 0x04 || (unsigned char)m[1] != 0x08) {
1906
+ if (m == NULL || m_len < 2 || (unsigned char)m[0] != 0x04 || (unsigned char)m[1] != 0x08)
1884
1907
  return YEPTRIS_ERROR_UNSUPPORTED;
1885
- }
1886
1908
  yep_emitter em;
1887
1909
  memset(&em, 0, sizeof(em));
1888
- em.doc = NULL;
1889
1910
  yep_writer* w = &em.w;
1890
1911
  w->sc_dec = NULL;
1891
1912
  w->sc_i = 0;
@@ -1927,7 +1948,6 @@ YEPTRIS_API YeptrisStatus yeptris_json_from_marshal(const char* m, size_t m_len,
1927
1948
  buf = w->p;
1928
1949
  free(c.syms);
1929
1950
  free(c.objs);
1930
- free(c.chain);
1931
1951
  if (failed || w->oom) {
1932
1952
  free(buf);
1933
1953
  return failed ? (YeptrisStatus)failed : YEPTRIS_ERROR_MEMORY;
@@ -20,6 +20,22 @@ size_t yep_scan_break_len(const char* p, size_t len, size_t pos) {
20
20
  if (p[pos] == '\r') {
21
21
  return (pos + 1 < len && p[pos + 1] == '\n') ? 2 : 1;
22
22
  }
23
+ /* NEL/LS/PS are breaks like LF/CR (libyaml's IS_BREAK): the byte
24
+ * scanners ride them as content otherwise, and a plain value
25
+ * folds across the break INTO a following document marker (the
26
+ * nightly's r74: "&k:\":(<PS>---" read as one scalar, libyaml
27
+ * two events) */
28
+ return yep_scan_mb_break_len(p, len, pos);
29
+ }
30
+
31
+ size_t yep_scan_mb_break_len(const char* p, size_t len, size_t pos) {
32
+ if (pos + 1 < len && (unsigned char)p[pos] == 0xC2 && (unsigned char)p[pos + 1] == 0x85) {
33
+ return 2; /* NEL */
34
+ }
35
+ if (pos + 2 < len && (unsigned char)p[pos] == 0xE2 && (unsigned char)p[pos + 1] == 0x80 &&
36
+ ((unsigned char)p[pos + 2] & 0xFE) == 0xA8) {
37
+ return 3; /* LS / PS */
38
+ }
23
39
  return 0;
24
40
  }
25
41
 
@@ -38,6 +54,13 @@ void yep_scan_advance_line(const char* p, size_t* from, size_t to, uint32_t* lin
38
54
  * (the nightly's run 38549939326) */
39
55
  (*line)++;
40
56
  *line_start = i + 1;
57
+ } else {
58
+ size_t mb = yep_scan_mb_break_len(p, to, i);
59
+ if (mb > 0) {
60
+ (*line)++;
61
+ i += mb - 1;
62
+ *line_start = i + 1;
63
+ }
41
64
  }
42
65
  }
43
66
  *from = to;
@@ -463,6 +486,21 @@ yep_span yep_scan_plain(const char* p, size_t len, size_t pos, int flow) {
463
486
  hit = k->stopset_find(stop, p + i, len - i);
464
487
  }
465
488
  size_t at = (hit < 0) ? len : i + (size_t)hit;
489
+ /* a multibyte break inside the piece ends it like an EOL: the
490
+ * byte stopset cannot see NEL/LS/PS, and the continuation
491
+ * would ride across the break into the next line's tokens */
492
+ if (at > i) {
493
+ const char* lead = (const char*)memchr(p + i, 0xC2, at - i);
494
+ const char* lead2 = (const char*)memchr(p + i, 0xE2, at - i);
495
+ const char* first =
496
+ (lead && lead2) ? (lead < lead2 ? lead : lead2) : (lead ? lead : lead2);
497
+ if (first != NULL && yep_scan_mb_break_len(p, len, (size_t)(first - p)) > 0) {
498
+ at = (size_t)(first - p);
499
+ i = at;
500
+ s.term = YEP_TERM_EOL;
501
+ break;
502
+ }
503
+ }
466
504
  unsigned char c = (at < len) ? (unsigned char)p[at] : 0;
467
505
 
468
506
  if (at == len) {
@@ -147,6 +147,9 @@ int yep_scan_is_key_start(unsigned char c);
147
147
  /* Consumed length of the line break at pos (0 if none): \n -> 1,
148
148
  * \r\n -> 2, lone \r -> 1. */
149
149
  size_t yep_scan_break_len(const char* p, size_t len, size_t pos);
150
+ /* NEL/LS/PS length at pos (2/3), 0 otherwise — the multibyte half of
151
+ * yep_scan_break_len's law, callable when only the UTF-8 breaks matter */
152
+ size_t yep_scan_mb_break_len(const char* p, size_t len, size_t pos);
150
153
 
151
154
  /* Line/col bookkeeping across a scanned gap: advances *line and
152
155
  * *line_start over the bytes in [*from, to) (only the unscanned region
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.46.0
4
+ version: 0.6.47.0
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.