yeptris 0.6.38.0-aarch64-linux → 0.6.41.0-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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.3/native.so +0 -0
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris/4.0/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 +70 -0
- data/vendor/libyeptris/src/yeptris/emit/writer.c +69 -45
- data/vendor/libyeptris/src/yeptris/emit/writer.h +3 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +163 -32
- 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: 5353bd36130f48f70c8825b21e60564dafc3078e1702e186e00389be89ad2bb0
|
|
4
|
+
data.tar.gz: f1ca7bf67a3e99b23339e5d60a4a5331c60aa859d9ad49208cfe74a5236bc61a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 72ee0c7302cfe878192a677b793836ed879039e783624f323ab54c5a5a11e2e37d82cedd2f3635f3da6830e71c9967cbc3a2737055f2677cbc08fa82a9dad453
|
|
7
|
+
data.tar.gz: 94add9c22c40c61de42fa34e8d2bb359475b20f8c270cc3a57c120594a237e28857bfe44202709f7b2f3fee7341fd887b511c6f09d002fb963bc1971675b35e8
|
|
@@ -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 41
|
|
15
|
+
#define YEPTRIS_VERSION_STRING "0.6.41"
|
|
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.3/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/3.4/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/4.0/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.41.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;
|
|
@@ -260,6 +326,7 @@ YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len,
|
|
|
260
326
|
em.w.cap = 0;
|
|
261
327
|
em.w.grow = 0;
|
|
262
328
|
em.w.oom = 0;
|
|
329
|
+
em.w.bounded = 0;
|
|
263
330
|
em.doc = (const yeptris_document*)handle;
|
|
264
331
|
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
265
332
|
return NULL;
|
|
@@ -326,6 +393,7 @@ char* yep_serialize_json_compact(const yeptris_document* doc, size_t* len) {
|
|
|
326
393
|
em.w.cap = 0;
|
|
327
394
|
em.w.grow = 0;
|
|
328
395
|
em.w.oom = 0;
|
|
396
|
+
em.w.bounded = 0;
|
|
329
397
|
em.doc = doc;
|
|
330
398
|
em.w.p = NULL;
|
|
331
399
|
em.w.last = 0;
|
|
@@ -389,6 +457,7 @@ char* yep_serialize_json_pretty_ex(const yeptris_document* doc, size_t* len, con
|
|
|
389
457
|
em.w.cap = 0;
|
|
390
458
|
em.w.grow = 0;
|
|
391
459
|
em.w.oom = 0;
|
|
460
|
+
em.w.bounded = 0;
|
|
392
461
|
em.doc = doc;
|
|
393
462
|
em.w.p = NULL;
|
|
394
463
|
em.w.last = 0;
|
|
@@ -459,6 +528,7 @@ YEPTRIS_API size_t yeptris_serialize_stream(YeptrisDocument handle,
|
|
|
459
528
|
em.w.cap = 0;
|
|
460
529
|
em.w.grow = 0;
|
|
461
530
|
em.w.oom = 0;
|
|
531
|
+
em.w.bounded = 0;
|
|
462
532
|
em.doc = (const yeptris_document*)handle;
|
|
463
533
|
if (yep_doc_dom((yeptris_document*)handle) == NULL) { /* #342 lazy */
|
|
464
534
|
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);
|
|
@@ -1069,7 +1089,11 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
|
|
|
1069
1089
|
w->len += total;
|
|
1070
1090
|
} else {
|
|
1071
1091
|
if (w->grow && w->len + total + 1 > w->cap) {
|
|
1072
|
-
|
|
1092
|
+
if (w->bounded) {
|
|
1093
|
+
w->oom = 1; /* keep counting: the trusted caller reads the size */
|
|
1094
|
+
} else {
|
|
1095
|
+
wr_grow(w, total);
|
|
1096
|
+
}
|
|
1073
1097
|
}
|
|
1074
1098
|
if (w->oom) {
|
|
1075
1099
|
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
|
}
|
|
@@ -1289,15 +1309,29 @@ static void e_props(yep_engine* e, yep_view* anchor, yep_view* tag) {
|
|
|
1289
1309
|
* re-lexes as the key indicator and the existing colon
|
|
1290
1310
|
* arm builds the anchored null key. */
|
|
1291
1311
|
if (e->p[e->pos - 1] == ':') {
|
|
1292
|
-
/* the follower scan crosses blanks
|
|
1293
|
-
* pair separator at line end still opens
|
|
1294
|
-
* the
|
|
1295
|
-
* 31, crash-c2232e17 — rode the colon
|
|
1296
|
-
* flow map
|
|
1312
|
+
/* the follower scan crosses blanks, breaks AND
|
|
1313
|
+
* comments: the pair separator at line end still opens
|
|
1314
|
+
* the value on a later line ("&k:\n {\n}" — the
|
|
1315
|
+
* nightly's round 31, crash-c2232e17 — rode the colon
|
|
1316
|
+
* and DROPPED the flow map; "&: #c\n|\nA" — round
|
|
1317
|
+
* 32's crash-d580b467 — stopped at the comment, rode
|
|
1318
|
+
* the colon into the anchor name and pass1/pass2
|
|
1319
|
+
* spelled different trees). A comment is
|
|
1320
|
+
* blank-equivalent here: skip it to its break and keep
|
|
1321
|
+
* scanning. */
|
|
1297
1322
|
size_t t = e->pos;
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1323
|
+
for (;;) {
|
|
1324
|
+
while (t < e->len && (e->p[t] == ' ' || e->p[t] == '\t' || e->p[t] == '\n' ||
|
|
1325
|
+
e->p[t] == '\r')) {
|
|
1326
|
+
t++;
|
|
1327
|
+
}
|
|
1328
|
+
if (t < e->len && e->p[t] == '#') {
|
|
1329
|
+
while (t < e->len && e->p[t] != '\n' && e->p[t] != '\r') {
|
|
1330
|
+
t++;
|
|
1331
|
+
}
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
break;
|
|
1301
1335
|
}
|
|
1302
1336
|
if (t < e->len &&
|
|
1303
1337
|
(e->p[t] == '[' || e->p[t] == '{' || e->p[t] == '|' || e->p[t] == '>')) {
|
|
@@ -2172,8 +2206,11 @@ static void e_flow_frame_init(void* stv, int idx, int kind, int pending_key) {
|
|
|
2172
2206
|
uint8_t pair_q; /* the pair opened on an explicit '?': its
|
|
2173
2207
|
* JSON-style ':' separator is still ahead */
|
|
2174
2208
|
uint8_t pair_wrap;
|
|
2175
|
-
uint8_t q_coll;
|
|
2176
|
-
uint8_t
|
|
2209
|
+
uint8_t q_coll; /* seq: an explicit '?' key collection awaits completion */
|
|
2210
|
+
uint8_t q_empty_value; /* seq: the '?' empty-key pair — 1: opened,
|
|
2211
|
+
* 2: its value state (the entry comma was
|
|
2212
|
+
* consumed inside the pair, yeptris#741) */
|
|
2213
|
+
uint8_t key_coll; /* map: the nested collection opened in key position */
|
|
2177
2214
|
int32_t buf_from;
|
|
2178
2215
|
uint32_t entry_line;
|
|
2179
2216
|
uint32_t entries;
|
|
@@ -2189,6 +2226,7 @@ static void e_flow_frame_init(void* stv, int idx, int kind, int pending_key) {
|
|
|
2189
2226
|
st[idx].pair_q = 0;
|
|
2190
2227
|
st[idx].pair_wrap = 0;
|
|
2191
2228
|
st[idx].q_coll = 0;
|
|
2229
|
+
st[idx].q_empty_value = 0;
|
|
2192
2230
|
st[idx].buf_from = -1;
|
|
2193
2231
|
st[idx].entry_line = 0;
|
|
2194
2232
|
st[idx].entries = 0;
|
|
@@ -2196,20 +2234,21 @@ static void e_flow_frame_init(void* stv, int idx, int kind, int pending_key) {
|
|
|
2196
2234
|
|
|
2197
2235
|
static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_id) {
|
|
2198
2236
|
struct {
|
|
2199
|
-
uint8_t kind;
|
|
2200
|
-
uint8_t pending_key;
|
|
2201
|
-
uint8_t sep;
|
|
2202
|
-
uint8_t just_closed;
|
|
2203
|
-
uint8_t pair_open;
|
|
2204
|
-
uint8_t pair_value;
|
|
2205
|
-
uint8_t pair_q;
|
|
2206
|
-
|
|
2207
|
-
uint8_t pair_wrap;
|
|
2208
|
-
uint8_t q_coll;
|
|
2209
|
-
uint8_t
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
uint32_t
|
|
2237
|
+
uint8_t kind; /* 0 seq, 1 map */
|
|
2238
|
+
uint8_t pending_key; /* map: 1 key seen awaiting ':' value; 2 key emitted w/o ':' */
|
|
2239
|
+
uint8_t sep; /* a ',' was consumed and no entry followed yet */
|
|
2240
|
+
uint8_t just_closed; /* a nested collection closed here (key candidate) */
|
|
2241
|
+
uint8_t pair_open; /* seq: a buffered single-pair awaits its MAP_END */
|
|
2242
|
+
uint8_t pair_value; /* seq: the open pair already has its value */
|
|
2243
|
+
uint8_t pair_q; /* seq: the pair opened on an explicit '?':
|
|
2244
|
+
* its JSON-style ':' separator is still ahead */
|
|
2245
|
+
uint8_t pair_wrap; /* map frame pushed as a seq single-pair */
|
|
2246
|
+
uint8_t q_coll; /* seq: an explicit '?' key collection awaits completion */
|
|
2247
|
+
uint8_t q_empty_value; /* seq: the '?' empty-key pair's state (#741) */
|
|
2248
|
+
uint8_t key_coll; /* map: a nested collection opened in key position */
|
|
2249
|
+
int32_t buf_from; /* seq: entry events buffered from this index */
|
|
2250
|
+
uint32_t entry_line; /* seq: line where the buffered entry ended */
|
|
2251
|
+
uint32_t entries; /* completed entries (for trailing-comma detection) */
|
|
2213
2252
|
} st[YEP_MAX_DEPTH];
|
|
2214
2253
|
int n = 0;
|
|
2215
2254
|
|
|
@@ -2249,6 +2288,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2249
2288
|
}
|
|
2250
2289
|
int keyish = (st[n - 1].kind == 1 && st[n - 1].pending_key == 0);
|
|
2251
2290
|
int q_key = 0;
|
|
2291
|
+
int q_empty_key = 0; /* the '?' synthesized a null key (#741) */
|
|
2252
2292
|
int rc;
|
|
2253
2293
|
if (e_flow_ws(e) != 0) {
|
|
2254
2294
|
return -1;
|
|
@@ -2278,6 +2318,10 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2278
2318
|
!(e->p[n2] == ' ' || e->p[n2] == '\t' || e->p[n2] == '\n' || e->p[n2] == '\r' ||
|
|
2279
2319
|
yep_ct_is((unsigned char)e->p[n2], YEP_CT_FLOW_IND))) {
|
|
2280
2320
|
st[n - 1].pair_q = 0;
|
|
2321
|
+
/* yeptris#741: the consumed ':' is the pair's VALUE
|
|
2322
|
+
* indicator — the '?' empty-key pair's value state ends
|
|
2323
|
+
* here or the value node trips the entry guard */
|
|
2324
|
+
st[n - 1].q_empty_value = 0;
|
|
2281
2325
|
e->pos++;
|
|
2282
2326
|
continue;
|
|
2283
2327
|
}
|
|
@@ -2305,6 +2349,15 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2305
2349
|
* in a sequence a ':'-led token is a new plain, so it errors */
|
|
2306
2350
|
return e_fail(e, YEP_ERR_UNEXPECTED, e->pos);
|
|
2307
2351
|
}
|
|
2352
|
+
if (st[n - 1].kind == 0 && st[n - 1].q_empty_value == 2 && e->pos < e->len &&
|
|
2353
|
+
e->p[e->pos] != ',' && e->p[e->pos] != ']' && e->p[e->pos] != '}' &&
|
|
2354
|
+
e->p[e->pos] != ':') {
|
|
2355
|
+
/* yeptris#741: a node in the '?' empty-key pair's value
|
|
2356
|
+
* state — ENTRY_MAPPING_VALUE wants VALUE/ENTRY/END only
|
|
2357
|
+
* ("[?, a]" is the libyaml "did not find expected ',' or
|
|
2358
|
+
* ']'" error) */
|
|
2359
|
+
return e_fail(e, YEP_ERR_UNEXPECTED, e->pos);
|
|
2360
|
+
}
|
|
2308
2361
|
if (e->pos < e->len && e->p[e->pos] == '?' &&
|
|
2309
2362
|
(e->pos + 1 >= e->len || e->p[e->pos + 1] == ' ' || e->p[e->pos + 1] == '\t' ||
|
|
2310
2363
|
e->p[e->pos + 1] == '\n' || e->p[e->pos + 1] == '\r' ||
|
|
@@ -2328,6 +2381,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2328
2381
|
ev.col = e_col(e, e->pos) + 1;
|
|
2329
2382
|
rc = 1;
|
|
2330
2383
|
q_key = 1;
|
|
2384
|
+
q_empty_key = (st[n - 1].kind == 0);
|
|
2331
2385
|
} else {
|
|
2332
2386
|
rc = e_flow_node(e, &ev, keyish);
|
|
2333
2387
|
q_key = 1;
|
|
@@ -2354,10 +2408,15 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2354
2408
|
st[n - 1].q_coll = 1;
|
|
2355
2409
|
}
|
|
2356
2410
|
}
|
|
2357
|
-
if (!q_key) {
|
|
2411
|
+
if (!q_key && st[n - 1].pair_open) {
|
|
2358
2412
|
/* a VALUE collection completes the pending pair; a KEY
|
|
2359
2413
|
* collection must not (its pair completes at ':' or the
|
|
2360
|
-
* boundary with a null value)
|
|
2414
|
+
* boundary with a null value). The gate is pair_open:
|
|
2415
|
+
* in a SEQUENCE a plain collection entry has no pair,
|
|
2416
|
+
* and the unguarded write planted a stale pair_value=1
|
|
2417
|
+
* that a LATER '?'-opened pair wrap inherited -- its
|
|
2418
|
+
* comma flush then skipped the implicit null
|
|
2419
|
+
* (crash-8106600e, "[... , ? 20:50 , ...]"). */
|
|
2361
2420
|
st[n - 1].pair_value = 1;
|
|
2362
2421
|
}
|
|
2363
2422
|
unsigned char oc = (unsigned char)e->p[e->pos];
|
|
@@ -2536,6 +2595,9 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2536
2595
|
if (emit_now(e, &ev) != 0) {
|
|
2537
2596
|
return -2;
|
|
2538
2597
|
}
|
|
2598
|
+
if (q_empty_key) {
|
|
2599
|
+
st[n - 1].q_empty_value = 1;
|
|
2600
|
+
}
|
|
2539
2601
|
continue;
|
|
2540
2602
|
}
|
|
2541
2603
|
}
|
|
@@ -2570,6 +2632,18 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2570
2632
|
unsigned char c = (unsigned char)e->p[e->pos];
|
|
2571
2633
|
if (c == ',') {
|
|
2572
2634
|
e->pos++;
|
|
2635
|
+
if (st[n - 1].kind == 0 && st[n - 1].pair_open && st[n - 1].pair_q &&
|
|
2636
|
+
!st[n - 1].pair_value && st[n - 1].q_empty_value == 1) {
|
|
2637
|
+
/* yeptris#741: the '?' empty-key pair's first comma is
|
|
2638
|
+
* the entry separator libyaml's ENTRY_MAPPING_KEY
|
|
2639
|
+
* consumed to reach its VALUE state — the pair stays
|
|
2640
|
+
* open and awaits ':' (value), ',' (empty value +
|
|
2641
|
+
* close), or ']' (empty value + close). Closing here
|
|
2642
|
+
* spilled the following ': x' into a second entry
|
|
2643
|
+
* ("[? ,: x , a]" read as two maps). */
|
|
2644
|
+
st[n - 1].q_empty_value = 2;
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2573
2647
|
if (st[n - 1].q_coll && st[n - 1].buf_from >= 0) {
|
|
2574
2648
|
int32_t bf0 = st[n - 1].buf_from;
|
|
2575
2649
|
st[n - 1].buf_from = -1;
|
|
@@ -2604,6 +2678,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2604
2678
|
st[n - 1].pair_open = 0;
|
|
2605
2679
|
st[n - 1].pair_value = 0;
|
|
2606
2680
|
st[n - 1].pair_q = 0;
|
|
2681
|
+
st[n - 1].q_empty_value = 0;
|
|
2607
2682
|
}
|
|
2608
2683
|
if (st[n - 1].buf_from >= 0) {
|
|
2609
2684
|
st[n - 1].buf_from = -1;
|
|
@@ -2737,6 +2812,7 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2737
2812
|
}
|
|
2738
2813
|
st[n - 1].pair_open = 0;
|
|
2739
2814
|
st[n - 1].pair_value = 0;
|
|
2815
|
+
st[n - 1].q_empty_value = 0;
|
|
2740
2816
|
}
|
|
2741
2817
|
if (st[n - 1].buf_from >= 0) {
|
|
2742
2818
|
st[n - 1].buf_from = -1;
|
|
@@ -2835,7 +2911,8 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2835
2911
|
}
|
|
2836
2912
|
}
|
|
2837
2913
|
if (was_empty) {
|
|
2838
|
-
yep_event kv; /* "[ : v" — the empty key
|
|
2914
|
+
yep_event kv; /* "[ : v" — the empty key (CFD4,
|
|
2915
|
+
* ledgered suite-VALID) */
|
|
2839
2916
|
e_event_init(&kv, YEP_EV_SCALAR);
|
|
2840
2917
|
kv.implicit = 1;
|
|
2841
2918
|
if (emit_now(e, &kv) != 0) {
|
|
@@ -2852,13 +2929,52 @@ static int e_flow(yep_engine* e, yep_view anchor, yep_view tag, uint32_t anchor_
|
|
|
2852
2929
|
continue; /* value next */
|
|
2853
2930
|
}
|
|
2854
2931
|
if (st[n - 1].kind == 0 && n >= 1) {
|
|
2932
|
+
if (st[n - 1].pair_open && st[n - 1].q_empty_value == 2) {
|
|
2933
|
+
/* yeptris#741: the ':' after the '?' empty-key
|
|
2934
|
+
* pair's comma completes THE SAME pair (the pair's
|
|
2935
|
+
* VALUE indicator), not a new entry */
|
|
2936
|
+
size_t p2 = e->pos; /* the ':' byte is already consumed */
|
|
2937
|
+
while (p2 < e->len && (e->p[p2] == ' ' || e->p[p2] == '\t')) {
|
|
2938
|
+
p2++;
|
|
2939
|
+
}
|
|
2940
|
+
if (p2 < e->len && (e->p[p2] == ',' || e->p[p2] == ']' || e->p[p2] == '}')) {
|
|
2941
|
+
/* empty value at the closer: the pair closes
|
|
2942
|
+
* here; the closer dispatches as the separator */
|
|
2943
|
+
yep_event nv;
|
|
2944
|
+
e_event_init(&nv, YEP_EV_SCALAR);
|
|
2945
|
+
nv.implicit = 1;
|
|
2946
|
+
nv.style = YEP_STYLE_PLAIN;
|
|
2947
|
+
if (emit_now(e, &nv) != 0) {
|
|
2948
|
+
return -2;
|
|
2949
|
+
}
|
|
2950
|
+
yep_event mk;
|
|
2951
|
+
e_event_init(&mk, YEP_EV_MAP_END);
|
|
2952
|
+
if (emit_now(e, &mk) != 0) {
|
|
2953
|
+
return -2;
|
|
2954
|
+
}
|
|
2955
|
+
st[n - 1].pair_open = 0;
|
|
2956
|
+
st[n - 1].pair_value = 0;
|
|
2957
|
+
st[n - 1].pair_q = 0;
|
|
2958
|
+
st[n - 1].q_empty_value = 0;
|
|
2959
|
+
st[n - 1].sep = 1;
|
|
2960
|
+
st[n - 1].just_closed = 0;
|
|
2961
|
+
e->pos = p2;
|
|
2962
|
+
continue;
|
|
2963
|
+
}
|
|
2964
|
+
st[n - 1].q_empty_value = 0;
|
|
2965
|
+
e->pos++;
|
|
2966
|
+
continue;
|
|
2967
|
+
}
|
|
2855
2968
|
if (st[n - 1].pair_open) {
|
|
2856
2969
|
/* "[s: : v]" — ':' in the open pair's value slot
|
|
2857
2970
|
* is not node content (libyaml: "did not find
|
|
2858
2971
|
* expected node content") */
|
|
2859
2972
|
return e_fail(e, YEP_ERR_UNEXPECTED, e->pos);
|
|
2860
2973
|
}
|
|
2861
|
-
/* '[ : v' — single-pair mapping with an empty key
|
|
2974
|
+
/* '[ : v' — single-pair mapping with an empty key
|
|
2975
|
+
* (CFD4, ledgered suite-VALID: the suite's tree parses
|
|
2976
|
+
* the bare-colon entry; libyaml's rejection is the
|
|
2977
|
+
* ledgered deviation) */
|
|
2862
2978
|
if (n >= e->max_depth) {
|
|
2863
2979
|
return e_fail(e, YEP_ERR_DEPTH, e->pos);
|
|
2864
2980
|
}
|
|
@@ -4174,6 +4290,15 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
|
|
|
4174
4290
|
p[i + 3] == '\r'));
|
|
4175
4291
|
if (rescue) {
|
|
4176
4292
|
c->quote = 0;
|
|
4293
|
+
/* the marker is an unconditional document boundary (the
|
|
4294
|
+
* parser's own law) and the parser's post-cut reset wipes
|
|
4295
|
+
* the flow state; the walk must too, or a flow opened in
|
|
4296
|
+
* an EARLIER document re-lexes the next document's spaces
|
|
4297
|
+
* as token starts ("1,\"\n--- 2[\"\n--- T \"<LS>" --
|
|
4298
|
+
* whole kept the LS raw inside the phantom quote while
|
|
4299
|
+
* every chunked window folded it; the nightly's round 35,
|
|
4300
|
+
* crash-ef534bdf) */
|
|
4301
|
+
c->in_flow = 0;
|
|
4177
4302
|
} else {
|
|
4178
4303
|
/* NEL folds inside quotes too; LS/PS stay raw content — but
|
|
4179
4304
|
* every break still arms the line-start state, or a col-0
|
|
@@ -4308,6 +4433,9 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
|
|
|
4308
4433
|
if (marker || (p[k] == '%' && ind == 0 && c->bs_content) ||
|
|
4309
4434
|
(!marker && c->bs_indent != (uint32_t)-1 && ind <= (uint32_t)c->bs_indent)) {
|
|
4310
4435
|
c->bs_open = 0; /* the fold ends: this line processes normally */
|
|
4436
|
+
if (marker) {
|
|
4437
|
+
c->in_flow = 0; /* markers are unconditional boundaries */
|
|
4438
|
+
}
|
|
4311
4439
|
} else {
|
|
4312
4440
|
size_t e = k;
|
|
4313
4441
|
while (e < len && p[e] != '\n') {
|
|
@@ -4358,10 +4486,13 @@ static yep_norm_tok norm_step(yep_norm_ctx* c, const char* p, size_t len, size_t
|
|
|
4358
4486
|
}
|
|
4359
4487
|
/* the doc-inline twin ("--- |"): the node hangs off the document,
|
|
4360
4488
|
* so only a later marker ends its fold (col-0 lines are content) */
|
|
4361
|
-
if (
|
|
4362
|
-
p[i + 1] == '-' && p[i + 2] == '-' &&
|
|
4489
|
+
if (!c->bs_open && ch == '-' && i + 2 < len && p[i + 1] == '-' && p[i + 2] == '-' &&
|
|
4363
4490
|
(i + 3 >= len || p[i + 3] == ' ' || p[i + 3] == '\t' || p[i + 3] == '\n' ||
|
|
4364
|
-
p[i + 3] == '\r')
|
|
4491
|
+
p[i + 3] == '\r') &&
|
|
4492
|
+
(c->at_break || i == 0)) {
|
|
4493
|
+
c->in_flow = 0; /* the marker bounds the document even when the
|
|
4494
|
+
* walk still believes a flow is open (the same
|
|
4495
|
+
* round-35 law as the in-quote rescue) */
|
|
4365
4496
|
size_t k = i + 3;
|
|
4366
4497
|
while (k < len && (p[k] == ' ' || p[k] == '\t')) {
|
|
4367
4498
|
k++;
|
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.41.0
|
|
5
5
|
platform: aarch64-linux
|
|
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-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- ext/yeptris_native/cbor_ruby.c
|
|
40
40
|
- ext/yeptris_native/extconf.rb
|
|
41
41
|
- ext/yeptris_native/json_ruby.c
|
|
42
|
+
- ext/yeptris_native/version.h
|
|
42
43
|
- ext/yeptris_native/yeptris_native.c
|
|
43
44
|
- lib/yeptris.rb
|
|
44
45
|
- lib/yeptris/3.3/native.so
|