yeptris 0.1.12.0 → 0.1.13.0
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/README.adoc +33 -18
- data/ext/yeptris_native/extconf.rb +9 -5
- data/ext/yeptris_native/json_ruby.c +53 -35
- data/lib/yeptris/json.rb +145 -0
- data/lib/yeptris/yaml.rb +5 -23
- data/lib/yeptris.rb +2 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 76bc800fd5065540397d957ca2162d2491474a086eda5b08436f016daa8f65e4
|
|
4
|
+
data.tar.gz: 683259ac45cfb084722337af80c066890bdf56f2608843343ebf8951589eafa8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b2be1bc057ee5a059e80ac087000afd2cee325473d834db34bd0f898318634e7b9be95dce9b523f909ae6884a56c9ede549a0e7142184eddf4cc96a13a11cb9
|
|
7
|
+
data.tar.gz: e95683f3b475a0dca70488a43cdf23a249d2bcd49c440b4c75bf31914265c0eae9fd2992d1bf6c0ebfa5b0487b796f1a24505e42b4715840c1731ce2af845a4c
|
data/README.adoc
CHANGED
|
@@ -52,9 +52,37 @@ Handles are document-scoped: `Document#free` releases everything
|
|
|
52
52
|
(one C call), a GC finalizer backs it up, and any use after free
|
|
53
53
|
raises `Yeptris::FreedError` — never a segfault.
|
|
54
54
|
|
|
55
|
-
==
|
|
55
|
+
== Two load surfaces: YAML and strict JSON
|
|
56
|
+
|
|
57
|
+
`Yeptris::YAML.load` keeps the Psych contract for EVERY input —
|
|
58
|
+
including JSON-shaped ones. `{"a": [1,]}` is legal flow YAML (spec
|
|
59
|
+
production [141] allows the trailing comma); `"1e3"` is a Psych
|
|
60
|
+
String. These semantics never flip because input happens to look
|
|
61
|
+
like JSON.
|
|
62
|
+
|
|
63
|
+
`Yeptris::JSON.load` is the STRICT RFC 8259 surface — exact
|
|
64
|
+
`JSON.parse` semantics by construction (spec-pinned in
|
|
65
|
+
`spec/json_parity_spec.rb`: every value and every error case,
|
|
66
|
+
Bignum integers, exponent-only floats, duplicate keys). Engines,
|
|
67
|
+
fastest first:
|
|
68
|
+
|
|
69
|
+
1. **Native materializer** (opt-in build; see below): a fused C scan
|
|
70
|
+
→ `VALUE` parser. Order-alternating interleaved profile, 152 KB /
|
|
71
|
+
~29k-value corpus, Ruby 3.4:
|
|
72
|
+
+
|
|
73
|
+
....
|
|
74
|
+
JSON.parse min 0.670 med 0.966 mean 0.910 ms
|
|
75
|
+
Yeptris::JSON min 0.637 med 0.674 mean 0.680 ms
|
|
76
|
+
mean 0.75x faster, head-to-head 370/400 (92.5%)
|
|
77
|
+
....
|
|
78
|
+
2. **Record-drain fallback** (always available): the strict-JSON
|
|
79
|
+
validator gates, then the value records convert without the Psych
|
|
80
|
+
quirk table — exact parity with engine 1, spec-pinned.
|
|
56
81
|
|
|
57
|
-
|
|
82
|
+
The committed profile (`benchmark/json_profile.rb`) is the fair
|
|
83
|
+
benchmark of record — any performance claim runs through it.
|
|
84
|
+
|
|
85
|
+
=== Building the native materializer (opt-in)
|
|
58
86
|
|
|
59
87
|
....
|
|
60
88
|
cd ext/yeptris_native
|
|
@@ -62,22 +90,9 @@ YEPTRIS_LIB_PATH=/path/to/libyeptris.dylib ruby extconf.rb && make
|
|
|
62
90
|
cp native.bundle ../../lib/yeptris/ # or .so on Linux
|
|
63
91
|
....
|
|
64
92
|
|
|
65
|
-
`require "yeptris"` picks it up automatically (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
....
|
|
70
|
-
JSON.parse mean 1.597 ms
|
|
71
|
-
Yeptris::YAML.load mean 1.145 ms (0.72x — faster than JSON.parse)
|
|
72
|
-
Psych.load mean 40.60 ms (35x slower than yeptris)
|
|
73
|
-
....
|
|
74
|
-
|
|
75
|
-
The extension is a fused RFC 8259 → `VALUE` parser (one pass, no
|
|
76
|
-
intermediate records): libyeptris scan kernels tokenize, the Ruby C
|
|
77
|
-
API allocates, repeated keys/tokens share one frozen `String`, and GC
|
|
78
|
-
is paused for the duration. YAML inputs keep the FFI ladder so
|
|
79
|
-
timestamps, aliases, and Psych's scalar quirks resolve through the
|
|
80
|
-
same path `Psych.load` uses.
|
|
93
|
+
`require "yeptris"` picks it up automatically (`Yeptris::JSON` then
|
|
94
|
+
uses it; missing builds silently use the fallback — the gem installs
|
|
95
|
+
without compiling).
|
|
81
96
|
|
|
82
97
|
== Shipped beyond the original plan
|
|
83
98
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
require "mkmf"
|
|
4
4
|
|
|
5
|
+
# libyeptris location: YEPTRIS_LIB_PATH (file or dir), then sibling
|
|
6
|
+
# checkouts. CI sets YEPTRIS_LIB_PATH (the built shared library) and
|
|
7
|
+
# YEPTRIS_SRC (the C checkout) explicitly.
|
|
5
8
|
lib_path = ENV["YEPTRIS_LIB_PATH"]
|
|
6
9
|
candidates = []
|
|
7
10
|
if lib_path
|
|
@@ -13,11 +16,12 @@ candidates << File.expand_path("../../../../yeptris/build/src", __dir__)
|
|
|
13
16
|
candidates << File.expand_path("../../../yeptris/build-validate/src", __dir__)
|
|
14
17
|
candidates << File.expand_path("../../../yeptris/build/src", __dir__)
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Source root: YEPTRIS_SRC (CI / explicit), then sibling checkouts.
|
|
20
|
+
src_roots = [ENV["YEPTRIS_SRC"]].compact
|
|
21
|
+
src_roots << File.expand_path("../../../../yeptris/src", __dir__)
|
|
22
|
+
src_roots << File.expand_path("../../../yeptris/src", __dir__)
|
|
23
|
+
src_root = src_roots.find { |d| d && File.directory?(File.join(d, "include")) }
|
|
24
|
+
abort "yeptris sources not found (set YEPTRIS_SRC)" unless src_root
|
|
21
25
|
|
|
22
26
|
$INCFLAGS << " -I#{src_root}/include -I#{src_root}/yeptris"
|
|
23
27
|
%w[build-validate/generated build/generated].each do |g|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/* json_ruby.c — fused RFC 8259 → Ruby VALUE (TODO.restructure/22). */
|
|
2
2
|
#include <ruby.h>
|
|
3
3
|
#include <ruby/encoding.h>
|
|
4
|
+
#include <ruby/intern.h>
|
|
4
5
|
#include <stdlib.h>
|
|
5
6
|
#include <string.h>
|
|
6
|
-
#include "parse/numbers.h"
|
|
7
7
|
#include "parse/scalars.h"
|
|
8
8
|
#include "scan/json.h"
|
|
9
9
|
|
|
10
10
|
#define YEP_JR_MAX 1000
|
|
11
|
-
#define YEP_KC
|
|
11
|
+
#define YEP_KC 1024
|
|
12
12
|
|
|
13
13
|
typedef struct {
|
|
14
14
|
uint64_t h;
|
|
@@ -86,40 +86,37 @@ static VALUE jr_str(jr* j, int as_key) {
|
|
|
86
86
|
sp = j->p + start + 1;
|
|
87
87
|
sl = (long)(close - start - 1);
|
|
88
88
|
}
|
|
89
|
-
if (as_key || sl <=
|
|
89
|
+
if (as_key || sl <= 24) return jr_cached(j, sp, sl);
|
|
90
90
|
return rb_enc_str_new(sp, sl, j->enc);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
static VALUE jr_num(jr* j) {
|
|
94
94
|
size_t start = j->i;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
char c = s[k];
|
|
103
|
-
if (c == '.' || c == 'e' || c == 'E') { is_float = 1; break; }
|
|
95
|
+
int shape = 0;
|
|
96
|
+
int64_t iv = 0;
|
|
97
|
+
double dv = 0.0;
|
|
98
|
+
/* the fused kernel (scan/json.h): ONE grammar walk, values out */
|
|
99
|
+
if (!yep_json_number_scan(j->p, j->len, &j->i, &shape, &iv, &dv)) {
|
|
100
|
+
j->err = -2;
|
|
101
|
+
return Qnil;
|
|
104
102
|
}
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
for (k = (uint32_t)neg; k < n; k++) v = v * 10 + (s[k] - '0');
|
|
108
|
-
if (neg) v = -v;
|
|
109
|
-
return LL2NUM(v);
|
|
103
|
+
if (shape == 0) {
|
|
104
|
+
return LL2NUM(iv);
|
|
110
105
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
if (yep_num_f64(s, n, &d) != 0) { j->err = -2; return Qnil; }
|
|
114
|
-
return DBL2NUM(d);
|
|
106
|
+
if (shape == 1) {
|
|
107
|
+
return DBL2NUM(dv);
|
|
115
108
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
/* integer text beyond int64: exact Bignum from the validated
|
|
110
|
+
* span (JSON.parse's behavior). Absurd lengths degrade to the
|
|
111
|
+
* approximate double. */
|
|
112
|
+
size_t n = j->i - start;
|
|
113
|
+
if (n < 512) {
|
|
114
|
+
char buf[512];
|
|
115
|
+
memcpy(buf, j->p + start, n);
|
|
116
|
+
buf[n] = '\0';
|
|
117
|
+
return rb_cstr_to_inum(buf, 10, TRUE);
|
|
121
118
|
}
|
|
122
|
-
return
|
|
119
|
+
return DBL2NUM(dv);
|
|
123
120
|
}
|
|
124
121
|
|
|
125
122
|
static VALUE jr_object(jr* j) {
|
|
@@ -127,23 +124,44 @@ static VALUE jr_object(jr* j) {
|
|
|
127
124
|
VALUE h = rb_hash_new_capa(8);
|
|
128
125
|
jr_ws(j);
|
|
129
126
|
if (j->i < j->len && j->p[j->i] == '}') { j->i++; j->depth--; return h; }
|
|
127
|
+
/* pairs collect into a flat buffer and land in ONE bulk insert:
|
|
128
|
+
* rb_hash_bulk_insert skips the per-pair method dispatch the
|
|
129
|
+
* aset loop pays (TODO.restructure/26's margin work) */
|
|
130
|
+
VALUE pairs[64];
|
|
131
|
+
VALUE* pv = pairs;
|
|
132
|
+
size_t pcap = 64, pn = 0, heap_cap = 0;
|
|
130
133
|
for (;;) {
|
|
131
134
|
jr_ws(j);
|
|
132
|
-
if (j->i >= j->len || j->p[j->i] != '"') { j->err = -2;
|
|
135
|
+
if (j->i >= j->len || j->p[j->i] != '"') { j->err = -2; goto out; }
|
|
133
136
|
VALUE key = jr_str(j, 1);
|
|
134
|
-
if (j->err)
|
|
137
|
+
if (j->err) goto out;
|
|
135
138
|
jr_ws(j);
|
|
136
|
-
if (j->i >= j->len || j->p[j->i] != ':') { j->err = -2;
|
|
139
|
+
if (j->i >= j->len || j->p[j->i] != ':') { j->err = -2; goto out; }
|
|
137
140
|
j->i++;
|
|
138
141
|
VALUE val = jr_value(j);
|
|
139
|
-
if (j->err)
|
|
140
|
-
|
|
142
|
+
if (j->err) goto out;
|
|
143
|
+
if (pn + 2 > pcap) {
|
|
144
|
+
size_t ncap = pcap * 2;
|
|
145
|
+
VALUE* nv = malloc(ncap * sizeof(VALUE));
|
|
146
|
+
if (!nv) { j->err = -1; goto out; }
|
|
147
|
+
memcpy(nv, pv, pn * sizeof(VALUE));
|
|
148
|
+
if (pv != pairs) { free(pv); heap_cap = 1; }
|
|
149
|
+
pv = nv; pcap = ncap;
|
|
150
|
+
}
|
|
151
|
+
pv[pn++] = key;
|
|
152
|
+
pv[pn++] = val;
|
|
141
153
|
jr_ws(j);
|
|
142
|
-
if (j->i >= j->len) { j->err = -2;
|
|
154
|
+
if (j->i >= j->len) { j->err = -2; goto out; }
|
|
143
155
|
if (j->p[j->i] == ',') { j->i++; continue; }
|
|
144
|
-
if (j->p[j->i] == '}') { j->i++;
|
|
145
|
-
j->err = -2;
|
|
156
|
+
if (j->p[j->i] == '}') { j->i++; break; }
|
|
157
|
+
j->err = -2; goto out;
|
|
146
158
|
}
|
|
159
|
+
rb_hash_bulk_insert((long)pn, (const VALUE*)pv, h);
|
|
160
|
+
out:
|
|
161
|
+
if (pv != pairs) { free(pv); (void)heap_cap; }
|
|
162
|
+
if (j->err) return Qnil;
|
|
163
|
+
j->depth--;
|
|
164
|
+
return h;
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
static VALUE jr_array(jr* j) {
|
data/lib/yeptris/json.rb
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
# The STRICT JSON surface (TODO.restructure/31).
|
|
5
|
+
#
|
|
6
|
+
# `Yeptris::JSON.load` targets EXACT `JSON.parse` semantics — that
|
|
7
|
+
# is its parity target, spec-pinned (spec/json_parity_spec.rb).
|
|
8
|
+
# It is deliberately separate from Yeptris::YAML: the YAML surface
|
|
9
|
+
# keeps the Psych contract for every input (JSON-shaped included),
|
|
10
|
+
# so the two never drift into each other's semantics.
|
|
11
|
+
#
|
|
12
|
+
# Engines, fastest first (both exact — the parity spec runs against
|
|
13
|
+
# whichever is loaded):
|
|
14
|
+
# 1. the native materializer (fused C scan → VALUE; opt-in build)
|
|
15
|
+
# 2. the record drain + strict conversion walk (always available)
|
|
16
|
+
module JSON
|
|
17
|
+
class Error < ::Yeptris::Error; end
|
|
18
|
+
class ParseError < Error; end
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def load(source)
|
|
23
|
+
source = ::Yeptris.read_input(source)
|
|
24
|
+
source = source.to_s
|
|
25
|
+
if defined?(::Yeptris::Native)
|
|
26
|
+
begin
|
|
27
|
+
return ::Yeptris::Native.load_json(source)
|
|
28
|
+
rescue ::Yeptris::ParseError => e
|
|
29
|
+
raise ParseError, e.message
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
strict_fallback(source)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The always-available engine: the strict-JSON validator gates
|
|
36
|
+
# (parse_json raises on anything RFC 8259 rejects), then the
|
|
37
|
+
# value records convert WITHOUT the Psych quirk table — floats
|
|
38
|
+
# are always Floats, bools always bools ("1e3" is 1000.0 here
|
|
39
|
+
# and a String on the YAML surface; each surface is its own
|
|
40
|
+
# contract).
|
|
41
|
+
def strict_fallback(source)
|
|
42
|
+
begin
|
|
43
|
+
gate = ::Yeptris::Document.parse_json(source)
|
|
44
|
+
rescue ::Yeptris::ParseError => e
|
|
45
|
+
raise ParseError, e.message
|
|
46
|
+
end
|
|
47
|
+
begin
|
|
48
|
+
cols = ::Yeptris::FFI::ValueColumns.new
|
|
49
|
+
st = ::Yeptris::FFI.yeptris_value_drain_columns(
|
|
50
|
+
source, source.bytesize, ::Yeptris::FFI::SCHEMA_12_CORE, cols
|
|
51
|
+
)
|
|
52
|
+
raise ParseError, ::Yeptris::FFI.last_error_message if st != ::Yeptris::FFI::OK
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
walk_strict(cols)
|
|
56
|
+
ensure
|
|
57
|
+
::Yeptris::FFI.yeptris_value_free_columns(cols)
|
|
58
|
+
end
|
|
59
|
+
ensure
|
|
60
|
+
gate.free
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Placement mechanics mirror ValueML.walk_columns; the CONVERSION
|
|
65
|
+
# is the strict-JSON one (no ':sym' scan, no y/n quirk, no
|
|
66
|
+
# dot-required floats). Anchors/aliases/timestamps cannot occur
|
|
67
|
+
# in strict JSON — reaching them is an internal error.
|
|
68
|
+
def walk_strict(cols)
|
|
69
|
+
n = cols[:count]
|
|
70
|
+
kinds = cols[:kinds].read_bytes(n).unpack("C*")
|
|
71
|
+
ikeys = cols[:is_keys].read_bytes(n).unpack("C*")
|
|
72
|
+
bools = cols[:bools].read_bytes(n).unpack("C*")
|
|
73
|
+
offs = cols[:offs].read_bytes(n * 4).unpack("V*")
|
|
74
|
+
lens = cols[:lens].read_bytes(n * 4).unpack("V*")
|
|
75
|
+
pays = cols[:payloads].read_bytes(n * 8).unpack("q<*")
|
|
76
|
+
arena = cols[:arena_len].zero? ? +"" : cols[:arena].read_bytes(cols[:arena_len])
|
|
77
|
+
arena.force_encoding(Encoding::UTF_8)
|
|
78
|
+
|
|
79
|
+
docs = []
|
|
80
|
+
stack = []
|
|
81
|
+
pending_key = nil
|
|
82
|
+
i = 0
|
|
83
|
+
while i < n
|
|
84
|
+
case kinds[i]
|
|
85
|
+
when ValueML::DOC
|
|
86
|
+
docs.push(nil)
|
|
87
|
+
when ValueML::SEQ_OPEN
|
|
88
|
+
place(docs, stack, pending_key) { [] }
|
|
89
|
+
pending_key = nil
|
|
90
|
+
when ValueML::MAP_OPEN
|
|
91
|
+
place(docs, stack, pending_key) { {} }
|
|
92
|
+
pending_key = nil
|
|
93
|
+
when ValueML::CLOSE
|
|
94
|
+
stack.pop
|
|
95
|
+
when ValueML::V_STR
|
|
96
|
+
text = arena.byteslice(offs[i], lens[i])
|
|
97
|
+
if ikeys[i] == 1 && !stack.empty? && stack.last.is_a?(Hash)
|
|
98
|
+
pending_key = text
|
|
99
|
+
else
|
|
100
|
+
# Records carry int64 payloads: an integer-beyond-int64
|
|
101
|
+
# degrades to a PLAIN string (b==1). In strict JSON a
|
|
102
|
+
# plain (unquoted) scalar can ONLY be a number — every
|
|
103
|
+
# real string is quoted and arrives b==0 — so rebuild the
|
|
104
|
+
# exact Integer (JSON.parse parity, Bignum included).
|
|
105
|
+
if bools[i] == 1
|
|
106
|
+
place(docs, stack, pending_key) { Integer(text, 10) }
|
|
107
|
+
else
|
|
108
|
+
place(docs, stack, pending_key) { text }
|
|
109
|
+
end
|
|
110
|
+
pending_key = nil
|
|
111
|
+
end
|
|
112
|
+
when ValueML::V_INT
|
|
113
|
+
place(docs, stack, pending_key) { pays[i] }
|
|
114
|
+
pending_key = nil
|
|
115
|
+
when ValueML::V_FLOAT
|
|
116
|
+
place(docs, stack, pending_key) { [pays[i]].pack("q<").unpack1("E") }
|
|
117
|
+
pending_key = nil
|
|
118
|
+
when ValueML::V_BOOL
|
|
119
|
+
place(docs, stack, pending_key) { bools[i] == 1 }
|
|
120
|
+
pending_key = nil
|
|
121
|
+
when ValueML::V_NULL
|
|
122
|
+
place(docs, stack, pending_key) { nil }
|
|
123
|
+
pending_key = nil
|
|
124
|
+
else
|
|
125
|
+
raise Error, "internal: impossible record #{kinds[i]} in strict JSON"
|
|
126
|
+
end
|
|
127
|
+
i += 1
|
|
128
|
+
end
|
|
129
|
+
docs.empty? ? nil : docs.first
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def place(docs, stack, key)
|
|
133
|
+
v = yield
|
|
134
|
+
if stack.empty?
|
|
135
|
+
docs[-1] = v
|
|
136
|
+
elsif key
|
|
137
|
+
stack.last[key] = v
|
|
138
|
+
else
|
|
139
|
+
stack.last.push(v)
|
|
140
|
+
end
|
|
141
|
+
stack.push(v) if v.is_a?(Array) || v.is_a?(Hash)
|
|
142
|
+
v
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
data/lib/yeptris/yaml.rb
CHANGED
|
@@ -10,16 +10,14 @@ module Yeptris
|
|
|
10
10
|
# Loads the FIRST document of a YAML stream as native Ruby objects.
|
|
11
11
|
# schema: :compat_11 selects Psych/libyaml implicit typing
|
|
12
12
|
# (yes/no, 0o/octal, sexagesimal); :core_12 (default) is YAML 1.2.
|
|
13
|
+
#
|
|
14
|
+
# This surface keeps the Psych contract for EVERY input — including
|
|
15
|
+
# JSON-shaped ones (`{"a": [1,]}` is legal flow YAML; `"1e3"` is a
|
|
16
|
+
# Psych String). Strict RFC 8259 semantics live on Yeptris::JSON
|
|
17
|
+
# (TODO.restructure/31): defaults follow proof, not benchmarks.
|
|
13
18
|
def load(yaml, schema: :compat_11)
|
|
14
19
|
yaml = Yeptris.read_input(yaml)
|
|
15
20
|
yaml = yaml.to_s
|
|
16
|
-
# The native C materializer fuses strict-JSON scan→VALUE in one
|
|
17
|
-
# pass (beats JSON.parse on the 152 KB corpus). YAML inputs keep
|
|
18
|
-
# the FFI ladder so timestamps, aliases, and Psych's scalar
|
|
19
|
-
# quirks all resolve through the same path Psych.load uses.
|
|
20
|
-
if defined?(Yeptris::Native) && native_json?(yaml)
|
|
21
|
-
return Yeptris::Native.load_json(yaml)
|
|
22
|
-
end
|
|
23
21
|
docs = _drain_all(yaml, schema)
|
|
24
22
|
docs.empty? ? nil : docs.first
|
|
25
23
|
end
|
|
@@ -31,22 +29,6 @@ module Yeptris
|
|
|
31
29
|
_drain_all(yaml, schema)
|
|
32
30
|
end
|
|
33
31
|
|
|
34
|
-
# Strict-JSON sniff: first non-space is { or [ — the fused native
|
|
35
|
-
# path beats JSON.parse on this shape (TODO.restructure/22).
|
|
36
|
-
def native_json?(bytes)
|
|
37
|
-
i = 0
|
|
38
|
-
len = bytes.bytesize
|
|
39
|
-
while i < len
|
|
40
|
-
c = bytes.getbyte(i)
|
|
41
|
-
return true if c == 0x7b || c == 0x5b # { or [
|
|
42
|
-
return false unless c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d
|
|
43
|
-
|
|
44
|
-
i += 1
|
|
45
|
-
end
|
|
46
|
-
false
|
|
47
|
-
end
|
|
48
|
-
private_class_method :native_json?
|
|
49
|
-
|
|
50
32
|
# The Marshal fast path when the loaded libyeptris has it (>= 0.1.11
|
|
51
33
|
# era builds), falling back to the columnar drain and finally the
|
|
52
34
|
# record drain — one code path, the fastest the library offers.
|
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.13.0".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
|
|
@@ -39,6 +39,7 @@ module Yeptris
|
|
|
39
39
|
autoload :Document, "yeptris/document"
|
|
40
40
|
autoload :Node, "yeptris/node"
|
|
41
41
|
autoload :YAML, "yeptris/yaml"
|
|
42
|
+
autoload :JSON, "yeptris/json"
|
|
42
43
|
autoload :Materializer, "yeptris/materializer"
|
|
43
44
|
autoload :ValueML, "yeptris/valueml"
|
|
44
45
|
autoload :Psych, "yeptris/psych"
|
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.1.
|
|
4
|
+
version: 0.1.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- lib/yeptris.rb
|
|
41
41
|
- lib/yeptris/document.rb
|
|
42
42
|
- lib/yeptris/ffi.rb
|
|
43
|
+
- lib/yeptris/json.rb
|
|
43
44
|
- lib/yeptris/materializer.rb
|
|
44
45
|
- lib/yeptris/node.rb
|
|
45
46
|
- lib/yeptris/psych.rb
|