yeptris 0.6.33.2 → 0.6.34.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/lib/yeptris/json.rb +8 -1
- data/lib/yeptris/yaml.rb +5 -0
- data/lib/yeptris.rb +1 -1
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/CMakeLists.txt +1 -0
- data/vendor/libyeptris/src/include/yeptris/marshal.h +11 -0
- data/vendor/libyeptris/src/yeptris/emit/float/api.h +5 -0
- data/vendor/libyeptris/src/yeptris/emit/float/fpconv.c +428 -0
- data/vendor/libyeptris/src/yeptris/emit/writer.c +4 -1
- data/vendor/libyeptris/src/yeptris/marshal.c +166 -30
- data/vendor/libyeptris/src/yeptris/parse/engine.c +7 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b8fbcfd83e15bb33982ca01b2d79c53083b2180a7cf168aa5419f841c1286d2
|
|
4
|
+
data.tar.gz: fb9422ddb1c0805c56bdf58e2730febaf1db5e1812b3bc27b88f3f52b6396138
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 318c4eeb0e613da7561ca531b4812fab8bbb7785e8aa5c5c333b0a95b123f122a317eb70cad16e661dc0e5bfddded5032473b67fef2d2522e576e0f65546e19f
|
|
7
|
+
data.tar.gz: 975b00c861a6eddb2a59a5aecf329e33ec9cbf922b3079cbe8c520dd2ebaf51ca65c7d6758aa3db15b7b04653f4d8aa730d1d41b4223d3b88ebe786b9cea229c
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -45,7 +45,14 @@ module Yeptris
|
|
|
45
45
|
# yeptris-ruby#278: with no Ruby-version-matched native build the
|
|
46
46
|
# pure-Ruby walk measured 0.11x vs JSON.parse on real documents —
|
|
47
47
|
# the marshal engine keeps the fallback C-side end to end.
|
|
48
|
-
|
|
48
|
+
# STRICT_DUPLICATE_KEYS rides the flags: json >= 3's default
|
|
49
|
+
# contract raises on duplicates. The flags were hardcoded to 0
|
|
50
|
+
# here, so every lane without the native ext (windows-latest has
|
|
51
|
+
# none) answered last-wins under json >= 3 — found by canon's
|
|
52
|
+
# dependent CI, whose parity pin now names the lane.
|
|
53
|
+
return marshal_engine(
|
|
54
|
+
source, STRICT_DUPLICATE_KEYS ? ::Yeptris::FFI::MARSHAL_STRICT_DUP_KEYS : 0
|
|
55
|
+
) if ::Yeptris::FFI::MARSHAL_STRICT_DUP
|
|
49
56
|
tape_engine(source)
|
|
50
57
|
end
|
|
51
58
|
|
data/lib/yeptris/yaml.rb
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# The BulkBuilder's `when Date` must not depend on the caller loading
|
|
4
|
+
# stdlib's date (issue #289: a clean `require "yeptris"` raised NameError
|
|
5
|
+
# before the DumpError branch could run). Psych requires date the same way.
|
|
6
|
+
require "date"
|
|
7
|
+
|
|
3
8
|
# Self-sufficient (issue #95): the neutral surface is a valid entry
|
|
4
9
|
# point on its own — lutaml-model requires exactly this file.
|
|
5
10
|
require "yeptris"
|
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.
|
|
6
|
+
VERSION = "0.6.34.2".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
|
|
@@ -43,6 +43,17 @@ YEPTRIS_API YeptrisStatus yeptris_marshal(const char* data, size_t len, YeptrisS
|
|
|
43
43
|
* bulk path behind Node#to_ruby (one call, not one per node). */
|
|
44
44
|
YEPTRIS_API YeptrisStatus yeptris_marshal_node(YeptrisNode node, char** out, size_t* out_len);
|
|
45
45
|
|
|
46
|
+
#define YEPTRIS_MARSHAL_STRICT_DUP_KEYS 1u
|
|
47
|
+
|
|
48
|
+
/* yeptris_marshal_node with flags. STRICT_DUP_KEYS raises
|
|
49
|
+
* YEPTRIS_ERROR_PARSE ("duplicate key in object") when any map carries
|
|
50
|
+
* the same key twice — json 3's default contract; the JSON DOM keeps
|
|
51
|
+
* both pairs, and the non-strict stream stays last-wins (json 2.x).
|
|
52
|
+
* Memory: *out is malloc'd, free with yeptris_marshal_free exactly
|
|
53
|
+
* once; on failure nothing is allocated. */
|
|
54
|
+
YEPTRIS_API YeptrisStatus yeptris_marshal_node_ex(YeptrisNode node, uint32_t flags, char** out,
|
|
55
|
+
size_t* out_len);
|
|
56
|
+
|
|
46
57
|
YEPTRIS_API void yeptris_marshal_free(char* out);
|
|
47
58
|
|
|
48
59
|
#ifdef __cplusplus
|
|
@@ -31,6 +31,11 @@ int yep_d2fixed(double d, uint32_t precision, char* buf);
|
|
|
31
31
|
/* YAML words for non-finite values (finite input returns 0). */
|
|
32
32
|
int yep_d2s_nonfinite(double d, char* buf);
|
|
33
33
|
|
|
34
|
+
/* The json-gem float byte shape (grisu2 digits + fpconv emit
|
|
35
|
+
* windows, vendored in fpconv.c) for JSON-mode canonical scalars
|
|
36
|
+
* (yeptris#651). 32-byte buffer. Returns the length written. */
|
|
37
|
+
int yep_d2s_json(double d, char* buf);
|
|
38
|
+
|
|
34
39
|
#ifdef __cplusplus
|
|
35
40
|
}
|
|
36
41
|
#endif
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/* fpconv.c — the json-gem float byte shape (yeptris#651).
|
|
2
|
+
*
|
|
3
|
+
* Vendored from night-shift/fpconv in the variant carried by the Ruby
|
|
4
|
+
* json package (ruby/json ext/json/ext/vendor/fpconv.c), which appends
|
|
5
|
+
* ".0" to integral floats. JSON.generate on json >= 2.10 emits floats
|
|
6
|
+
* through exactly this code, so byte parity for the JSON writer
|
|
7
|
+
* (emit_canonical_scalar's JSON route) requires the same digits AND
|
|
8
|
+
* the same emit windows: grisu2 is not always-shortest, so the
|
|
9
|
+
* clean-room shortest printer cannot be reshaped to match.
|
|
10
|
+
*
|
|
11
|
+
* Boost Software License - Version 1.0 - August 17th, 2003.
|
|
12
|
+
* Permission is hereby granted, free of charge, to any person or
|
|
13
|
+
* organization obtaining a copy of this software (see
|
|
14
|
+
* https://www.boost.org/LICENSE_1_0.txt). Attribution preserved per
|
|
15
|
+
* the vendoring discipline (cf. the ryu port plan).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#include <stdbool.h>
|
|
19
|
+
#include <stdint.h>
|
|
20
|
+
#include <string.h>
|
|
21
|
+
|
|
22
|
+
#include "emit/float/api.h"
|
|
23
|
+
|
|
24
|
+
#define npowers 87
|
|
25
|
+
#define steppowers 8
|
|
26
|
+
#define firstpower -348 /* 10 ^ -348 */
|
|
27
|
+
|
|
28
|
+
#define expmax -32
|
|
29
|
+
#define expmin -60
|
|
30
|
+
|
|
31
|
+
typedef struct Fp {
|
|
32
|
+
uint64_t frac;
|
|
33
|
+
int exp;
|
|
34
|
+
} Fp;
|
|
35
|
+
|
|
36
|
+
static const Fp powers_ten[] = {
|
|
37
|
+
{18054884314459144840U, -1220}, {13451937075301367670U, -1193}, {10022474136428063862U, -1166},
|
|
38
|
+
{14934650266808366570U, -1140}, {11127181549972568877U, -1113}, {16580792590934885855U, -1087},
|
|
39
|
+
{12353653155963782858U, -1060}, {18408377700990114895U, -1034}, {13715310171984221708U, -1007},
|
|
40
|
+
{10218702384817765436U, -980}, {15227053142812498563U, -954}, {11345038669416679861U, -927},
|
|
41
|
+
{16905424996341287883U, -901}, {12595523146049147757U, -874}, {9384396036005875287U, -847},
|
|
42
|
+
{13983839803942852151U, -821}, {10418772551374772303U, -794}, {15525180923007089351U, -768},
|
|
43
|
+
{11567161174868858868U, -741}, {17236413322193710309U, -715}, {12842128665889583758U, -688},
|
|
44
|
+
{9568131466127621947U, -661}, {14257626930069360058U, -635}, {10622759856335341974U, -608},
|
|
45
|
+
{15829145694278690180U, -582}, {11793632577567316726U, -555}, {17573882009934360870U, -529},
|
|
46
|
+
{13093562431584567480U, -502}, {9755464219737475723U, -475}, {14536774485912137811U, -449},
|
|
47
|
+
{10830740992659433045U, -422}, {16139061738043178685U, -396}, {12024538023802026127U, -369},
|
|
48
|
+
{17917957937422433684U, -343}, {13349918974505688015U, -316}, {9946464728195732843U, -289},
|
|
49
|
+
{14821387422376473014U, -263}, {11042794154864902060U, -236}, {16455045573212060422U, -210},
|
|
50
|
+
{12259964326927110867U, -183}, {18268770466636286478U, -157}, {13611294676837538539U, -130},
|
|
51
|
+
{10141204801825835212U, -103}, {15111572745182864684U, -77}, {11258999068426240000U, -50},
|
|
52
|
+
{16777216000000000000U, -24}, {12500000000000000000U, 3}, {9313225746154785156U, 30},
|
|
53
|
+
{13877787807814456755U, 56}, {10339757656912845936U, 83}, {15407439555097886824U, 109},
|
|
54
|
+
{11479437019748901445U, 136}, {17105694144590052135U, 162}, {12744735289059618216U, 189},
|
|
55
|
+
{9495567745759798747U, 216}, {14149498560666738074U, 242}, {10542197943230523224U, 269},
|
|
56
|
+
{15709099088952724970U, 295}, {11704190886730495818U, 322}, {17440603504673385349U, 348},
|
|
57
|
+
{12994262207056124023U, 375}, {9681479787123295682U, 402}, {14426529090290212157U, 428},
|
|
58
|
+
{10748601772107342003U, 455}, {16016664761464807395U, 481}, {11933345169920330789U, 508},
|
|
59
|
+
{17782069995880619868U, 534}, {13248674568444952270U, 561}, {9871031767461413346U, 588},
|
|
60
|
+
{14708983551653345445U, 614}, {10959046745042015199U, 641}, {16330252207878254650U, 667},
|
|
61
|
+
{12166986024289022870U, 694}, {18130221999122236476U, 720}, {13508068024458167312U, 747},
|
|
62
|
+
{10064294952495520794U, 774}, {14996968138956309548U, 800}, {11173611982879273257U, 827},
|
|
63
|
+
{16649979327439178909U, 853}, {12405201291620119593U, 880}, {9242595204427927429U, 907},
|
|
64
|
+
{13772540099066387757U, 933}, {10261342003245940623U, 960}, {15290591125556738113U, 986},
|
|
65
|
+
{11392378155556871081U, 1013}, {16975966327722178521U, 1039}, {12648080533535911531U, 1066}};
|
|
66
|
+
|
|
67
|
+
static Fp find_cachedpow10(int exp, int* k) {
|
|
68
|
+
const double one_log_ten = 0.30102999566398114;
|
|
69
|
+
|
|
70
|
+
int approx = (int)(-(exp + npowers) * one_log_ten);
|
|
71
|
+
int idx = (approx - firstpower) / steppowers;
|
|
72
|
+
|
|
73
|
+
while (1) {
|
|
74
|
+
int current = exp + powers_ten[idx].exp + 64;
|
|
75
|
+
|
|
76
|
+
if (current < expmin) {
|
|
77
|
+
idx++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (current > expmax) {
|
|
82
|
+
idx--;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
*k = (firstpower + idx * steppowers);
|
|
87
|
+
|
|
88
|
+
return powers_ten[idx];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#define fracmask 0x000FFFFFFFFFFFFFU
|
|
93
|
+
#define expmask 0x7FF0000000000000U
|
|
94
|
+
#define hiddenbit 0x0010000000000000U
|
|
95
|
+
#define signmask 0x8000000000000000U
|
|
96
|
+
#define expbias (1023 + 52)
|
|
97
|
+
|
|
98
|
+
#define absv(n) ((n) < 0 ? -(n) : (n))
|
|
99
|
+
#define minv(a, b) ((a) < (b) ? (a) : (b))
|
|
100
|
+
|
|
101
|
+
static const uint64_t tens[] = {10000000000000000000U,
|
|
102
|
+
1000000000000000000U,
|
|
103
|
+
100000000000000000U,
|
|
104
|
+
10000000000000000U,
|
|
105
|
+
1000000000000000U,
|
|
106
|
+
100000000000000U,
|
|
107
|
+
10000000000000U,
|
|
108
|
+
1000000000000U,
|
|
109
|
+
100000000000U,
|
|
110
|
+
10000000000U,
|
|
111
|
+
1000000000U,
|
|
112
|
+
100000000U,
|
|
113
|
+
10000000U,
|
|
114
|
+
1000000U,
|
|
115
|
+
100000U,
|
|
116
|
+
10000U,
|
|
117
|
+
1000U,
|
|
118
|
+
100U,
|
|
119
|
+
10U,
|
|
120
|
+
1U};
|
|
121
|
+
|
|
122
|
+
static uint64_t get_dbits(double d) {
|
|
123
|
+
union {
|
|
124
|
+
double dbl;
|
|
125
|
+
uint64_t i;
|
|
126
|
+
} dbl_bits = {d};
|
|
127
|
+
|
|
128
|
+
return dbl_bits.i;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static Fp build_fp(double d) {
|
|
132
|
+
uint64_t bits = get_dbits(d);
|
|
133
|
+
|
|
134
|
+
Fp fp;
|
|
135
|
+
fp.frac = bits & fracmask;
|
|
136
|
+
fp.exp = (bits & expmask) >> 52;
|
|
137
|
+
|
|
138
|
+
if (fp.exp) {
|
|
139
|
+
fp.frac += hiddenbit;
|
|
140
|
+
fp.exp -= expbias;
|
|
141
|
+
|
|
142
|
+
} else {
|
|
143
|
+
fp.exp = -expbias + 1;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return fp;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static void normalize(Fp* fp) {
|
|
150
|
+
while ((fp->frac & hiddenbit) == 0) {
|
|
151
|
+
fp->frac <<= 1;
|
|
152
|
+
fp->exp--;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
int shift = 64 - 52 - 1;
|
|
156
|
+
fp->frac <<= shift;
|
|
157
|
+
fp->exp -= shift;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
static void get_normalized_boundaries(Fp* fp, Fp* lower, Fp* upper) {
|
|
161
|
+
upper->frac = (fp->frac << 1) + 1;
|
|
162
|
+
upper->exp = fp->exp - 1;
|
|
163
|
+
|
|
164
|
+
while ((upper->frac & (hiddenbit << 1)) == 0) {
|
|
165
|
+
upper->frac <<= 1;
|
|
166
|
+
upper->exp--;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
int u_shift = 64 - 52 - 2;
|
|
170
|
+
|
|
171
|
+
upper->frac <<= u_shift;
|
|
172
|
+
upper->exp = upper->exp - u_shift;
|
|
173
|
+
|
|
174
|
+
int l_shift = fp->frac == hiddenbit ? 2 : 1;
|
|
175
|
+
|
|
176
|
+
lower->frac = (fp->frac << l_shift) - 1;
|
|
177
|
+
lower->exp = fp->exp - l_shift;
|
|
178
|
+
|
|
179
|
+
lower->frac <<= lower->exp - upper->exp;
|
|
180
|
+
lower->exp = upper->exp;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static Fp multiply(Fp* a, Fp* b) {
|
|
184
|
+
const uint64_t lomask = 0x00000000FFFFFFFF;
|
|
185
|
+
|
|
186
|
+
uint64_t ah_bl = (a->frac >> 32) * (b->frac & lomask);
|
|
187
|
+
uint64_t al_bh = (a->frac & lomask) * (b->frac >> 32);
|
|
188
|
+
uint64_t al_bl = (a->frac & lomask) * (b->frac & lomask);
|
|
189
|
+
uint64_t ah_bh = (a->frac >> 32) * (b->frac >> 32);
|
|
190
|
+
|
|
191
|
+
uint64_t tmp = (ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32);
|
|
192
|
+
/* round up */
|
|
193
|
+
tmp += 1U << 31;
|
|
194
|
+
|
|
195
|
+
Fp fp = {ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32), a->exp + b->exp + 64};
|
|
196
|
+
|
|
197
|
+
return fp;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static void round_digit(char* digits, int ndigits, uint64_t delta, uint64_t rem, uint64_t kappa,
|
|
201
|
+
uint64_t frac) {
|
|
202
|
+
while (rem < frac && delta - rem >= kappa &&
|
|
203
|
+
(rem + kappa < frac || frac - rem > rem + kappa - frac)) {
|
|
204
|
+
digits[ndigits - 1]--;
|
|
205
|
+
rem += kappa;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K) {
|
|
210
|
+
uint64_t wfrac = upper->frac - fp->frac;
|
|
211
|
+
uint64_t delta = upper->frac - lower->frac;
|
|
212
|
+
|
|
213
|
+
Fp one;
|
|
214
|
+
one.frac = 1ULL << -upper->exp;
|
|
215
|
+
one.exp = upper->exp;
|
|
216
|
+
|
|
217
|
+
uint64_t part1 = upper->frac >> -one.exp;
|
|
218
|
+
uint64_t part2 = upper->frac & (one.frac - 1);
|
|
219
|
+
|
|
220
|
+
int idx = 0, kappa = 10;
|
|
221
|
+
const uint64_t* divp;
|
|
222
|
+
/* 1000000000 */
|
|
223
|
+
for (divp = tens + 10; kappa > 0; divp++) {
|
|
224
|
+
uint64_t div = *divp;
|
|
225
|
+
unsigned digit = (unsigned)(part1 / div);
|
|
226
|
+
|
|
227
|
+
if (digit || idx) {
|
|
228
|
+
digits[idx++] = (char)(digit + '0');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
part1 -= digit * div;
|
|
232
|
+
kappa--;
|
|
233
|
+
|
|
234
|
+
uint64_t tmp = (part1 << -one.exp) + part2;
|
|
235
|
+
if (tmp <= delta) {
|
|
236
|
+
*K += kappa;
|
|
237
|
+
round_digit(digits, idx, delta, tmp, div << -one.exp, wfrac);
|
|
238
|
+
|
|
239
|
+
return idx;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* 10 */
|
|
244
|
+
const uint64_t* unit = tens + 18;
|
|
245
|
+
|
|
246
|
+
while (true) {
|
|
247
|
+
part2 *= 10;
|
|
248
|
+
delta *= 10;
|
|
249
|
+
kappa--;
|
|
250
|
+
|
|
251
|
+
unsigned digit = (unsigned)(part2 >> -one.exp);
|
|
252
|
+
if (digit || idx) {
|
|
253
|
+
digits[idx++] = (char)(digit + '0');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
part2 &= one.frac - 1;
|
|
257
|
+
if (part2 < delta) {
|
|
258
|
+
*K += kappa;
|
|
259
|
+
round_digit(digits, idx, delta, part2, one.frac, wfrac * *unit);
|
|
260
|
+
|
|
261
|
+
return idx;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
unit--;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static int grisu2(double d, char* digits, int* K) {
|
|
269
|
+
Fp w = build_fp(d);
|
|
270
|
+
|
|
271
|
+
Fp lower, upper;
|
|
272
|
+
get_normalized_boundaries(&w, &lower, &upper);
|
|
273
|
+
|
|
274
|
+
normalize(&w);
|
|
275
|
+
|
|
276
|
+
int k;
|
|
277
|
+
Fp cp = find_cachedpow10(upper.exp, &k);
|
|
278
|
+
|
|
279
|
+
w = multiply(&w, &cp);
|
|
280
|
+
upper = multiply(&upper, &cp);
|
|
281
|
+
lower = multiply(&lower, &cp);
|
|
282
|
+
|
|
283
|
+
lower.frac++;
|
|
284
|
+
upper.frac--;
|
|
285
|
+
|
|
286
|
+
*K = -k;
|
|
287
|
+
|
|
288
|
+
return generate_digits(&w, &upper, &lower, digits, K);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg) {
|
|
292
|
+
int exp = absv(K + ndigits - 1);
|
|
293
|
+
|
|
294
|
+
if (K >= 0 && exp < 15) {
|
|
295
|
+
memcpy(dest, digits, (size_t)ndigits);
|
|
296
|
+
memset(dest + ndigits, '0', (size_t)K);
|
|
297
|
+
|
|
298
|
+
/* add a .0 to mark this as a float. */
|
|
299
|
+
dest[ndigits + K] = '.';
|
|
300
|
+
dest[ndigits + K + 1] = '0';
|
|
301
|
+
|
|
302
|
+
return ndigits + K + 2;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/* write decimal w/o scientific notation */
|
|
306
|
+
if (K < 0 && (K > -7 || exp < 10)) {
|
|
307
|
+
int offset = ndigits - absv(K);
|
|
308
|
+
/* fp < 1.0 -> write leading zero */
|
|
309
|
+
if (offset <= 0) {
|
|
310
|
+
offset = -offset;
|
|
311
|
+
dest[0] = '0';
|
|
312
|
+
dest[1] = '.';
|
|
313
|
+
memset(dest + 2, '0', (size_t)offset);
|
|
314
|
+
memcpy(dest + offset + 2, digits, (size_t)ndigits);
|
|
315
|
+
|
|
316
|
+
return ndigits + 2 + offset;
|
|
317
|
+
|
|
318
|
+
/* fp > 1.0 */
|
|
319
|
+
} else {
|
|
320
|
+
memcpy(dest, digits, (size_t)offset);
|
|
321
|
+
dest[offset] = '.';
|
|
322
|
+
memcpy(dest + offset + 1, digits + offset, (size_t)(ndigits - offset));
|
|
323
|
+
|
|
324
|
+
return ndigits + 1;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* write decimal w/ scientific notation */
|
|
329
|
+
ndigits = minv(ndigits, 18 - neg);
|
|
330
|
+
|
|
331
|
+
int idx = 0;
|
|
332
|
+
dest[idx++] = digits[0];
|
|
333
|
+
|
|
334
|
+
if (ndigits > 1) {
|
|
335
|
+
dest[idx++] = '.';
|
|
336
|
+
memcpy(dest + idx, digits + 1, (size_t)(ndigits - 1));
|
|
337
|
+
idx += ndigits - 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
dest[idx++] = 'e';
|
|
341
|
+
|
|
342
|
+
char sign = K + ndigits - 1 < 0 ? '-' : '+';
|
|
343
|
+
dest[idx++] = sign;
|
|
344
|
+
|
|
345
|
+
int cent = 0;
|
|
346
|
+
|
|
347
|
+
if (exp > 99) {
|
|
348
|
+
cent = exp / 100;
|
|
349
|
+
dest[idx++] = (char)(cent + '0');
|
|
350
|
+
exp -= cent * 100;
|
|
351
|
+
}
|
|
352
|
+
if (exp > 9) {
|
|
353
|
+
int dec = exp / 10;
|
|
354
|
+
dest[idx++] = (char)(dec + '0');
|
|
355
|
+
exp -= dec * 10;
|
|
356
|
+
|
|
357
|
+
} else if (cent) {
|
|
358
|
+
dest[idx++] = '0';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
dest[idx++] = (char)(exp % 10 + '0');
|
|
362
|
+
|
|
363
|
+
return idx;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
static int filter_special(double fp, char* dest) {
|
|
367
|
+
if (fp == 0.0) {
|
|
368
|
+
dest[0] = '0';
|
|
369
|
+
dest[1] = '.';
|
|
370
|
+
dest[2] = '0';
|
|
371
|
+
return 3;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
uint64_t bits = get_dbits(fp);
|
|
375
|
+
|
|
376
|
+
bool nan = (bits & expmask) == expmask;
|
|
377
|
+
|
|
378
|
+
if (!nan) {
|
|
379
|
+
return 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (bits & fracmask) {
|
|
383
|
+
dest[0] = 'n';
|
|
384
|
+
dest[1] = 'a';
|
|
385
|
+
dest[2] = 'n';
|
|
386
|
+
|
|
387
|
+
} else {
|
|
388
|
+
dest[0] = 'i';
|
|
389
|
+
dest[1] = 'n';
|
|
390
|
+
dest[2] = 'f';
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return 3;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* Fast and accurate double to string conversion based on Florian
|
|
397
|
+
* Loitsch's Grisu-algorithm. The generated string is never longer
|
|
398
|
+
* than 32 characters and is not null terminated. Returns the number
|
|
399
|
+
* of written characters. */
|
|
400
|
+
static int fpconv_dtoa(double d, char dest[32]) {
|
|
401
|
+
char digits[18];
|
|
402
|
+
|
|
403
|
+
int str_len = 0;
|
|
404
|
+
bool neg = false;
|
|
405
|
+
|
|
406
|
+
if (get_dbits(d) & signmask) {
|
|
407
|
+
dest[0] = '-';
|
|
408
|
+
str_len++;
|
|
409
|
+
neg = true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
int spec = filter_special(d, dest + str_len);
|
|
413
|
+
|
|
414
|
+
if (spec) {
|
|
415
|
+
return str_len + spec;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
int K = 0;
|
|
419
|
+
int ndigits = grisu2(d, digits, &K);
|
|
420
|
+
|
|
421
|
+
str_len += emit_digits(digits, ndigits, dest + str_len, K, neg);
|
|
422
|
+
|
|
423
|
+
return str_len;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
int yep_d2s_json(double d, char* buf) {
|
|
427
|
+
return fpconv_dtoa(d, buf);
|
|
428
|
+
}
|
|
@@ -377,7 +377,10 @@ static void emit_canonical_scalar(yep_writer* w, const yep_dnode* n) {
|
|
|
377
377
|
tmp[tl] = '\0';
|
|
378
378
|
double d = strtod(tmp, &end);
|
|
379
379
|
if (end != NULL && *end == '\0' && end != tmp) {
|
|
380
|
-
|
|
380
|
+
/* JSON floats carry the json-gem byte shape so
|
|
381
|
+
* JSON.generate round trips stay byte-identical
|
|
382
|
+
* (yeptris#651); YAML keeps the shortest shape. */
|
|
383
|
+
int wl = w->json ? yep_d2s_json(d, buf) : yep_d2s_shortest(d, buf);
|
|
381
384
|
wr_put(w, buf, (uint32_t)wl);
|
|
382
385
|
return;
|
|
383
386
|
}
|
|
@@ -656,6 +656,171 @@ static YeptrisStatus map_fail(int rc) {
|
|
|
656
656
|
return rc == -1 ? YEPTRIS_ERROR_MEMORY : YEPTRIS_ERROR_UNSUPPORTED;
|
|
657
657
|
}
|
|
658
658
|
|
|
659
|
+
/* ---- strict duplicate-key pre-scan (yeptris-ruby#278) ----------------
|
|
660
|
+
*
|
|
661
|
+
* json 3 makes duplicate keys an error by DEFAULT; the marshal route
|
|
662
|
+
* must raise before any bytes are written, exactly like the native
|
|
663
|
+
* materializer's rb_hash_aref probe. One linear pass over the same
|
|
664
|
+
* records the writer consumes — the writer itself stays untouched
|
|
665
|
+
* (the dup law lives here, once). The JSON DOM keeps both pairs
|
|
666
|
+
* ("{\"a\":1,\"a\":2}" map_count=2), so the records see them; the
|
|
667
|
+
* non-strict Marshal stream stays last-wins, which is json 2.x's
|
|
668
|
+
* contract.
|
|
669
|
+
*
|
|
670
|
+
* Key identity is the record's typed payload: span kinds compare the
|
|
671
|
+
* arena bytes, the rest compare (kind, b, payload). One growable
|
|
672
|
+
* open-addressing table serves the INNERMOST open map (cleared on
|
|
673
|
+
* close) — duplicates are per-map by definition.
|
|
674
|
+
*/
|
|
675
|
+
|
|
676
|
+
typedef struct {
|
|
677
|
+
uint64_t p; /* 0 = empty slot (payload 0 with kind 0 cannot be a key) */
|
|
678
|
+
uint32_t off, len;
|
|
679
|
+
uint8_t kind, b;
|
|
680
|
+
} dup_slot;
|
|
681
|
+
|
|
682
|
+
typedef struct {
|
|
683
|
+
dup_slot* slots;
|
|
684
|
+
size_t cap; /* power of two */
|
|
685
|
+
} dup_set;
|
|
686
|
+
|
|
687
|
+
static uint64_t dup_hash(const dup_slot* k, const char* arena) {
|
|
688
|
+
uint64_t h = 1469598103934665603ull;
|
|
689
|
+
if (k->len != UINT32_MAX && (k->kind == YEP_V_STR || k->kind == YEP_V_TIMESTAMP ||
|
|
690
|
+
k->kind == YEP_V_ALIAS || k->kind == YEP_V_ANCHOR)) {
|
|
691
|
+
for (uint32_t i = 0; i < k->len; i++) {
|
|
692
|
+
h ^= (unsigned char)arena[k->off + i];
|
|
693
|
+
h *= 1099511628211ull;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
h ^= k->kind;
|
|
697
|
+
h *= 1099511628211ull;
|
|
698
|
+
h ^= k->b;
|
|
699
|
+
h *= 1099511628211ull;
|
|
700
|
+
h ^= k->p;
|
|
701
|
+
h *= 1099511628211ull;
|
|
702
|
+
return h;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
static int dup_eq(const dup_slot* a, const dup_slot* b, const char* arena) {
|
|
706
|
+
if (a->kind != b->kind || a->b != b->b || a->p != b->p) {
|
|
707
|
+
return 0;
|
|
708
|
+
}
|
|
709
|
+
int spanned = (a->kind == YEP_V_STR || a->kind == YEP_V_TIMESTAMP || a->kind == YEP_V_ALIAS ||
|
|
710
|
+
a->kind == YEP_V_ANCHOR);
|
|
711
|
+
if (!spanned) {
|
|
712
|
+
return 1;
|
|
713
|
+
}
|
|
714
|
+
if (a->len != b->len) {
|
|
715
|
+
return 0;
|
|
716
|
+
}
|
|
717
|
+
return a->len == 0 || memcmp(arena + a->off, arena + b->off, a->len) == 0;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
static int dup_set_grow(dup_set* s) {
|
|
721
|
+
size_t ncap = s->cap ? s->cap * 2 : 16;
|
|
722
|
+
dup_slot* ns = calloc(ncap, sizeof(*ns));
|
|
723
|
+
if (ns == NULL) {
|
|
724
|
+
return -1;
|
|
725
|
+
}
|
|
726
|
+
if (s->slots != NULL) {
|
|
727
|
+
free(s->slots);
|
|
728
|
+
}
|
|
729
|
+
s->slots = ns;
|
|
730
|
+
s->cap = ncap;
|
|
731
|
+
return 0;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/* 1 = duplicate found (TLS error set), 0 = clean, -1 = oom */
|
|
735
|
+
static int strict_dup_scan(const yep_value_ctx* c) {
|
|
736
|
+
dup_set s = {NULL, 0};
|
|
737
|
+
int rc = 0;
|
|
738
|
+
const YeptrisValue* vals = c->vals;
|
|
739
|
+
for (size_t i = 0; i < c->n && rc == 0; i++) {
|
|
740
|
+
const YeptrisValue* v = &vals[i];
|
|
741
|
+
if (v->kind == YEP_V_MAP_OPEN) {
|
|
742
|
+
if (s.slots != NULL) {
|
|
743
|
+
memset(s.slots, 0, s.cap * sizeof(*s.slots)); /* innermost map only */
|
|
744
|
+
}
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (v->kind == YEP_V_CLOSE || v->is_key != 1) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
dup_slot k = {v->p, v->off, v->len, v->kind, v->b};
|
|
751
|
+
if (k.p == 0) {
|
|
752
|
+
k.p = 1; /* never aliases the empty-slot sentinel */
|
|
753
|
+
}
|
|
754
|
+
if (s.cap == 0 && dup_set_grow(&s) != 0) {
|
|
755
|
+
rc = -1;
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
uint64_t h = dup_hash(&k, c->arena);
|
|
759
|
+
for (;;) {
|
|
760
|
+
dup_slot* slot = &s.slots[h & (s.cap - 1)];
|
|
761
|
+
if (slot->p == 0) {
|
|
762
|
+
*slot = k;
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
765
|
+
if (dup_eq(slot, &k, c->arena)) {
|
|
766
|
+
yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
|
|
767
|
+
"duplicate key in object");
|
|
768
|
+
rc = 1;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
h++;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
free(s.slots);
|
|
775
|
+
return rc;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
static int dom_subtree_tagged(const yep_dom* d, uint32_t id);
|
|
779
|
+
static YeptrisStatus marshal_records(const yep_value_ctx* c, int compat, YeptrisMarshalMode mode,
|
|
780
|
+
int node_mode, char** out, size_t* out_len);
|
|
781
|
+
|
|
782
|
+
YEPTRIS_API YeptrisStatus yeptris_marshal_node_ex(YeptrisNode node, uint32_t flags, char** out,
|
|
783
|
+
size_t* out_len) {
|
|
784
|
+
if (node == NULL || out == NULL || out_len == NULL ||
|
|
785
|
+
(flags & ~YEPTRIS_MARSHAL_STRICT_DUP_KEYS) != 0) {
|
|
786
|
+
return YEPTRIS_ERROR_ARG;
|
|
787
|
+
}
|
|
788
|
+
*out = NULL;
|
|
789
|
+
*out_len = 0;
|
|
790
|
+
yeptris_node* h = (yeptris_node*)node;
|
|
791
|
+
yep_dom* dom = yep_doc_dom(h->doc); /* #378: a lazy parse carries the
|
|
792
|
+
* tape — the tree materializes here */
|
|
793
|
+
if (dom == NULL) {
|
|
794
|
+
return YEPTRIS_ERROR_MEMORY; /* the lazy route's retry contract */
|
|
795
|
+
}
|
|
796
|
+
if (dom_subtree_tagged(dom, h->id) != 0) {
|
|
797
|
+
yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
|
|
798
|
+
"marshal: explicitly tagged node not expressible; "
|
|
799
|
+
"fall back to the value walk");
|
|
800
|
+
return YEPTRIS_ERROR_UNSUPPORTED;
|
|
801
|
+
}
|
|
802
|
+
yep_value_ctx* c = NULL;
|
|
803
|
+
if (yep_values_from_dom(dom, h->id, 0, &c) != 0) {
|
|
804
|
+
return YEPTRIS_ERROR_MEMORY;
|
|
805
|
+
}
|
|
806
|
+
if ((flags & YEPTRIS_MARSHAL_STRICT_DUP_KEYS) != 0) {
|
|
807
|
+
int drc = strict_dup_scan(c);
|
|
808
|
+
if (drc != 0) {
|
|
809
|
+
yep_value_ctx_free(c);
|
|
810
|
+
return drc < 0 ? YEPTRIS_ERROR_MEMORY : YEPTRIS_ERROR_PARSE;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
YeptrisStatus st = marshal_records(c, h->doc->schema == YEPTRIS_SCHEMA_11_COMPAT,
|
|
814
|
+
YEPTRIS_MARSHAL_ALL_DOCS, 1, out, out_len);
|
|
815
|
+
if (st == YEPTRIS_ERROR_UNSUPPORTED) {
|
|
816
|
+
yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
|
|
817
|
+
"marshal: merge key, timestamp or forward alias not expressible; "
|
|
818
|
+
"fall back to the value walk");
|
|
819
|
+
}
|
|
820
|
+
yep_value_ctx_free(c);
|
|
821
|
+
return st;
|
|
822
|
+
}
|
|
823
|
+
|
|
659
824
|
static YeptrisStatus marshal_records(const yep_value_ctx* c, int compat, YeptrisMarshalMode mode,
|
|
660
825
|
int node_mode, char** out, size_t* out_len) {
|
|
661
826
|
*out = NULL;
|
|
@@ -750,36 +915,7 @@ static int dom_subtree_tagged(const yep_dom* d, uint32_t id) {
|
|
|
750
915
|
}
|
|
751
916
|
|
|
752
917
|
YEPTRIS_API YeptrisStatus yeptris_marshal_node(YeptrisNode node, char** out, size_t* out_len) {
|
|
753
|
-
|
|
754
|
-
return YEPTRIS_ERROR_ARG;
|
|
755
|
-
}
|
|
756
|
-
*out = NULL;
|
|
757
|
-
*out_len = 0;
|
|
758
|
-
yeptris_node* h = (yeptris_node*)node;
|
|
759
|
-
yep_dom* dom = yep_doc_dom(h->doc); /* #378: a lazy parse carries the
|
|
760
|
-
* tape — the tree materializes here */
|
|
761
|
-
if (dom == NULL) {
|
|
762
|
-
return YEPTRIS_ERROR_MEMORY; /* the lazy route's retry contract */
|
|
763
|
-
}
|
|
764
|
-
if (dom_subtree_tagged(dom, h->id) != 0) {
|
|
765
|
-
yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
|
|
766
|
-
"marshal: explicitly tagged node not expressible; "
|
|
767
|
-
"fall back to the value walk");
|
|
768
|
-
return YEPTRIS_ERROR_UNSUPPORTED;
|
|
769
|
-
}
|
|
770
|
-
yep_value_ctx* c = NULL;
|
|
771
|
-
if (yep_values_from_dom(dom, h->id, 0, &c) != 0) {
|
|
772
|
-
return YEPTRIS_ERROR_MEMORY;
|
|
773
|
-
}
|
|
774
|
-
YeptrisStatus st = marshal_records(c, h->doc->schema == YEPTRIS_SCHEMA_11_COMPAT,
|
|
775
|
-
YEPTRIS_MARSHAL_ALL_DOCS, 1, out, out_len);
|
|
776
|
-
if (st == YEPTRIS_ERROR_UNSUPPORTED) {
|
|
777
|
-
yep_error_set(yep_error_tls(), YEP_ERR_UNEXPECTED, 0, 0, 0,
|
|
778
|
-
"marshal: merge key, timestamp or forward alias not expressible; "
|
|
779
|
-
"fall back to the value walk");
|
|
780
|
-
}
|
|
781
|
-
yep_value_ctx_free(c);
|
|
782
|
-
return st;
|
|
918
|
+
return yeptris_marshal_node_ex(node, 0, out, out_len);
|
|
783
919
|
}
|
|
784
920
|
|
|
785
921
|
YEPTRIS_API void yeptris_marshal_free(char* out) {
|
|
@@ -3684,9 +3684,15 @@ static int e_parse_value(yep_engine* e, yep_ctx ctx, uint16_t floor_col) {
|
|
|
3684
3684
|
continue;
|
|
3685
3685
|
}
|
|
3686
3686
|
int pc_comment = (pc == '#' && (prev == ' ' || prev == '\t'));
|
|
3687
|
+
/* an opening quote is node CONTENT, not a prop: the walk
|
|
3688
|
+
* must stop there ('only_props' stays 0) — walking on put
|
|
3689
|
+
* the line's ' #' inside the quoted span, read the quoted
|
|
3690
|
+
* key of the nightly's run 39223056891 ("x:\n!t \"a #b\": y")
|
|
3691
|
+
* as a comment and rejected the keyed pair */
|
|
3687
3692
|
if (pc == '&' || pc == '!' || pc == '<' || pc == ',' || pc == '>' ||
|
|
3688
3693
|
(pc != '\n' && pc != '\r' && !pc_comment &&
|
|
3689
|
-
!yep_ct_is((unsigned char)pc, YEP_CT_FLOW_IND) && pc != ':' && pc != ' '
|
|
3694
|
+
!yep_ct_is((unsigned char)pc, YEP_CT_FLOW_IND) && pc != ':' && pc != ' ' &&
|
|
3695
|
+
pc != '"' && pc != '\'')) {
|
|
3690
3696
|
prev = pc;
|
|
3691
3697
|
t++;
|
|
3692
3698
|
continue;
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yeptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.34.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
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
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -123,6 +123,7 @@ files:
|
|
|
123
123
|
- vendor/libyeptris/src/yeptris/emit/float/api.h
|
|
124
124
|
- vendor/libyeptris/src/yeptris/emit/float/dragon.c
|
|
125
125
|
- vendor/libyeptris/src/yeptris/emit/float/floatint.h
|
|
126
|
+
- vendor/libyeptris/src/yeptris/emit/float/fpconv.c
|
|
126
127
|
- vendor/libyeptris/src/yeptris/emit/float/print.c
|
|
127
128
|
- vendor/libyeptris/src/yeptris/emit/style.c
|
|
128
129
|
- vendor/libyeptris/src/yeptris/emit/style.h
|