yeptris 0.6.39.1-arm-linux-eabihf → 0.6.42.0-arm-linux-eabihf
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/extconf.rb +82 -36
- data/ext/yeptris_native/json_ruby.c +12 -1
- data/ext/yeptris_native/version.h +29 -0
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris.rb +1 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/include/yeptris/emit.h +13 -0
- data/vendor/libyeptris/src/yeptris/emit/emit.c +96 -2
- data/vendor/libyeptris/src/yeptris/emit/writer.c +72 -45
- data/vendor/libyeptris/src/yeptris/emit/writer.h +3 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +150 -25
- 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: 7abcdd568d0a95540be90ecbe988bb575aa4e97d8a7bda0292dc7b578ff7ca5f
|
|
4
|
+
data.tar.gz: 834db1e48082ef9b40cc0ceda0ffcdd17a4fc1bcb5324a6ce53f09be6cc9213a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '039d4d530fb04d72b3afe2f67ab249137d88d3e0c275141aeffd2dd8cc15a7f686c22e6416e11d7d7ade99a5ead7a9f465c7ebbad8a87a417af8c9222e5aff23'
|
|
7
|
+
data.tar.gz: 894301188585b5318d4d6896e0c2c4e8ff6a74e2f746361294e242dbcf52fadc7481b3f1fe6061bfafe92106b841ca155df35339102da048659e0e9d7345ef2b
|
|
@@ -1,47 +1,93 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Builds the native JSON/CBOR materializer (yeptris-ruby#311): the
|
|
4
|
+
# per-minor prebuilt bundle loads FIRST ("lib/yeptris/<minor>/native")
|
|
5
|
+
# and THIS build is the fallback slot ("require 'yeptris/native'") —
|
|
6
|
+
# compiled by the installing Ruby, so it is ABI-correct on every
|
|
7
|
+
# minor, present and future. Installs must NEVER fail: any miss (no
|
|
8
|
+
# engine binary, no headers, no usable toolchain) degrades to a no-op
|
|
9
|
+
# Makefile and the FFI ladder carries the load, exactly as today.
|
|
10
|
+
|
|
3
11
|
require "mkmf"
|
|
12
|
+
require "fileutils"
|
|
13
|
+
|
|
14
|
+
gem_root = File.expand_path("../..", __dir__)
|
|
15
|
+
vendored = File.join(gem_root, "vendor", "libyeptris")
|
|
4
16
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
candidates << lib_path if File.directory?(lib_path)
|
|
17
|
+
def write_dummy_makefile(reason)
|
|
18
|
+
File.write("Makefile", <<~MAKE)
|
|
19
|
+
all:
|
|
20
|
+
\t@echo "#{reason}"
|
|
21
|
+
install:
|
|
22
|
+
clean:
|
|
23
|
+
MAKE
|
|
13
24
|
end
|
|
14
|
-
candidates << File.expand_path("../../../../yeptris/build-validate/src", __dir__)
|
|
15
|
-
candidates << File.expand_path("../../../../yeptris/build/src", __dir__)
|
|
16
|
-
candidates << File.expand_path("../../../yeptris/build-validate/src", __dir__)
|
|
17
|
-
candidates << File.expand_path("../../../yeptris/build/src", __dir__)
|
|
18
25
|
|
|
19
|
-
#
|
|
26
|
+
# 1. The engine binary to LINK. Order: CI/dev explicit, then the gem
|
|
27
|
+
# root — platform gems vendor the prebuilt lib there, and the pure
|
|
28
|
+
# gem's ext/libyeptris extconf builds it there BEFORE this extension
|
|
29
|
+
# runs (rubygems builds extensions in gemspec order).
|
|
30
|
+
lib = ENV["YEPTRIS_LIB_PATH"]
|
|
31
|
+
lib = nil unless lib && File.file?(lib)
|
|
32
|
+
lib ||= ["libyeptris.so", "libyeptris.dylib", "yeptris.dll"]
|
|
33
|
+
.map { |n| File.join(gem_root, n) }
|
|
34
|
+
.find { |f| File.exist?(f) }
|
|
35
|
+
|
|
36
|
+
# 2. The engine headers: CI/dev checkout, then the vendored tree that
|
|
37
|
+
# rides every gem artifact (the packaging doctrine's recompile half).
|
|
20
38
|
src_roots = [ENV["YEPTRIS_SRC"]].compact
|
|
21
|
-
src_roots << File.
|
|
22
|
-
src_roots << File.expand_path("../../../yeptris/src", __dir__)
|
|
39
|
+
src_roots << File.join(vendored, "src")
|
|
23
40
|
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
|
|
25
41
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
# 0. The toolchain probe — the docstring's third miss (yeptris-ruby#317):
|
|
43
|
+
# a toolchain-free image (the alpine musl smoke has no compiler) must
|
|
44
|
+
# degrade to the no-op Makefile, not abort inside mkmf's sanity link.
|
|
45
|
+
if !have_devel?
|
|
46
|
+
write_dummy_makefile("yeptris: no usable toolchain for the materializer; the FFI ladder carries the load")
|
|
47
|
+
elsif lib.nil?
|
|
48
|
+
write_dummy_makefile("yeptris: no libyeptris binary for the materializer; the FFI ladder carries the load")
|
|
49
|
+
elsif src_root.nil?
|
|
50
|
+
write_dummy_makefile("yeptris: no engine headers for the materializer; the FFI ladder carries the load")
|
|
51
|
+
else
|
|
52
|
+
$INCFLAGS << " -I#{src_root}/include -I#{src_root}/yeptris"
|
|
53
|
+
%w[build-validate/generated build/generated].each do |g|
|
|
54
|
+
d = File.join(File.dirname(src_root), g)
|
|
55
|
+
$INCFLAGS << " -I#{d}" if File.directory?(d)
|
|
56
|
+
end
|
|
57
|
+
# the generated version.h: nothing in the materializer's include
|
|
58
|
+
# chain needs it today; generate it into the workdir so a future
|
|
59
|
+
# include cannot break installs (the vendored tree ships only .in)
|
|
60
|
+
vhin = File.join(src_root, "include", "yeptris", "version.h.in")
|
|
61
|
+
unless File.exist?(File.join(src_root, "include", "yeptris", "version.h"))
|
|
62
|
+
if File.exist?(vhin)
|
|
63
|
+
# CMakeLists sits at the engine tree's root: the vendored gem
|
|
64
|
+
# tree (vendor/libyeptris) or the CI checkout (libyeptris-src)
|
|
65
|
+
ver = File.read(File.join(File.dirname(src_root), "CMakeLists.txt"))[/project\s*\([^)]*?VERSION\s+([0-9.]+)/m, 1] || "0.0.0"
|
|
66
|
+
maj, min, pat = ver.split(".")
|
|
67
|
+
File.write("version.h", File.read(vhin)
|
|
68
|
+
.gsub("@PROJECT_VERSION_MAJOR@", maj || "0")
|
|
69
|
+
.gsub("@PROJECT_VERSION_MINOR@", min || "0")
|
|
70
|
+
.gsub("@PROJECT_VERSION_PATCH@", pat || "0")
|
|
71
|
+
.gsub("@PROJECT_VERSION@", ver))
|
|
72
|
+
$INCFLAGS << " -I."
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
libdir = File.dirname(lib)
|
|
77
|
+
$LIBPATH << libdir
|
|
78
|
+
# the bundle must find libyeptris at require time; the gem root is
|
|
79
|
+
# stable within an installation
|
|
80
|
+
$LDFLAGS << " -Wl,-rpath,#{libdir}" if RUBY_PLATFORM =~ /linux|darwin/
|
|
31
81
|
|
|
32
|
-
|
|
33
|
-
|
|
82
|
+
if have_library("yeptris", "yep_json_string") && have_library("yeptris", "yeptris_visit_json")
|
|
83
|
+
# Windows resolves DLL dependencies beside the loading module —
|
|
84
|
+
# install the dll beside the built bundle
|
|
85
|
+
if RUBY_PLATFORM =~ /mswin|mingw/ && File.dirname(lib) != gem_root
|
|
86
|
+
($INSTALLFILES ||= []) << [lib, "$(RUBYARCHDIR)/yeptris"]
|
|
87
|
+
end
|
|
88
|
+
$CFLAGS << " -O3 -fvisibility=hidden" unless RUBY_PLATFORM =~ /mswin|mingw/
|
|
89
|
+
create_makefile("yeptris/native")
|
|
90
|
+
else
|
|
91
|
+
write_dummy_makefile("yeptris: materializer link probe failed; the FFI ladder carries the load")
|
|
92
|
+
end
|
|
34
93
|
end
|
|
35
|
-
abort "libyeptris not found (set YEPTRIS_LIB_PATH)" unless libdir
|
|
36
|
-
|
|
37
|
-
$LIBPATH << libdir
|
|
38
|
-
# Platform gems bake a RELATIVE rpath so the bundle finds the
|
|
39
|
-
# vendored libyeptris next to itself; absolute build-time paths
|
|
40
|
-
# would dangle on user machines.
|
|
41
|
-
rpath = ENV["YEPTRIS_RPATH"] || libdir
|
|
42
|
-
$LDFLAGS << " -Wl,-rpath,#{rpath}" if RUBY_PLATFORM =~ /linux|darwin/
|
|
43
|
-
have_library("yeptris", "yep_json_string") or abort "yep_json_string not exported — rebuild libyeptris"
|
|
44
|
-
have_library("yeptris", "yeptris_visit_json") or abort "yeptris_visit_json missing"
|
|
45
|
-
|
|
46
|
-
$CFLAGS << " -O3 -fvisibility=hidden" unless RUBY_PLATFORM =~ /mswin|mingw/
|
|
47
|
-
create_makefile("yeptris/native")
|
|
@@ -63,7 +63,18 @@ static int yep_cache_mode = YEP_CACHE_ON;
|
|
|
63
63
|
* rb_ary_new() so 3-element arrays stay EMBEDDED in the RVALUE, and
|
|
64
64
|
* bulk-path hashes get exact capacity from the counted pairs. */
|
|
65
65
|
enum { YEP_SHAPE_PRE = 0, YEP_SHAPE_NATURAL = 1 };
|
|
66
|
-
|
|
66
|
+
/* Default per arch (the #310 arm64 referee matrix, 24 cells over the
|
|
67
|
+
* json-users-shaped corpus): NATURAL — rb_ary_new() keeps 3-element
|
|
68
|
+
* arrays embedded in the RVALUE instead of forcing heap buffers, and
|
|
69
|
+
* it won EVERY cell (1.00-1.04x vs pre's 1.10-1.22x vs JSON.parse).
|
|
70
|
+
* x86 keeps PRE until its referee says otherwise (the GATE judges the
|
|
71
|
+
* per-arch default on CI). */
|
|
72
|
+
#if defined(__aarch64__) || defined(__arm__) || defined(__ARMEL__) || defined(_M_ARM64)
|
|
73
|
+
#define YEP_SHAPE_DEFAULT YEP_SHAPE_NATURAL
|
|
74
|
+
#else
|
|
75
|
+
#define YEP_SHAPE_DEFAULT YEP_SHAPE_PRE
|
|
76
|
+
#endif
|
|
77
|
+
static int yep_shape_mode = YEP_SHAPE_DEFAULT;
|
|
67
78
|
|
|
68
79
|
static uint64_t jr_hash(const char* sp, long sl) {
|
|
69
80
|
/* 8-byte-prefix key (the leptris nametab trick): one safe load
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* version.h — generated from the CMake project VERSION.
|
|
2
|
+
*
|
|
3
|
+
* CMakeLists.txt project(yeptris VERSION ...) is the single source of truth;
|
|
4
|
+
* scripts/bump-version.sh syncs vcpkg.json and CHANGELOG.md from it. Never
|
|
5
|
+
* hand-edit the generated file (it lives in the build tree).
|
|
6
|
+
*/
|
|
7
|
+
#ifndef YEPTRIS_VERSION_H
|
|
8
|
+
#define YEPTRIS_VERSION_H
|
|
9
|
+
|
|
10
|
+
#include <yeptris/api.h>
|
|
11
|
+
|
|
12
|
+
#define YEPTRIS_VERSION_MAJOR 0
|
|
13
|
+
#define YEPTRIS_VERSION_MINOR 6
|
|
14
|
+
#define YEPTRIS_VERSION_PATCH 42
|
|
15
|
+
#define YEPTRIS_VERSION_STRING "0.6.42"
|
|
16
|
+
|
|
17
|
+
#ifdef __cplusplus
|
|
18
|
+
extern "C" {
|
|
19
|
+
#endif
|
|
20
|
+
|
|
21
|
+
/* Returns the library version string ("major.minor.patch").
|
|
22
|
+
* Memory: static storage; valid for the lifetime of the process. */
|
|
23
|
+
YEPTRIS_API const char* yeptris_version(void);
|
|
24
|
+
|
|
25
|
+
#ifdef __cplusplus
|
|
26
|
+
}
|
|
27
|
+
#endif
|
|
28
|
+
|
|
29
|
+
#endif /* YEPTRIS_VERSION_H */
|
data/lib/yeptris/3.4/native.so
CHANGED
|
Binary file
|
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.42.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
|
|
@@ -55,6 +55,19 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument doc, const yeptris_emit_o
|
|
|
55
55
|
* returned. Returns 0 on a NULL document. */
|
|
56
56
|
YEPTRIS_API size_t yeptris_serialize_into(YeptrisDocument doc, char* buf, size_t cap);
|
|
57
57
|
|
|
58
|
+
/* Trusted-cap emission (#352): ONE write pass into the caller's
|
|
59
|
+
* fixed buffer — no sizing pass, no per-call allocation. Returns the
|
|
60
|
+
* byte count written when ret < cap (the document is complete and
|
|
61
|
+
* NUL-terminated at buf[ret]); when ret >= cap the output did not
|
|
62
|
+
* fit — buf holds a partial document and ret is the exact total the
|
|
63
|
+
* document needs (re-call with cap >= ret + 1; the retry writes
|
|
64
|
+
* identical bytes). opts == NULL selects the fidelity mode. Returns 0
|
|
65
|
+
* on a NULL document or buffer (use yeptris_serialize_into for the
|
|
66
|
+
* measure-only form). */
|
|
67
|
+
YEPTRIS_API size_t yeptris_serialize_into_trusted(YeptrisDocument doc,
|
|
68
|
+
const yeptris_emit_options* opts, char* buf,
|
|
69
|
+
size_t cap);
|
|
70
|
+
|
|
58
71
|
/* Streaming emission (13C): bytes are delivered in chunks to sink as
|
|
59
72
|
* the walk proceeds — memory bounded by the largest scalar, never the
|
|
60
73
|
* document. Returns the total byte count, or 0 on a NULL argument or
|
|
@@ -44,6 +44,7 @@ YEPTRIS_API size_t yeptris_serialize_into_ex(YeptrisDocument handle,
|
|
|
44
44
|
em.w.cap = 0;
|
|
45
45
|
em.w.grow = 0;
|
|
46
46
|
em.w.oom = 0;
|
|
47
|
+
em.w.bounded = 0;
|
|
47
48
|
em.doc = (const yeptris_document*)handle;
|
|
48
49
|
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
49
50
|
return 0;
|
|
@@ -97,6 +98,69 @@ YEPTRIS_API size_t yeptris_serialize_into_ex(YeptrisDocument handle,
|
|
|
97
98
|
return wrote;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
YEPTRIS_API size_t yeptris_serialize_into_trusted(YeptrisDocument handle,
|
|
102
|
+
const yeptris_emit_options* opts, char* buf,
|
|
103
|
+
size_t cap) {
|
|
104
|
+
if (handle == NULL || buf == NULL || cap == 0) {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
yep_emitter em;
|
|
108
|
+
em.w.sc_dec = NULL;
|
|
109
|
+
em.w.sc_i = 0;
|
|
110
|
+
em.w.cap = 0;
|
|
111
|
+
em.w.grow = 0;
|
|
112
|
+
em.w.oom = 0;
|
|
113
|
+
em.w.bounded = 0;
|
|
114
|
+
em.doc = (const yeptris_document*)handle;
|
|
115
|
+
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
em.w.p = buf;
|
|
119
|
+
em.w.last = 0;
|
|
120
|
+
em.w.force_flow = 0;
|
|
121
|
+
em.w.in_flow = 0;
|
|
122
|
+
em.w.flow_item = 0;
|
|
123
|
+
em.w.unserializable = 0;
|
|
124
|
+
em.w.canonical = opts_canonical(opts);
|
|
125
|
+
em.w.explicit_doc_start = opts_doc_marker(opts);
|
|
126
|
+
em.w.json = 0;
|
|
127
|
+
em.w.json_compact = 0;
|
|
128
|
+
em.w.json_pretty = 0;
|
|
129
|
+
em.w.pretty_depth = 0;
|
|
130
|
+
em.w.best_width = opts_width(opts);
|
|
131
|
+
em.w.col = 0;
|
|
132
|
+
em.w.sv_input = ((const yeptris_document*)handle)->dom->input_base;
|
|
133
|
+
em.w.sv_arena = ((const yeptris_document*)handle)->dom->str;
|
|
134
|
+
em.w.sink = NULL;
|
|
135
|
+
em.w.watermark = 0;
|
|
136
|
+
em.w.sink_aborted = 0;
|
|
137
|
+
em.w.flushed = 0;
|
|
138
|
+
memset(&em.canon_names, 0, sizeof(em.canon_names));
|
|
139
|
+
if (em.w.canonical && !yep_nametab_init(&em.canon_names, yep_system_allocator())) {
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
/* #352: ONE bounded wet pass — no memo, no dry run (the emit-split
|
|
143
|
+
* table's 2p/1p column priced the dry pass at ~0.55x). Routes derive
|
|
144
|
+
* on demand (the streaming route's bytes); an oversized write sets
|
|
145
|
+
* oom and the pass keeps counting, so the exact size falls out of
|
|
146
|
+
* the same walk and the retry writes identical bytes. */
|
|
147
|
+
em.w.cap = cap;
|
|
148
|
+
em.w.grow = 1;
|
|
149
|
+
em.w.bounded = 1;
|
|
150
|
+
size_t wrote = yep_emit_run(&em, 0);
|
|
151
|
+
em.w.bounded = 0;
|
|
152
|
+
int fit = !em.w.oom && !em.w.unserializable;
|
|
153
|
+
size_t ret = fit ? wrote : em.w.len;
|
|
154
|
+
if (em.canon_names.sys != NULL) {
|
|
155
|
+
yep_nametab_free(&em.canon_names);
|
|
156
|
+
}
|
|
157
|
+
if (!fit) {
|
|
158
|
+
return ret; /* ret >= cap: buf holds a partial document */
|
|
159
|
+
}
|
|
160
|
+
buf[wrote] = '\0';
|
|
161
|
+
return ret; /* ret < cap: the document is complete */
|
|
162
|
+
}
|
|
163
|
+
|
|
100
164
|
YEPTRIS_API void yeptris_free(void* p) {
|
|
101
165
|
free(p);
|
|
102
166
|
}
|
|
@@ -112,6 +176,7 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
|
|
|
112
176
|
em.w.cap = 0;
|
|
113
177
|
em.w.grow = 0;
|
|
114
178
|
em.w.oom = 0;
|
|
179
|
+
em.w.bounded = 0;
|
|
115
180
|
em.doc = (const yeptris_document*)handle;
|
|
116
181
|
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
117
182
|
return NULL;
|
|
@@ -199,6 +264,7 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
|
|
|
199
264
|
em.w.cap = 0;
|
|
200
265
|
em.w.grow = 0;
|
|
201
266
|
em.w.oom = 0;
|
|
267
|
+
em.w.bounded = 0;
|
|
202
268
|
em.doc = (const yeptris_document*)handle;
|
|
203
269
|
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
204
270
|
return NULL;
|
|
@@ -227,8 +293,15 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
|
|
|
227
293
|
if (em.w.canonical && !yep_nametab_init(&em.canon_names, yep_system_allocator())) {
|
|
228
294
|
return NULL;
|
|
229
295
|
}
|
|
230
|
-
|
|
231
|
-
|
|
296
|
+
/* #739: ONE wet pass into a growable buffer — the dry exact-sizing
|
|
297
|
+
* pass priced ~2x on the json lane (the #352 slice-2 pattern);
|
|
298
|
+
* the estimate starts at the input size (+64) and doubles on
|
|
299
|
+
* demand, shrink-to-fit at the end. */
|
|
300
|
+
size_t est = em.doc->dom->input_len + 64;
|
|
301
|
+
if (est < 256) {
|
|
302
|
+
est = 256;
|
|
303
|
+
}
|
|
304
|
+
char* out = (char*)malloc(est);
|
|
232
305
|
if (out == NULL) {
|
|
233
306
|
if (em.canon_names.sys != NULL) {
|
|
234
307
|
yep_nametab_free(&em.canon_names);
|
|
@@ -236,7 +309,24 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
|
|
|
236
309
|
return NULL;
|
|
237
310
|
}
|
|
238
311
|
em.w.p = out;
|
|
312
|
+
em.w.cap = est;
|
|
313
|
+
em.w.grow = 1;
|
|
239
314
|
size_t wrote = yep_emit_run(&em, 0);
|
|
315
|
+
em.w.grow = 0;
|
|
316
|
+
out = em.w.p; /* wr_grow reallocs through w->p — the local is stale */
|
|
317
|
+
if (em.w.oom || em.w.unserializable) {
|
|
318
|
+
free(out);
|
|
319
|
+
if (em.canon_names.sys != NULL) {
|
|
320
|
+
yep_nametab_free(&em.canon_names);
|
|
321
|
+
}
|
|
322
|
+
return NULL;
|
|
323
|
+
}
|
|
324
|
+
if (em.w.cap - (wrote + 1) > em.w.cap / 8) {
|
|
325
|
+
char* tight = (char*)realloc(out, wrote + 1);
|
|
326
|
+
if (tight != NULL) {
|
|
327
|
+
out = tight;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
240
330
|
if (em.canon_names.sys != NULL) {
|
|
241
331
|
yep_nametab_free(&em.canon_names);
|
|
242
332
|
}
|
|
@@ -260,6 +350,7 @@ YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len,
|
|
|
260
350
|
em.w.cap = 0;
|
|
261
351
|
em.w.grow = 0;
|
|
262
352
|
em.w.oom = 0;
|
|
353
|
+
em.w.bounded = 0;
|
|
263
354
|
em.doc = (const yeptris_document*)handle;
|
|
264
355
|
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
265
356
|
return NULL;
|
|
@@ -326,6 +417,7 @@ char* yep_serialize_json_compact(const yeptris_document* doc, size_t* len) {
|
|
|
326
417
|
em.w.cap = 0;
|
|
327
418
|
em.w.grow = 0;
|
|
328
419
|
em.w.oom = 0;
|
|
420
|
+
em.w.bounded = 0;
|
|
329
421
|
em.doc = doc;
|
|
330
422
|
em.w.p = NULL;
|
|
331
423
|
em.w.last = 0;
|
|
@@ -389,6 +481,7 @@ char* yep_serialize_json_pretty_ex(const yeptris_document* doc, size_t* len, con
|
|
|
389
481
|
em.w.cap = 0;
|
|
390
482
|
em.w.grow = 0;
|
|
391
483
|
em.w.oom = 0;
|
|
484
|
+
em.w.bounded = 0;
|
|
392
485
|
em.doc = doc;
|
|
393
486
|
em.w.p = NULL;
|
|
394
487
|
em.w.last = 0;
|
|
@@ -459,6 +552,7 @@ YEPTRIS_API size_t yeptris_serialize_stream(YeptrisDocument handle,
|
|
|
459
552
|
em.w.cap = 0;
|
|
460
553
|
em.w.grow = 0;
|
|
461
554
|
em.w.oom = 0;
|
|
555
|
+
em.w.bounded = 0;
|
|
462
556
|
em.doc = (const yeptris_document*)handle;
|
|
463
557
|
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
464
558
|
return 0;
|
|
@@ -99,7 +99,11 @@ static void wr_put(yep_writer* w, const char* p, uint32_t n) {
|
|
|
99
99
|
* first byte of the next heap block's header (the #182 flake:
|
|
100
100
|
* corrupt deterministic, detected later, timing-varying) */
|
|
101
101
|
if (w->grow && w->len + n + 1 > w->cap) {
|
|
102
|
-
|
|
102
|
+
if (w->bounded) {
|
|
103
|
+
w->oom = 1; /* keep counting: the trusted caller reads the size */
|
|
104
|
+
} else {
|
|
105
|
+
wr_grow(w, n);
|
|
106
|
+
}
|
|
103
107
|
}
|
|
104
108
|
if (w->oom) {
|
|
105
109
|
w->len += n; /* keep counting: the caller fails on oom */
|
|
@@ -122,7 +126,11 @@ static void wr_byte(yep_writer* w, char c) {
|
|
|
122
126
|
return;
|
|
123
127
|
}
|
|
124
128
|
if (w->grow && w->len + 2 > w->cap) { /* +1 write, +1 terminator */
|
|
125
|
-
|
|
129
|
+
if (w->bounded) {
|
|
130
|
+
w->oom = 1; /* keep counting: the trusted caller reads the size */
|
|
131
|
+
} else {
|
|
132
|
+
wr_grow(w, 1);
|
|
133
|
+
}
|
|
126
134
|
}
|
|
127
135
|
if (w->oom) {
|
|
128
136
|
w->len++;
|
|
@@ -346,37 +354,13 @@ static void emit_literal(yep_writer* w, const yep_dnode* n, int parent_col) {
|
|
|
346
354
|
wr_indent(w, pad);
|
|
347
355
|
}
|
|
348
356
|
}
|
|
349
|
-
/* The
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
* pass
|
|
355
|
-
uint32_t
|
|
356
|
-
while (ls > 0 && p[ls - 1] != '\n') {
|
|
357
|
-
ls--;
|
|
358
|
-
}
|
|
359
|
-
int tab_last = 0;
|
|
360
|
-
int ws_last = 1;
|
|
361
|
-
for (uint32_t i = ls; i < body; i++) {
|
|
362
|
-
if (p[i] == '\t') {
|
|
363
|
-
tab_last = 1;
|
|
364
|
-
} else if (p[i] != ' ') {
|
|
365
|
-
ws_last = 0;
|
|
366
|
-
break;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
/* the reader swallows the own break of a whitespace-only last line
|
|
370
|
-
* that carries a TAB (the tab-indent law shifts the line's content
|
|
371
|
-
* start): "|+\n:\n \t\n\n\n" reads trail=2 where a spaces-only
|
|
372
|
-
* line reads 1:1 (the nightly's round 32, crash-90c98918: pass1
|
|
373
|
-
* "|+\n :\n \t\n\n" re-parsed one break short and the second
|
|
374
|
-
* pass emitted clip). One extra break spells the tab case — and
|
|
375
|
-
* ONLY that case: a CONTENT last line that merely carries a tab
|
|
376
|
-
* ("|+\n \t:\n..." — the round-33 composite, crash-306b67eb)
|
|
377
|
-
* reads its own break 1:1, and the extra break made the value
|
|
378
|
-
* GROW one break per pass. */
|
|
379
|
-
for (uint32_t i = 0; i < trail + ((tab_last && ws_last) ? 1 : 0); i++) {
|
|
357
|
+
/* The keep spelling writes exactly the parsed trailing breaks:
|
|
358
|
+
* written N reads N. (The round-32 extra break for tab-carrying
|
|
359
|
+
* ws-only last lines compensated a READER bug -- the tab-content
|
|
360
|
+
* arm consumed its line's own break without counting it toward
|
|
361
|
+
* trailing -- and the reader counts it now; the compensation made
|
|
362
|
+
* every pass GROW one break.) */
|
|
363
|
+
for (uint32_t i = 0; i < trail; i++) {
|
|
380
364
|
wr_byte(w, '\n');
|
|
381
365
|
}
|
|
382
366
|
}
|
|
@@ -688,22 +672,55 @@ static void wr_anchor_check(yep_writer* w, const yep_view* name) {
|
|
|
688
672
|
}
|
|
689
673
|
}
|
|
690
674
|
|
|
675
|
+
/* The anchor-name spell laws, shared by every anchor write path (the
|
|
676
|
+
* node lead AND the line-tail props: the round-45 bonus catch -- a
|
|
677
|
+
* PS-named anchor on a SEQ spelled raw through emit_props_tail and
|
|
678
|
+
* the re-parse died on the normalized break). Returns nonzero when
|
|
679
|
+
* the serialize must fail. */
|
|
680
|
+
static int anchor_name_unspellable(yep_writer* w, const yep_dnode* n) {
|
|
681
|
+
yep_view chk = wv(w, n->anchor);
|
|
682
|
+
if (chk.len == 0) {
|
|
683
|
+
return 0;
|
|
684
|
+
}
|
|
685
|
+
if (chk.p[chk.len - 1] == ':') {
|
|
686
|
+
/* a SCALAR keeps riding raw only when it spells INLINE
|
|
687
|
+
* ("k: &n: v" is byte-stable); a BLOCK-style spell puts the
|
|
688
|
+
* header where the separator should be ("&: |" -- the
|
|
689
|
+
* nightly's round 44, crash-07ad9ecc) -- there is no spell:
|
|
690
|
+
* fail the serialize */
|
|
691
|
+
if (n->kind != 0 || n->style == 4 || n->style == 5) {
|
|
692
|
+
w->unserializable = 1;
|
|
693
|
+
return 1;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
/* a NORMALIZE-SUBJECT byte (LS/PS raw, NEL, or a hard break)
|
|
697
|
+
* re-reads as a folded break: pass2 sees a broken/empty name and
|
|
698
|
+
* dies. YAML has no quoted anchor names: fail the serialize. */
|
|
699
|
+
for (size_t ci = 0; ci < chk.len; ci++) {
|
|
700
|
+
unsigned char cb = (unsigned char)chk.p[ci];
|
|
701
|
+
if (cb == '\n' || cb == '\r') {
|
|
702
|
+
w->unserializable = 1;
|
|
703
|
+
return 1;
|
|
704
|
+
}
|
|
705
|
+
if (cb == 0xC2 && ci + 1 < chk.len && (unsigned char)chk.p[ci + 1] == 0x85) {
|
|
706
|
+
w->unserializable = 1; /* NEL */
|
|
707
|
+
return 1;
|
|
708
|
+
}
|
|
709
|
+
if (cb == 0xE2 && ci + 2 < chk.len && (unsigned char)chk.p[ci + 1] == 0x80 &&
|
|
710
|
+
((unsigned char)chk.p[ci + 2] & 0xFE) == 0xA8) {
|
|
711
|
+
w->unserializable = 1; /* LS/PS */
|
|
712
|
+
return 1;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return 0;
|
|
716
|
+
}
|
|
717
|
+
|
|
691
718
|
static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
|
|
692
719
|
yep_writer* w = &em->w;
|
|
693
720
|
if (n->anchor.len == 0) {
|
|
694
721
|
return;
|
|
695
722
|
}
|
|
696
|
-
|
|
697
|
-
* (2SXE): spelled directly before a collection ("&: [[]]", the
|
|
698
|
-
* nightly's round 31, crash-66a513ea; "k: &: [[1]]" fails its own
|
|
699
|
-
* re-parse the same way) every spelling re-lexes differently --
|
|
700
|
-
* the riding/yield law reads the colon as the key indicator at
|
|
701
|
-
* the follower, so the tree changes or the parse dies. A scalar
|
|
702
|
-
* node keeps riding raw ("k: &n: v" round-trips byte-stably).
|
|
703
|
-
* There is no spell for the collection form: fail the serialize. */
|
|
704
|
-
yep_view chk = wv(w, n->anchor);
|
|
705
|
-
if (n->kind != 0 && chk.len > 0 && chk.p[chk.len - 1] == ':') {
|
|
706
|
-
w->unserializable = 1;
|
|
723
|
+
if (anchor_name_unspellable(w, n)) {
|
|
707
724
|
return;
|
|
708
725
|
}
|
|
709
726
|
wr_byte(w, '&');
|
|
@@ -875,6 +892,9 @@ static void emit_props_tail(yep_writer* w, const yep_dnode* n) {
|
|
|
875
892
|
wr_tag_token(w, &tdec2);
|
|
876
893
|
}
|
|
877
894
|
if (n->anchor.len > 0) {
|
|
895
|
+
if (anchor_name_unspellable(w, n)) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
878
898
|
wr_byte(w, ' ');
|
|
879
899
|
yep_view adec2 = wv(w, n->anchor);
|
|
880
900
|
wr_anchor_check(w, &adec2);
|
|
@@ -892,6 +912,9 @@ static void emit_node(yep_emitter* em, uint32_t id, int parent_col, int as_key);
|
|
|
892
912
|
* written). Conservative factor-4 estimate for every expandable
|
|
893
913
|
* byte — overestimating only takes the (always-legal) explicit form. */
|
|
894
914
|
static int key_written_over_limit(yep_writer* w, const yep_dnode* kn) {
|
|
915
|
+
if (w->json) {
|
|
916
|
+
return 0; /* JSON has no simple-key law: every key writes (#739) */
|
|
917
|
+
}
|
|
895
918
|
uint64_t est = 2; /* the quotes */
|
|
896
919
|
yep_view kv_ = wv(w, kn->value);
|
|
897
920
|
const unsigned char* q = (const unsigned char*)kv_.p;
|
|
@@ -1069,7 +1092,11 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
|
|
|
1069
1092
|
w->len += total;
|
|
1070
1093
|
} else {
|
|
1071
1094
|
if (w->grow && w->len + total + 1 > w->cap) {
|
|
1072
|
-
|
|
1095
|
+
if (w->bounded) {
|
|
1096
|
+
w->oom = 1; /* keep counting: the trusted caller reads the size */
|
|
1097
|
+
} else {
|
|
1098
|
+
wr_grow(w, total);
|
|
1099
|
+
}
|
|
1073
1100
|
}
|
|
1074
1101
|
if (w->oom) {
|
|
1075
1102
|
w->len += total; /* keep counting: the caller fails on oom */
|
|
@@ -57,6 +57,9 @@ typedef struct yep_writer {
|
|
|
57
57
|
size_t cap;
|
|
58
58
|
int grow;
|
|
59
59
|
int oom;
|
|
60
|
+
int bounded; /* #352: caller-owned fixed buffer — an oversized write
|
|
61
|
+
sets oom and keeps counting (the needed size falls
|
|
62
|
+
out of the same pass); wr_grow is never called */
|
|
60
63
|
int in_flow; /* scalars inside flow collections: plain must drop
|
|
61
64
|
',', '[', ']', '{', '}' — flow terminators */
|
|
62
65
|
int flow_item; /* the NEXT scalar is a flow-SEQ item: a null must
|
|
@@ -918,7 +918,27 @@ static int e_block_scalar(yep_engine* e, yep_event* ev, int parent_col) {
|
|
|
918
918
|
saw_content = 1;
|
|
919
919
|
pending_breaks = 0;
|
|
920
920
|
trailing_breaks = 0;
|
|
921
|
-
|
|
921
|
+
/* the line's OWN break counts toward trailing (the
|
|
922
|
+
* content-line arm and the spaces-past-indent arm
|
|
923
|
+
* scan it below): consuming it silently made keep
|
|
924
|
+
* spell one break short whenever a tab-carrying
|
|
925
|
+
* content line ENDED the block ("|+\n\n >\t\n
|
|
926
|
+
* \t \n" — libyaml keeps the final break, the
|
|
927
|
+
* nightly's round 37) */
|
|
928
|
+
{
|
|
929
|
+
size_t at = li.end;
|
|
930
|
+
size_t br = yep_scan_break_len(e->p, e->len, at);
|
|
931
|
+
if (br > 0) {
|
|
932
|
+
trailing_breaks = 1;
|
|
933
|
+
e->pos = at + br;
|
|
934
|
+
e->line++;
|
|
935
|
+
e->line_start = e->pos;
|
|
936
|
+
e->li_cache_line = 0;
|
|
937
|
+
e->shape_cache_line = 0;
|
|
938
|
+
} else {
|
|
939
|
+
e->pos = at;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
922
942
|
continue;
|
|
923
943
|
}
|
|
924
944
|
}
|
|
@@ -1187,10 +1207,18 @@ static yep_view e_resolve_tag_raw(yep_engine* e, yep_view tag) {
|
|
|
1187
1207
|
if (tag.len == 1 && tag.p[0] == '!') {
|
|
1188
1208
|
return tag;
|
|
1189
1209
|
}
|
|
1190
|
-
/* longest matching handle wins
|
|
1210
|
+
/* longest matching handle wins; a redefined PRIMARY ('%TAG ! ...')
|
|
1211
|
+
* cannot capture a '!!'-led tag — the secondary handle resolves
|
|
1212
|
+
* through its own entry or the fixed tag:yaml.org,2002: default
|
|
1213
|
+
* (spec 6.8.2.2; libyaml: "%TAG ! !p/" + "!!i" is
|
|
1214
|
+
* tag:yaml.org,2002:i, the nightly's round 52, crash-763bb852) */
|
|
1215
|
+
int secondary = tag.len >= 2 && tag.p[0] == '!' && tag.p[1] == '!';
|
|
1191
1216
|
int best = -1;
|
|
1192
1217
|
uint32_t best_len = 0;
|
|
1193
1218
|
for (int i = 0; i < e->tagmap_n; i++) {
|
|
1219
|
+
if (secondary && e->tagmap[i].handle.len == 1) {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1194
1222
|
if (tag.len >= e->tagmap[i].handle.len && e->tagmap[i].handle.len > best_len &&
|
|
1195
1223
|
memcmp(tag.p, e->tagmap[i].handle.p, e->tagmap[i].handle.len) == 0) {
|
|
1196
1224
|
best = i;
|
|
@@ -2186,8 +2214,11 @@ static void e_flow_frame_init(void* stv, int idx, int kind, int pending_key) {
|
|
|
2186
2214
|
uint8_t pair_q; /* the pair opened on an explicit '?': its
|
|
2187
2215
|
* JSON-style ':' separator is still ahead */
|
|
2188
2216
|
uint8_t pair_wrap;
|
|
2189
|
-
uint8_t q_coll;
|
|
2190
|
-
uint8_t
|
|
2217
|
+
uint8_t q_coll; /* seq: an explicit '?' key collection awaits completion */
|
|
2218
|
+
uint8_t q_empty_value; /* seq: the '?' empty-key pair — 1: opened,
|
|
2219
|
+
* 2: its value state (the entry comma was
|
|
2220
|
+
* consumed inside the pair, yeptris#741) */
|
|
2221
|
+
uint8_t key_coll; /* map: the nested collection opened in key position */
|
|
2191
2222
|
int32_t buf_from;
|
|
2192
2223
|
uint32_t entry_line;
|
|
2193
2224
|
uint32_t entries;
|
|
@@ -2203,6 +2234,7 @@ static void e_flow_frame_init(void* stv, int idx, int kind, int pending_key) {
|
|
|
2203
2234
|
st[idx].pair_q = 0;
|
|
2204
2235
|
st[idx].pair_wrap = 0;
|
|
2205
2236
|
st[idx].q_coll = 0;
|
|
2237
|
+
st[idx].q_empty_value = 0;
|
|
2206
2238
|
st[idx].buf_from = -1;
|
|
2207
2239
|
st[idx].entry_line = 0;
|
|
2208
2240
|
st[idx].entries = 0;
|
|
@@ -2210,20 +2242,21 @@ static void e_flow_frame_init(void* stv, int idx, int kind, int pending_key) {
|
|
|
2210
2242
|
|
|
2211
2243
|
static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_id) {
|
|
2212
2244
|
struct {
|
|
2213
|
-
uint8_t kind;
|
|
2214
|
-
uint8_t pending_key;
|
|
2215
|
-
uint8_t sep;
|
|
2216
|
-
uint8_t just_closed;
|
|
2217
|
-
uint8_t pair_open;
|
|
2218
|
-
uint8_t pair_value;
|
|
2219
|
-
uint8_t pair_q;
|
|
2220
|
-
|
|
2221
|
-
uint8_t pair_wrap;
|
|
2222
|
-
uint8_t q_coll;
|
|
2223
|
-
uint8_t
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
uint32_t
|
|
2245
|
+
uint8_t kind; /* 0 seq, 1 map */
|
|
2246
|
+
uint8_t pending_key; /* map: 1 key seen awaiting ':' value; 2 key emitted w/o ':' */
|
|
2247
|
+
uint8_t sep; /* a ',' was consumed and no entry followed yet */
|
|
2248
|
+
uint8_t just_closed; /* a nested collection closed here (key candidate) */
|
|
2249
|
+
uint8_t pair_open; /* seq: a buffered single-pair awaits its MAP_END */
|
|
2250
|
+
uint8_t pair_value; /* seq: the open pair already has its value */
|
|
2251
|
+
uint8_t pair_q; /* seq: the pair opened on an explicit '?':
|
|
2252
|
+
* its JSON-style ':' separator is still ahead */
|
|
2253
|
+
uint8_t pair_wrap; /* map frame pushed as a seq single-pair */
|
|
2254
|
+
uint8_t q_coll; /* seq: an explicit '?' key collection awaits completion */
|
|
2255
|
+
uint8_t q_empty_value; /* seq: the '?' empty-key pair's state (#741) */
|
|
2256
|
+
uint8_t key_coll; /* map: a nested collection opened in key position */
|
|
2257
|
+
int32_t buf_from; /* seq: entry events buffered from this index */
|
|
2258
|
+
uint32_t entry_line; /* seq: line where the buffered entry ended */
|
|
2259
|
+
uint32_t entries; /* completed entries (for trailing-comma detection) */
|
|
2227
2260
|
} st[YEP_MAX_DEPTH];
|
|
2228
2261
|
int n = 0;
|
|
2229
2262
|
|
|
@@ -2263,6 +2296,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2263
2296
|
}
|
|
2264
2297
|
int keyish = (st[n - 1].kind == 1 && st[n - 1].pending_key == 0);
|
|
2265
2298
|
int q_key = 0;
|
|
2299
|
+
int q_empty_key = 0; /* the '?' synthesized a null key (#741) */
|
|
2266
2300
|
int rc;
|
|
2267
2301
|
if (e_flow_ws(e) != 0) {
|
|
2268
2302
|
return -1;
|
|
@@ -2292,6 +2326,10 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2292
2326
|
!(e->p[n2] == ' ' || e->p[n2] == '\t' || e->p[n2] == '\n' || e->p[n2] == '\r' ||
|
|
2293
2327
|
yep_ct_is((unsigned char)e->p[n2], YEP_CT_FLOW_IND))) {
|
|
2294
2328
|
st[n - 1].pair_q = 0;
|
|
2329
|
+
/* yeptris#741: the consumed ':' is the pair's VALUE
|
|
2330
|
+
* indicator — the '?' empty-key pair's value state ends
|
|
2331
|
+
* here or the value node trips the entry guard */
|
|
2332
|
+
st[n - 1].q_empty_value = 0;
|
|
2295
2333
|
e->pos++;
|
|
2296
2334
|
continue;
|
|
2297
2335
|
}
|
|
@@ -2319,6 +2357,15 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2319
2357
|
* in a sequence a ':'-led token is a new plain, so it errors */
|
|
2320
2358
|
return e_fail(e, YEP_ERR_UNEXPECTED, e->pos);
|
|
2321
2359
|
}
|
|
2360
|
+
if (st[n - 1].kind == 0 && st[n - 1].q_empty_value == 2 && e->pos < e->len &&
|
|
2361
|
+
e->p[e->pos] != ',' && e->p[e->pos] != ']' && e->p[e->pos] != '}' &&
|
|
2362
|
+
e->p[e->pos] != ':') {
|
|
2363
|
+
/* yeptris#741: a node in the '?' empty-key pair's value
|
|
2364
|
+
* state — ENTRY_MAPPING_VALUE wants VALUE/ENTRY/END only
|
|
2365
|
+
* ("[?, a]" is the libyaml "did not find expected ',' or
|
|
2366
|
+
* ']'" error) */
|
|
2367
|
+
return e_fail(e, YEP_ERR_UNEXPECTED, e->pos);
|
|
2368
|
+
}
|
|
2322
2369
|
if (e->pos < e->len && e->p[e->pos] == '?' &&
|
|
2323
2370
|
(e->pos + 1 >= e->len || e->p[e->pos + 1] == ' ' || e->p[e->pos + 1] == '\t' ||
|
|
2324
2371
|
e->p[e->pos + 1] == '\n' || e->p[e->pos + 1] == '\r' ||
|
|
@@ -2342,6 +2389,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2342
2389
|
ev.col = e_col(e, e->pos) + 1;
|
|
2343
2390
|
rc = 1;
|
|
2344
2391
|
q_key = 1;
|
|
2392
|
+
q_empty_key = (st[n - 1].kind == 0);
|
|
2345
2393
|
} else {
|
|
2346
2394
|
rc = e_flow_node(e, &ev, keyish);
|
|
2347
2395
|
q_key = 1;
|
|
@@ -2368,10 +2416,15 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2368
2416
|
st[n - 1].q_coll = 1;
|
|
2369
2417
|
}
|
|
2370
2418
|
}
|
|
2371
|
-
if (!q_key) {
|
|
2419
|
+
if (!q_key && st[n - 1].pair_open) {
|
|
2372
2420
|
/* a VALUE collection completes the pending pair; a KEY
|
|
2373
2421
|
* collection must not (its pair completes at ':' or the
|
|
2374
|
-
* boundary with a null value)
|
|
2422
|
+
* boundary with a null value). The gate is pair_open:
|
|
2423
|
+
* in a SEQUENCE a plain collection entry has no pair,
|
|
2424
|
+
* and the unguarded write planted a stale pair_value=1
|
|
2425
|
+
* that a LATER '?'-opened pair wrap inherited -- its
|
|
2426
|
+
* comma flush then skipped the implicit null
|
|
2427
|
+
* (crash-8106600e, "[... , ? 20:50 , ...]"). */
|
|
2375
2428
|
st[n - 1].pair_value = 1;
|
|
2376
2429
|
}
|
|
2377
2430
|
unsigned char oc = (unsigned char)e->p[e->pos];
|
|
@@ -2550,6 +2603,9 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2550
2603
|
if (emit_now(e, &ev) != 0) {
|
|
2551
2604
|
return -2;
|
|
2552
2605
|
}
|
|
2606
|
+
if (q_empty_key) {
|
|
2607
|
+
st[n - 1].q_empty_value = 1;
|
|
2608
|
+
}
|
|
2553
2609
|
continue;
|
|
2554
2610
|
}
|
|
2555
2611
|
}
|
|
@@ -2584,6 +2640,18 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2584
2640
|
unsigned char c = (unsigned char)e->p[e->pos];
|
|
2585
2641
|
if (c == ',') {
|
|
2586
2642
|
e->pos++;
|
|
2643
|
+
if (st[n - 1].kind == 0 && st[n - 1].pair_open && st[n - 1].pair_q &&
|
|
2644
|
+
!st[n - 1].pair_value && st[n - 1].q_empty_value == 1) {
|
|
2645
|
+
/* yeptris#741: the '?' empty-key pair's first comma is
|
|
2646
|
+
* the entry separator libyaml's ENTRY_MAPPING_KEY
|
|
2647
|
+
* consumed to reach its VALUE state — the pair stays
|
|
2648
|
+
* open and awaits ':' (value), ',' (empty value +
|
|
2649
|
+
* close), or ']' (empty value + close). Closing here
|
|
2650
|
+
* spilled the following ': x' into a second entry
|
|
2651
|
+
* ("[? ,: x , a]" read as two maps). */
|
|
2652
|
+
st[n - 1].q_empty_value = 2;
|
|
2653
|
+
continue;
|
|
2654
|
+
}
|
|
2587
2655
|
if (st[n - 1].q_coll && st[n - 1].buf_from >= 0) {
|
|
2588
2656
|
int32_t bf0 = st[n - 1].buf_from;
|
|
2589
2657
|
st[n - 1].buf_from = -1;
|
|
@@ -2618,6 +2686,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2618
2686
|
st[n - 1].pair_open = 0;
|
|
2619
2687
|
st[n - 1].pair_value = 0;
|
|
2620
2688
|
st[n - 1].pair_q = 0;
|
|
2689
|
+
st[n - 1].q_empty_value = 0;
|
|
2621
2690
|
}
|
|
2622
2691
|
if (st[n - 1].buf_from >= 0) {
|
|
2623
2692
|
st[n - 1].buf_from = -1;
|
|
@@ -2751,6 +2820,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2751
2820
|
}
|
|
2752
2821
|
st[n - 1].pair_open = 0;
|
|
2753
2822
|
st[n - 1].pair_value = 0;
|
|
2823
|
+
st[n - 1].q_empty_value = 0;
|
|
2754
2824
|
}
|
|
2755
2825
|
if (st[n - 1].buf_from >= 0) {
|
|
2756
2826
|
st[n - 1].buf_from = -1;
|
|
@@ -2849,7 +2919,8 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2849
2919
|
}
|
|
2850
2920
|
}
|
|
2851
2921
|
if (was_empty) {
|
|
2852
|
-
yep_event kv; /* "[ : v" — the empty key
|
|
2922
|
+
yep_event kv; /* "[ : v" — the empty key (CFD4,
|
|
2923
|
+
* ledgered suite-VALID) */
|
|
2853
2924
|
e_event_init(&kv, YEP_EV_SCALAR);
|
|
2854
2925
|
kv.implicit = 1;
|
|
2855
2926
|
if (emit_now(e, &kv) != 0) {
|
|
@@ -2866,13 +2937,52 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2866
2937
|
continue; /* value next */
|
|
2867
2938
|
}
|
|
2868
2939
|
if (st[n - 1].kind == 0 && n >= 1) {
|
|
2940
|
+
if (st[n - 1].pair_open && st[n - 1].q_empty_value == 2) {
|
|
2941
|
+
/* yeptris#741: the ':' after the '?' empty-key
|
|
2942
|
+
* pair's comma completes THE SAME pair (the pair's
|
|
2943
|
+
* VALUE indicator), not a new entry */
|
|
2944
|
+
size_t p2 = e->pos; /* the ':' byte is already consumed */
|
|
2945
|
+
while (p2 < e->len && (e->p[p2] == ' ' || e->p[p2] == '\t')) {
|
|
2946
|
+
p2++;
|
|
2947
|
+
}
|
|
2948
|
+
if (p2 < e->len && (e->p[p2] == ',' || e->p[p2] == ']' || e->p[p2] == '}')) {
|
|
2949
|
+
/* empty value at the closer: the pair closes
|
|
2950
|
+
* here; the closer dispatches as the separator */
|
|
2951
|
+
yep_event nv;
|
|
2952
|
+
e_event_init(&nv, YEP_EV_SCALAR);
|
|
2953
|
+
nv.implicit = 1;
|
|
2954
|
+
nv.style = YEP_STYLE_PLAIN;
|
|
2955
|
+
if (emit_now(e, &nv) != 0) {
|
|
2956
|
+
return -2;
|
|
2957
|
+
}
|
|
2958
|
+
yep_event mk;
|
|
2959
|
+
e_event_init(&mk, YEP_EV_MAP_END);
|
|
2960
|
+
if (emit_now(e, &mk) != 0) {
|
|
2961
|
+
return -2;
|
|
2962
|
+
}
|
|
2963
|
+
st[n - 1].pair_open = 0;
|
|
2964
|
+
st[n - 1].pair_value = 0;
|
|
2965
|
+
st[n - 1].pair_q = 0;
|
|
2966
|
+
st[n - 1].q_empty_value = 0;
|
|
2967
|
+
st[n - 1].sep = 1;
|
|
2968
|
+
st[n - 1].just_closed = 0;
|
|
2969
|
+
e->pos = p2;
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
st[n - 1].q_empty_value = 0;
|
|
2973
|
+
e->pos++;
|
|
2974
|
+
continue;
|
|
2975
|
+
}
|
|
2869
2976
|
if (st[n - 1].pair_open) {
|
|
2870
2977
|
/* "[s: : v]" — ':' in the open pair's value slot
|
|
2871
2978
|
* is not node content (libyaml: "did not find
|
|
2872
2979
|
* expected node content") */
|
|
2873
2980
|
return e_fail(e, YEP_ERR_UNEXPECTED, e->pos);
|
|
2874
2981
|
}
|
|
2875
|
-
/* '[ : v' — single-pair mapping with an empty key
|
|
2982
|
+
/* '[ : v' — single-pair mapping with an empty key
|
|
2983
|
+
* (CFD4, ledgered suite-VALID: the suite's tree parses
|
|
2984
|
+
* the bare-colon entry; libyaml's rejection is the
|
|
2985
|
+
* ledgered deviation) */
|
|
2876
2986
|
if (n >= e->max_depth) {
|
|
2877
2987
|
return e_fail(e, YEP_ERR_DEPTH, e->pos);
|
|
2878
2988
|
}
|
|
@@ -4188,6 +4298,15 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
|
|
|
4188
4298
|
p[i + 3] == '\r'));
|
|
4189
4299
|
if (rescue) {
|
|
4190
4300
|
c->quote = 0;
|
|
4301
|
+
/* the marker is an unconditional document boundary (the
|
|
4302
|
+
* parser's own law) and the parser's post-cut reset wipes
|
|
4303
|
+
* the flow state; the walk must too, or a flow opened in
|
|
4304
|
+
* an EARLIER document re-lexes the next document's spaces
|
|
4305
|
+
* as token starts ("1,\"\n--- 2[\"\n--- T \"<LS>" --
|
|
4306
|
+
* whole kept the LS raw inside the phantom quote while
|
|
4307
|
+
* every chunked window folded it; the nightly's round 35,
|
|
4308
|
+
* crash-ef534bdf) */
|
|
4309
|
+
c->in_flow = 0;
|
|
4191
4310
|
} else {
|
|
4192
4311
|
/* NEL folds inside quotes too; LS/PS stay raw content — but
|
|
4193
4312
|
* every break still arms the line-start state, or a col-0
|
|
@@ -4322,6 +4441,9 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
|
|
|
4322
4441
|
if (marker || (p[k] == '%' && ind == 0 && c->bs_content) ||
|
|
4323
4442
|
(!marker && c->bs_indent != (uint32_t)-1 && ind <= (uint32_t)c->bs_indent)) {
|
|
4324
4443
|
c->bs_open = 0; /* the fold ends: this line processes normally */
|
|
4444
|
+
if (marker) {
|
|
4445
|
+
c->in_flow = 0; /* markers are unconditional boundaries */
|
|
4446
|
+
}
|
|
4325
4447
|
} else {
|
|
4326
4448
|
size_t e = k;
|
|
4327
4449
|
while (e < len && p[e] != '\n') {
|
|
@@ -4372,10 +4494,13 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
|
|
|
4372
4494
|
}
|
|
4373
4495
|
/* the doc-inline twin ("--- |"): the node hangs off the document,
|
|
4374
4496
|
* so only a later marker ends its fold (col-0 lines are content) */
|
|
4375
|
-
if (
|
|
4376
|
-
p[i + 1] == '-' && p[i + 2] == '-' &&
|
|
4497
|
+
if (!c->bs_open && ch == '-' && i + 2 < len && p[i + 1] == '-' && p[i + 2] == '-' &&
|
|
4377
4498
|
(i + 3 >= len || p[i + 3] == ' ' || p[i + 3] == '\t' || p[i + 3] == '\n' ||
|
|
4378
|
-
p[i + 3] == '\r')
|
|
4499
|
+
p[i + 3] == '\r') &&
|
|
4500
|
+
(c->at_break || i == 0)) {
|
|
4501
|
+
c->in_flow = 0; /* the marker bounds the document even when the
|
|
4502
|
+
* walk still believes a flow is open (the same
|
|
4503
|
+
* round-35 law as the in-quote rescue) */
|
|
4379
4504
|
size_t k = i + 3;
|
|
4380
4505
|
while (k < len && (p[k] == ' ' || p[k] == '\t')) {
|
|
4381
4506
|
k++;
|
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.
|
|
4
|
+
version: 0.6.42.0
|
|
5
5
|
platform: arm-linux-eabihf
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -38,6 +38,7 @@ files:
|
|
|
38
38
|
- ext/yeptris_native/cbor_ruby.c
|
|
39
39
|
- ext/yeptris_native/extconf.rb
|
|
40
40
|
- ext/yeptris_native/json_ruby.c
|
|
41
|
+
- ext/yeptris_native/version.h
|
|
41
42
|
- ext/yeptris_native/yeptris_native.c
|
|
42
43
|
- lib/yeptris.rb
|
|
43
44
|
- lib/yeptris/3.4/native.so
|