yeptris 0.1.14.2 → 0.1.15.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 +4 -4
- data/ext/yeptris_native/json_ruby.c +16 -3
- data/ext/yeptris_native/yeptris_native.c +6 -4
- data/lib/yeptris/json.rb +17 -3
- data/lib/yeptris/node.rb +10 -1
- data/lib/yeptris/psych/parser.rb +1 -1
- data/lib/yeptris/psych.rb +22 -5
- data/lib/yeptris/valueml.rb +14 -2
- data/lib/yeptris.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d146f3183f11d9eb980be238bec31b1f7c801c5a72480318a3cbb625d81ca8a1
|
|
4
|
+
data.tar.gz: 69416e379b25969d66e7f331db7a8da17dd43fd1cff315b802df9f7babd8adb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 941936e4141c052a1c5faddd116c746333e35e1de32d6ac326dde1db667986a2fbcd658eb3665cecdb66568331f5e66c12ebc7e0443c5e11958129d03f48c8fd
|
|
7
|
+
data.tar.gz: 6d62eeaf6c3c1277ee44d9b54f5a3346d7f12ed4cdaa0665ae5641beff714d8ce3070d56068217a4626d3a4cd01a128c0cea77ce90b99377b736c9807b513833
|
|
@@ -26,6 +26,8 @@ typedef struct {
|
|
|
26
26
|
int depth;
|
|
27
27
|
int err;
|
|
28
28
|
rb_encoding* enc;
|
|
29
|
+
int strict_dup; /* json gem >= 3: duplicate keys raise (issue #37) */
|
|
30
|
+
VALUE dup_key;
|
|
29
31
|
kcent kc[YEP_KC];
|
|
30
32
|
} jr;
|
|
31
33
|
|
|
@@ -175,8 +177,14 @@ static VALUE jr_object_body(jr* j, VALUE h_pre) {
|
|
|
175
177
|
j->i++;
|
|
176
178
|
VALUE val = jr_value(j);
|
|
177
179
|
if (j->err) goto out;
|
|
178
|
-
if (yep_ins_mode == YEP_INS_ASET) {
|
|
179
|
-
|
|
180
|
+
if (yep_ins_mode == YEP_INS_ASET || j->strict_dup) {
|
|
181
|
+
VALUE h = h_pre == Qundef ? (h_pre = rb_hash_new_capa(8)) : h_pre;
|
|
182
|
+
if (j->strict_dup && !NIL_P(rb_hash_aref(h, key))) {
|
|
183
|
+
j->err = -3;
|
|
184
|
+
j->dup_key = key;
|
|
185
|
+
goto out;
|
|
186
|
+
}
|
|
187
|
+
rb_hash_aset(h, key, val);
|
|
180
188
|
} else {
|
|
181
189
|
if (pn + 2 > pcap) {
|
|
182
190
|
size_t ncap = pcap * 2;
|
|
@@ -312,10 +320,11 @@ double yep_rb_scan_time(const char* p, size_t len, int n) {
|
|
|
312
320
|
return (double)(t1.tv_sec - t0.tv_sec) + (double)(t1.tv_nsec - t0.tv_nsec) / 1e9;
|
|
313
321
|
}
|
|
314
322
|
|
|
315
|
-
VALUE yep_rb_parse_json(const char* p, size_t len) {
|
|
323
|
+
VALUE yep_rb_parse_json(const char* p, size_t len, int strict_dup) {
|
|
316
324
|
jr j;
|
|
317
325
|
memset(&j, 0, sizeof(j));
|
|
318
326
|
j.p = p; j.len = len; j.enc = rb_utf8_encoding();
|
|
327
|
+
j.strict_dup = strict_dup;
|
|
319
328
|
VALUE already = Qtrue;
|
|
320
329
|
if (yep_gc_mode != YEP_GC_NONE) already = rb_gc_disable();
|
|
321
330
|
VALUE v = jr_value(&j);
|
|
@@ -325,6 +334,10 @@ VALUE yep_rb_parse_json(const char* p, size_t len) {
|
|
|
325
334
|
if (already == Qfalse) rb_gc_enable();
|
|
326
335
|
if (yep_gc_mode == YEP_GC_START && j.err == 0) rb_gc_start();
|
|
327
336
|
if (j.err == -1) rb_raise(rb_eNoMemError, "yeptris native json");
|
|
337
|
+
if (j.err == -3) {
|
|
338
|
+
rb_raise(rb_path2class("Yeptris::ParseError"), "duplicate key \"%s\" in JSON object",
|
|
339
|
+
RSTRING_PTR(j.dup_key));
|
|
340
|
+
}
|
|
328
341
|
if (j.err != 0) rb_raise(rb_path2class("Yeptris::ParseError"), "native json parse failed");
|
|
329
342
|
return v;
|
|
330
343
|
}
|
|
@@ -229,7 +229,7 @@ static const YeptrisVisitVTable k_vt = {
|
|
|
229
229
|
};
|
|
230
230
|
|
|
231
231
|
/* fused JSON→Ruby (json_ruby.c) — no vtable, beats JSON.parse */
|
|
232
|
-
VALUE yep_rb_parse_json(const char* p, size_t len);
|
|
232
|
+
VALUE yep_rb_parse_json(const char* p, size_t len, int strict_dup);
|
|
233
233
|
|
|
234
234
|
static VALUE ctx_result(rb_ctx* c, YeptrisStatus st) {
|
|
235
235
|
if (st != YEPTRIS_OK || c->failed) {
|
|
@@ -244,10 +244,12 @@ static VALUE ctx_result(rb_ctx* c, YeptrisStatus st) {
|
|
|
244
244
|
return c->root;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
static VALUE native_load_json(VALUE
|
|
247
|
+
static VALUE native_load_json(int argc, VALUE* argv, VALUE self) {
|
|
248
248
|
(void)self;
|
|
249
|
+
VALUE input, strict;
|
|
250
|
+
rb_scan_args(argc, argv, "11", &input, &strict);
|
|
249
251
|
StringValue(input);
|
|
250
|
-
return yep_rb_parse_json(RSTRING_PTR(input), (size_t)RSTRING_LEN(input));
|
|
252
|
+
return yep_rb_parse_json(RSTRING_PTR(input), (size_t)RSTRING_LEN(input), RTEST(strict));
|
|
251
253
|
}
|
|
252
254
|
|
|
253
255
|
static VALUE native_load(VALUE self, VALUE input, VALUE schema) {
|
|
@@ -412,7 +414,7 @@ RUBY_FUNC_EXPORTED void Init_native(void) {
|
|
|
412
414
|
utf8_enc = rb_utf8_encoding();
|
|
413
415
|
VALUE mYep = rb_define_module("Yeptris");
|
|
414
416
|
VALUE mNat = rb_define_module_under(mYep, "Native");
|
|
415
|
-
rb_define_singleton_method(mNat, "load_json", native_load_json, 1);
|
|
417
|
+
rb_define_singleton_method(mNat, "load_json", native_load_json, -1);
|
|
416
418
|
rb_define_singleton_method(mNat, "load", native_load, 2);
|
|
417
419
|
rb_define_singleton_method(mNat, "load_stream", native_load_stream, 2);
|
|
418
420
|
rb_define_singleton_method(mNat, "gc_mode", native_gc_mode, 0);
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -17,6 +17,13 @@ module Yeptris
|
|
|
17
17
|
class Error < ::Yeptris::Error; end
|
|
18
18
|
class ParseError < Error; end
|
|
19
19
|
|
|
20
|
+
# json gem 3.0 made duplicate keys an error by DEFAULT; 2.x is
|
|
21
|
+
# last-wins. The parity target is the RESOLVED json gem's own
|
|
22
|
+
# behavior — strictness follows it (issue #37, found by canon's
|
|
23
|
+
# CI where json 3.0.0 resolved while the dev box had 2.x).
|
|
24
|
+
require "json"
|
|
25
|
+
STRICT_DUPLICATE_KEYS = Gem::Version.new(::JSON::VERSION) >= Gem::Version.new("3")
|
|
26
|
+
|
|
20
27
|
module_function
|
|
21
28
|
|
|
22
29
|
def load(source)
|
|
@@ -24,7 +31,7 @@ module Yeptris
|
|
|
24
31
|
source = source.to_s
|
|
25
32
|
if defined?(::Yeptris::Native)
|
|
26
33
|
begin
|
|
27
|
-
return ::Yeptris::Native.load_json(source)
|
|
34
|
+
return ::Yeptris::Native.load_json(source, STRICT_DUPLICATE_KEYS)
|
|
28
35
|
rescue ::Yeptris::ParseError => e
|
|
29
36
|
raise ParseError, e.message
|
|
30
37
|
end
|
|
@@ -52,7 +59,7 @@ module Yeptris
|
|
|
52
59
|
raise ParseError, ::Yeptris::FFI.last_error_message if st != ::Yeptris::FFI::OK
|
|
53
60
|
|
|
54
61
|
begin
|
|
55
|
-
walk_strict(cols)
|
|
62
|
+
walk_strict(cols, STRICT_DUPLICATE_KEYS)
|
|
56
63
|
ensure
|
|
57
64
|
::Yeptris::FFI.yeptris_value_free_columns(cols)
|
|
58
65
|
end
|
|
@@ -65,7 +72,7 @@ module Yeptris
|
|
|
65
72
|
# is the strict-JSON one (no ':sym' scan, no y/n quirk, no
|
|
66
73
|
# dot-required floats). Anchors/aliases/timestamps cannot occur
|
|
67
74
|
# in strict JSON — reaching them is an internal error.
|
|
68
|
-
def walk_strict(cols)
|
|
75
|
+
def walk_strict(cols, strict_dup = STRICT_DUPLICATE_KEYS)
|
|
69
76
|
n = cols[:count]
|
|
70
77
|
kinds = cols[:kinds].read_bytes(n).unpack("C*")
|
|
71
78
|
ikeys = cols[:is_keys].read_bytes(n).unpack("C*")
|
|
@@ -78,6 +85,7 @@ module Yeptris
|
|
|
78
85
|
|
|
79
86
|
docs = []
|
|
80
87
|
stack = []
|
|
88
|
+
key_sets = strict_dup ? [{}] : nil
|
|
81
89
|
pending_key = nil
|
|
82
90
|
i = 0
|
|
83
91
|
while i < n
|
|
@@ -88,13 +96,19 @@ module Yeptris
|
|
|
88
96
|
place(docs, stack, pending_key) { [] }
|
|
89
97
|
pending_key = nil
|
|
90
98
|
when ValueML::MAP_OPEN
|
|
99
|
+
key_sets&.push({})
|
|
91
100
|
place(docs, stack, pending_key) { {} }
|
|
92
101
|
pending_key = nil
|
|
93
102
|
when ValueML::CLOSE
|
|
94
103
|
stack.pop
|
|
104
|
+
key_sets&.pop
|
|
95
105
|
when ValueML::V_STR
|
|
96
106
|
text = arena.byteslice(offs[i], lens[i])
|
|
97
107
|
if ikeys[i] == 1 && !stack.empty? && stack.last.is_a?(Hash)
|
|
108
|
+
if strict_dup && key_sets.last.key?(text)
|
|
109
|
+
raise ParseError, %(duplicate key "#{text}" in JSON object)
|
|
110
|
+
end
|
|
111
|
+
key_sets&.last&.store(text, true)
|
|
98
112
|
pending_key = text
|
|
99
113
|
else
|
|
100
114
|
# Records carry int64 payloads: an integer-beyond-int64
|
data/lib/yeptris/node.rb
CHANGED
|
@@ -340,7 +340,16 @@ class Yeptris::Node
|
|
|
340
340
|
to_f
|
|
341
341
|
end
|
|
342
342
|
when :timestamp then ::Yeptris::Materializer.parse_timestamp(value)
|
|
343
|
-
else
|
|
343
|
+
else
|
|
344
|
+
# beyond int64 the C resolver leaves plain scalars :str; Psych
|
|
345
|
+
# materializes Integer (issue #31) — rebuild from the text
|
|
346
|
+
text = value.to_s
|
|
347
|
+
if style == :plain && text.length > 18 &&
|
|
348
|
+
::Yeptris::ValueML::PSYCH_INT_SHAPE.match?(text)
|
|
349
|
+
text.delete(",_").to_i
|
|
350
|
+
else
|
|
351
|
+
value
|
|
352
|
+
end
|
|
344
353
|
end
|
|
345
354
|
end
|
|
346
355
|
|
data/lib/yeptris/psych/parser.rb
CHANGED
data/lib/yeptris/psych.rb
CHANGED
|
@@ -30,13 +30,30 @@ module Yeptris
|
|
|
30
30
|
# object instance exists.
|
|
31
31
|
autoload :Encodable, "yeptris/psych/encodable"
|
|
32
32
|
class Error < StandardError; end
|
|
33
|
+
# Psych's exact interface (issue #32): same constructor arity,
|
|
34
|
+
# same reader set (file/line/column/offset/problem/context), same
|
|
35
|
+
# message shape — drop-in consumers' rescues and constructors
|
|
36
|
+
# keep working after the rebind.
|
|
33
37
|
class SyntaxError < Error
|
|
34
|
-
attr_reader :line, :column
|
|
38
|
+
attr_reader :file, :line, :column, :offset, :problem, :context
|
|
35
39
|
|
|
36
|
-
def initialize(
|
|
37
|
-
|
|
40
|
+
def initialize(file = nil, line = 0, column = 0, offset = 0, problem = nil, context = nil)
|
|
41
|
+
@file = file
|
|
38
42
|
@line = line
|
|
39
43
|
@column = column
|
|
44
|
+
@offset = offset
|
|
45
|
+
@problem = problem
|
|
46
|
+
@context = context
|
|
47
|
+
where = file ? "(#{file})" : "(<unknown>)"
|
|
48
|
+
detail = context ? "#{problem} #{context}" : problem.to_s
|
|
49
|
+
super("#{where}: #{detail} at line #{line} column #{column}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Structured lift from the C parser's message (carries
|
|
53
|
+
# "line L, column C" detail).
|
|
54
|
+
def self.from_parse_error(error)
|
|
55
|
+
md = /\bline (\d+),? column (\d+)/.match(error.message)
|
|
56
|
+
new(nil, md ? md[1].to_i : 0, md ? md[2].to_i : 0, 0, error.message)
|
|
40
57
|
end
|
|
41
58
|
end
|
|
42
59
|
class BadAlias < Error; end
|
|
@@ -84,7 +101,7 @@ module Yeptris
|
|
|
84
101
|
|
|
85
102
|
Nodes::Builder.document(doc)
|
|
86
103
|
rescue Yeptris::ParseError => e
|
|
87
|
-
raise SyntaxError
|
|
104
|
+
raise SyntaxError.from_parse_error(e)
|
|
88
105
|
end
|
|
89
106
|
|
|
90
107
|
def parse_stream(yaml)
|
|
@@ -101,7 +118,7 @@ module Yeptris
|
|
|
101
118
|
)
|
|
102
119
|
stream
|
|
103
120
|
rescue Yeptris::ParseError => e
|
|
104
|
-
raise SyntaxError
|
|
121
|
+
raise SyntaxError.from_parse_error(e)
|
|
105
122
|
ensure
|
|
106
123
|
doc&.free if doc && !stream
|
|
107
124
|
end
|
data/lib/yeptris/valueml.rb
CHANGED
|
@@ -14,6 +14,12 @@ module Yeptris
|
|
|
14
14
|
V_INT = 3
|
|
15
15
|
V_FLOAT = 4
|
|
16
16
|
V_STR = 5
|
|
17
|
+
|
|
18
|
+
# Psych's integer shape (scalar_scanner): sign optional, no
|
|
19
|
+
# leading zero, '_' / ',' separators only between digits. Beyond
|
|
20
|
+
# int64 the C resolver leaves the scalar a string; Psych
|
|
21
|
+
# materializes Integer (issue #31) — every walk rebuilds it.
|
|
22
|
+
PSYCH_INT_SHAPE = /\A[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*)\z/.freeze
|
|
17
23
|
V_TS = 6
|
|
18
24
|
SEQ_OPEN = 7
|
|
19
25
|
MAP_OPEN = 8
|
|
@@ -149,7 +155,10 @@ module Yeptris
|
|
|
149
155
|
text = arena.byteslice(flat[i + OFF], flat[i + LEN])
|
|
150
156
|
# implicit-plain ':name' scans to a Symbol (Psych's
|
|
151
157
|
# ScalarScanner); quoted ':x' stays a String
|
|
152
|
-
v = if flat[i + B] == 1 && text.length >
|
|
158
|
+
v = if flat[i + B] == 1 && text.length > 18 &&
|
|
159
|
+
PSYCH_INT_SHAPE.match?(text)
|
|
160
|
+
text.delete(",_").to_i
|
|
161
|
+
elsif flat[i + B] == 1 && text.length > 1 &&
|
|
153
162
|
text.start_with?(":") && !text.start_with?("::")
|
|
154
163
|
text[1..].to_sym
|
|
155
164
|
else
|
|
@@ -290,7 +299,10 @@ module Yeptris
|
|
|
290
299
|
case kinds[i]
|
|
291
300
|
when V_STR
|
|
292
301
|
text = arena.byteslice(offs[i], lens[i])
|
|
293
|
-
v = if bools[i] == 1 && text.length >
|
|
302
|
+
v = if bools[i] == 1 && text.length > 18 &&
|
|
303
|
+
PSYCH_INT_SHAPE.match?(text)
|
|
304
|
+
text.delete(",_").to_i
|
|
305
|
+
elsif bools[i] == 1 && text.length > 1 &&
|
|
294
306
|
text.start_with?(":") && !text.start_with?("::")
|
|
295
307
|
text[1..].to_sym
|
|
296
308
|
else
|
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.1.
|
|
6
|
+
VERSION = "0.1.15.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
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yeptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.15.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-09-08 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: ffi
|
|
@@ -57,6 +58,7 @@ metadata:
|
|
|
57
58
|
homepage_uri: https://github.com/leptris/yeptris
|
|
58
59
|
source_code_uri: https://github.com/leptris/yeptris
|
|
59
60
|
changelog_uri: https://github.com/leptris/yeptris/releases
|
|
61
|
+
post_install_message:
|
|
60
62
|
rdoc_options: []
|
|
61
63
|
require_paths:
|
|
62
64
|
- lib
|
|
@@ -71,7 +73,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
71
73
|
- !ruby/object:Gem::Version
|
|
72
74
|
version: '0'
|
|
73
75
|
requirements: []
|
|
74
|
-
rubygems_version:
|
|
76
|
+
rubygems_version: 3.5.22
|
|
77
|
+
signing_key:
|
|
75
78
|
specification_version: 4
|
|
76
79
|
summary: 'The YAML counterpart of libleptris: ultra-performance YAML 1.2 for Ruby'
|
|
77
80
|
test_files: []
|