nosj 0.4.0 → 0.5.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/CHANGELOG.md +105 -0
- data/Cargo.lock +17 -17
- data/README.md +32 -15
- data/ext/nosj/Cargo.toml +6 -6
- data/ext/nosj/fuzz/Cargo.toml +1 -1
- data/ext/nosj/fuzz/src/prelude.rb +20 -44
- data/ext/nosj/src/files.rs +21 -20
- data/ext/nosj/src/gen/errors.rs +12 -10
- data/ext/nosj/src/gen/mod.rs +8 -10
- data/ext/nosj/src/gen/opts.rs +76 -141
- data/ext/nosj/src/gen/ruby.rs +50 -22
- data/ext/nosj/src/gen/walker.rs +219 -102
- data/ext/nosj/src/lazy.rs +60 -50
- data/ext/nosj/src/lib.rs +4 -0
- data/ext/nosj/src/lines.rs +27 -17
- data/ext/nosj/src/locate.rs +162 -0
- data/ext/nosj/src/opt_reader.rs +170 -0
- data/ext/nosj/src/parse.rs +170 -99
- data/ext/nosj/src/patch.rs +25 -15
- data/ext/nosj/src/pointer.rs +9 -11
- data/ext/nosj/src/reformat.rs +83 -140
- data/ext/nosj/src/sink.rs +193 -60
- data/ext/nosj/src/state.rs +65 -14
- data/ext/nosj/src/stats.rs +35 -33
- data/lib/nosj/json.rb +105 -39
- data/lib/nosj/version.rb +1 -1
- data/lib/nosj.rb +36 -22
- data/sig/nosj.rbs +4 -3
- metadata +4 -2
data/ext/nosj/src/gen/opts.rs
CHANGED
|
@@ -5,6 +5,9 @@ use magnus::value::ReprValue;
|
|
|
5
5
|
use magnus::{Error, RHash, RString, Ruby, Value};
|
|
6
6
|
use nosj::emit::EscapeMode;
|
|
7
7
|
|
|
8
|
+
use crate::opt_reader::{Opt, OptReader};
|
|
9
|
+
use crate::sink::MAX_NESTING;
|
|
10
|
+
|
|
8
11
|
pub(crate) struct GenConfig {
|
|
9
12
|
pub(crate) indent: Vec<u8>,
|
|
10
13
|
pub(crate) space: Vec<u8>,
|
|
@@ -21,195 +24,129 @@ pub(crate) struct GenConfig {
|
|
|
21
24
|
/// null (Float#as_json parity). Set only by the Rails entry, never
|
|
22
25
|
/// from user option hashes.
|
|
23
26
|
pub(super) rails: bool,
|
|
27
|
+
/// json 3: keys that render the same (`"a"` and `:a`) raise unless
|
|
28
|
+
/// this is set. The Rails configs keep ActiveSupport's own handling.
|
|
29
|
+
pub(super) allow_duplicate_key: bool,
|
|
24
30
|
pub(crate) mode: EscapeMode,
|
|
25
31
|
/// Precomputed "any formatting string set": scanning the five
|
|
26
32
|
/// vectors per call was measurable on tiny documents.
|
|
27
33
|
pub(super) pretty: bool,
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
/// The json gem's defaults under an escape mode, for the plain walk or
|
|
37
|
+
/// the Rails encoder's (which keeps ActiveSupport's own key handling,
|
|
38
|
+
/// so it allows keys that render alike). A const fn because statics
|
|
39
|
+
/// cannot struct-update a type with `Vec` fields; `Vec::new` is const
|
|
40
|
+
/// and allocation-free.
|
|
41
|
+
const fn defaults(rails: bool, mode: EscapeMode) -> GenConfig {
|
|
42
|
+
GenConfig {
|
|
43
|
+
indent: Vec::new(),
|
|
44
|
+
space: Vec::new(),
|
|
45
|
+
space_before: Vec::new(),
|
|
46
|
+
object_nl: Vec::new(),
|
|
47
|
+
array_nl: Vec::new(),
|
|
48
|
+
max_nesting: MAX_NESTING,
|
|
49
|
+
start_depth: 0,
|
|
50
|
+
allow_nan: false,
|
|
51
|
+
strict: false,
|
|
52
|
+
rails,
|
|
53
|
+
allow_duplicate_key: rails,
|
|
54
|
+
mode,
|
|
55
|
+
pretty: false,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
30
59
|
/// The nil-options configuration, shared instead of rebuilt: stamping
|
|
31
60
|
/// a fresh ~140-byte GenConfig onto the stack per call was measurable
|
|
32
61
|
/// on tiny documents (the json gem likewise reuses a cached State for
|
|
33
|
-
/// the default options). Safe as a static:
|
|
34
|
-
///
|
|
35
|
-
pub(crate) static DEFAULT_CONFIG: GenConfig =
|
|
36
|
-
indent: Vec::new(),
|
|
37
|
-
space: Vec::new(),
|
|
38
|
-
space_before: Vec::new(),
|
|
39
|
-
object_nl: Vec::new(),
|
|
40
|
-
array_nl: Vec::new(),
|
|
41
|
-
max_nesting: 100,
|
|
42
|
-
start_depth: 0,
|
|
43
|
-
allow_nan: false,
|
|
44
|
-
strict: false,
|
|
45
|
-
rails: false,
|
|
46
|
-
mode: EscapeMode::Standard,
|
|
47
|
-
pretty: false,
|
|
48
|
-
};
|
|
62
|
+
/// the default options). Safe as a static: generation only ever
|
|
63
|
+
/// borrows the config.
|
|
64
|
+
pub(crate) static DEFAULT_CONFIG: GenConfig = defaults(false, EscapeMode::Standard);
|
|
49
65
|
|
|
50
66
|
/// The Rails-encoder configuration for ActiveSupport's default escape
|
|
51
67
|
/// flags (HTML entities and JS separators both on, the overwhelmingly
|
|
52
68
|
/// common case): escaping is fused into the crate's HtmlSafe kernels,
|
|
53
69
|
/// one pass, no post-scan.
|
|
54
|
-
pub(super) static RAILS_HTML_SAFE_CONFIG: GenConfig =
|
|
55
|
-
indent: Vec::new(),
|
|
56
|
-
space: Vec::new(),
|
|
57
|
-
space_before: Vec::new(),
|
|
58
|
-
object_nl: Vec::new(),
|
|
59
|
-
array_nl: Vec::new(),
|
|
60
|
-
max_nesting: 100,
|
|
61
|
-
start_depth: 0,
|
|
62
|
-
allow_nan: false,
|
|
63
|
-
strict: false,
|
|
64
|
-
rails: true,
|
|
65
|
-
mode: EscapeMode::HtmlSafe,
|
|
66
|
-
pretty: false,
|
|
67
|
-
};
|
|
70
|
+
pub(super) static RAILS_HTML_SAFE_CONFIG: GenConfig = defaults(true, EscapeMode::HtmlSafe);
|
|
68
71
|
|
|
69
72
|
/// Rails-encoder configuration with HTML entities on and JS separators
|
|
70
73
|
/// off.
|
|
71
|
-
pub(super) static RAILS_HTML_ENTITIES_CONFIG: GenConfig =
|
|
72
|
-
indent: Vec::new(),
|
|
73
|
-
space: Vec::new(),
|
|
74
|
-
space_before: Vec::new(),
|
|
75
|
-
object_nl: Vec::new(),
|
|
76
|
-
array_nl: Vec::new(),
|
|
77
|
-
max_nesting: 100,
|
|
78
|
-
start_depth: 0,
|
|
79
|
-
allow_nan: false,
|
|
80
|
-
strict: false,
|
|
81
|
-
rails: true,
|
|
82
|
-
mode: EscapeMode::HtmlEntities,
|
|
83
|
-
pretty: false,
|
|
84
|
-
};
|
|
74
|
+
pub(super) static RAILS_HTML_ENTITIES_CONFIG: GenConfig = defaults(true, EscapeMode::HtmlEntities);
|
|
85
75
|
|
|
86
76
|
/// Rails-encoder configuration with JS separators on and HTML entities
|
|
87
77
|
/// off.
|
|
88
|
-
pub(super) static RAILS_JS_SEPARATORS_CONFIG: GenConfig =
|
|
89
|
-
indent: Vec::new(),
|
|
90
|
-
space: Vec::new(),
|
|
91
|
-
space_before: Vec::new(),
|
|
92
|
-
object_nl: Vec::new(),
|
|
93
|
-
array_nl: Vec::new(),
|
|
94
|
-
max_nesting: 100,
|
|
95
|
-
start_depth: 0,
|
|
96
|
-
allow_nan: false,
|
|
97
|
-
strict: false,
|
|
98
|
-
rails: true,
|
|
99
|
-
mode: EscapeMode::JsSeparators,
|
|
100
|
-
pretty: false,
|
|
101
|
-
};
|
|
78
|
+
pub(super) static RAILS_JS_SEPARATORS_CONFIG: GenConfig = defaults(true, EscapeMode::JsSeparators);
|
|
102
79
|
|
|
103
80
|
/// The Rails-encoder configuration with every escape flag off
|
|
104
81
|
/// (encode(escape: false)). Mirrors JSONGemEncoder#stringify, which
|
|
105
82
|
/// generates with the json gem's defaults.
|
|
106
|
-
pub(super) static RAILS_CONFIG: GenConfig =
|
|
107
|
-
indent: Vec::new(),
|
|
108
|
-
space: Vec::new(),
|
|
109
|
-
space_before: Vec::new(),
|
|
110
|
-
object_nl: Vec::new(),
|
|
111
|
-
array_nl: Vec::new(),
|
|
112
|
-
max_nesting: 100,
|
|
113
|
-
start_depth: 0,
|
|
114
|
-
allow_nan: false,
|
|
115
|
-
strict: false,
|
|
116
|
-
rails: true,
|
|
117
|
-
mode: EscapeMode::Standard,
|
|
118
|
-
pretty: false,
|
|
119
|
-
};
|
|
83
|
+
pub(super) static RAILS_CONFIG: GenConfig = defaults(true, EscapeMode::Standard);
|
|
120
84
|
|
|
121
85
|
impl Default for GenConfig {
|
|
122
86
|
fn default() -> Self {
|
|
123
|
-
|
|
124
|
-
indent: Vec::new(),
|
|
125
|
-
space: Vec::new(),
|
|
126
|
-
space_before: Vec::new(),
|
|
127
|
-
object_nl: Vec::new(),
|
|
128
|
-
array_nl: Vec::new(),
|
|
129
|
-
max_nesting: 100,
|
|
130
|
-
start_depth: 0,
|
|
131
|
-
allow_nan: false,
|
|
132
|
-
strict: false,
|
|
133
|
-
rails: false,
|
|
134
|
-
mode: EscapeMode::Standard,
|
|
135
|
-
pretty: false,
|
|
136
|
-
}
|
|
87
|
+
defaults(false, EscapeMode::Standard)
|
|
137
88
|
}
|
|
138
89
|
}
|
|
139
90
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return Ok(None);
|
|
146
|
-
}
|
|
91
|
+
/// A formatting string option's bytes; empty when absent or nil.
|
|
92
|
+
fn opt_bytes(r: &mut OptReader, opt: Opt) -> Result<Vec<u8>, Error> {
|
|
93
|
+
let Some(v) = r.get(opt).filter(|v| !v.is_nil()) else {
|
|
94
|
+
return Ok(Vec::new());
|
|
95
|
+
};
|
|
147
96
|
let s = RString::from_value(v).ok_or_else(|| {
|
|
148
97
|
Error::new(
|
|
149
|
-
ruby.exception_type_error(),
|
|
150
|
-
format!("{
|
|
98
|
+
r.ruby().exception_type_error(),
|
|
99
|
+
format!("{} must be a String", opt.name()),
|
|
151
100
|
)
|
|
152
101
|
})?;
|
|
153
|
-
Ok(
|
|
102
|
+
Ok(unsafe { s.as_slice() }.to_vec())
|
|
154
103
|
}
|
|
155
104
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
None
|
|
160
|
-
} else {
|
|
161
|
-
Some(v.to_bool())
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/// Decode a non-nil options hash (nil takes [`DEFAULT_CONFIG`] at the
|
|
166
|
-
/// call site without constructing anything).
|
|
105
|
+
/// Decode a generate options hash (nil takes [`DEFAULT_CONFIG`] at the
|
|
106
|
+
/// call site without constructing anything); keys it does not read
|
|
107
|
+
/// raise ArgumentError, like json 3.
|
|
167
108
|
pub(crate) fn parse_gen_opts(ruby: &Ruby, opts: Value) -> Result<(GenConfig, usize), Error> {
|
|
168
|
-
let mut cfg = GenConfig::default();
|
|
169
|
-
let mut cap_hint = 0usize;
|
|
170
109
|
if opts.is_nil() {
|
|
171
|
-
return Ok((
|
|
110
|
+
return Ok((GenConfig::default(), 0));
|
|
172
111
|
}
|
|
173
112
|
let opts = RHash::from_value(opts)
|
|
174
113
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "options must be a Hash or nil"))?;
|
|
114
|
+
let mut reader = OptReader::new(ruby, opts);
|
|
115
|
+
let decoded = read_gen_opts(&mut reader)?;
|
|
116
|
+
reader.finish()?;
|
|
117
|
+
Ok(decoded)
|
|
118
|
+
}
|
|
175
119
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if let Some(v) = opt_bool(ruby, opts, "strict") {
|
|
195
|
-
cfg.strict = v;
|
|
196
|
-
}
|
|
197
|
-
let ascii = opt_bool(ruby, opts, "ascii_only").unwrap_or(false);
|
|
198
|
-
let script = opt_bool(ruby, opts, "script_safe").unwrap_or(false)
|
|
199
|
-
|| opt_bool(ruby, opts, "escape_slash").unwrap_or(false);
|
|
120
|
+
/// Read the json 3 generate options and the buffer size hint. sort_keys
|
|
121
|
+
/// and as_json, which NOSJ does not implement, raise unless falsy.
|
|
122
|
+
pub(crate) fn read_gen_opts(r: &mut OptReader) -> Result<(GenConfig, usize), Error> {
|
|
123
|
+
let mut cfg = GenConfig {
|
|
124
|
+
indent: opt_bytes(r, Opt::Indent)?,
|
|
125
|
+
space: opt_bytes(r, Opt::Space)?,
|
|
126
|
+
space_before: opt_bytes(r, Opt::SpaceBefore)?,
|
|
127
|
+
object_nl: opt_bytes(r, Opt::ObjectNl)?,
|
|
128
|
+
array_nl: opt_bytes(r, Opt::ArrayNl)?,
|
|
129
|
+
allow_nan: r.truthy(Opt::AllowNan),
|
|
130
|
+
strict: r.truthy(Opt::Strict),
|
|
131
|
+
allow_duplicate_key: r.truthy(Opt::AllowDuplicateKey),
|
|
132
|
+
..GenConfig::default()
|
|
133
|
+
};
|
|
134
|
+
let mut cap_hint = 0usize;
|
|
135
|
+
r.tolerate(&[Opt::SortKeys, Opt::AsJson]);
|
|
136
|
+
let ascii = r.truthy(Opt::AsciiOnly);
|
|
137
|
+
let script = r.truthy(Opt::ScriptSafe);
|
|
200
138
|
if ascii {
|
|
201
139
|
cfg.mode = EscapeMode::AsciiOnly;
|
|
202
140
|
if script {
|
|
203
141
|
return Err(Error::new(
|
|
204
|
-
ruby.exception_arg_error(),
|
|
142
|
+
r.ruby().exception_arg_error(),
|
|
205
143
|
"NOSJ.generate: ascii_only and script_safe cannot be combined",
|
|
206
144
|
));
|
|
207
145
|
}
|
|
208
146
|
} else if script {
|
|
209
147
|
cfg.mode = EscapeMode::ScriptSafe;
|
|
210
148
|
}
|
|
211
|
-
if let Some(v) =
|
|
212
|
-
let v: Value = v;
|
|
149
|
+
if let Some(v) = r.get(Opt::MaxNesting) {
|
|
213
150
|
// nil/false → unlimited; true → keep the default 100; Integer → limit.
|
|
214
151
|
if !v.to_bool() {
|
|
215
152
|
cfg.max_nesting = 0;
|
|
@@ -217,14 +154,12 @@ pub(crate) fn parse_gen_opts(ruby: &Ruby, opts: Value) -> Result<(GenConfig, usi
|
|
|
217
154
|
cfg.max_nesting = if n <= 0 { 0 } else { n as usize };
|
|
218
155
|
}
|
|
219
156
|
}
|
|
220
|
-
if let Some(v) =
|
|
221
|
-
let v: Value = v;
|
|
157
|
+
if let Some(v) = r.get(Opt::Depth) {
|
|
222
158
|
if let Ok(n) = <i64 as magnus::TryConvert>::try_convert(v) {
|
|
223
159
|
cfg.start_depth = if n <= 0 { 0 } else { n as usize };
|
|
224
160
|
}
|
|
225
161
|
}
|
|
226
|
-
if let Some(v) =
|
|
227
|
-
let v: Value = v;
|
|
162
|
+
if let Some(v) = r.get(Opt::BufferInitialLength) {
|
|
228
163
|
if let Ok(n) = <i64 as magnus::TryConvert>::try_convert(v) {
|
|
229
164
|
if n > 0 {
|
|
230
165
|
cap_hint = n as usize;
|
data/ext/nosj/src/gen/ruby.rs
CHANGED
|
@@ -18,11 +18,31 @@ pub(super) fn protected_to_s(v: VALUE) -> Result<VALUE, Error> {
|
|
|
18
18
|
magnus::rb_sys::protect(|| unsafe { rb_sys::rb_obj_as_string(v) })
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/// `v.inspect`, protected (user code for any element's `inspect`).
|
|
22
|
+
pub(super) fn protected_inspect(v: VALUE) -> Result<VALUE, Error> {
|
|
23
|
+
magnus::rb_sys::protect(|| unsafe { rb_sys::rb_inspect(v) })
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
/// `v.to_json`, protected.
|
|
22
27
|
pub(super) fn protected_to_json(v: VALUE) -> Result<VALUE, Error> {
|
|
23
28
|
magnus::rb_sys::protect(|| unsafe { rb_sys::rb_funcall(v, to_json_id(), 0) })
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
/// `v.to_json` if `v.respond_to?(:to_json)`, else `None`, under ONE
|
|
32
|
+
/// protect: `rb_respond_to` dispatches to a user-defined `respond_to?` /
|
|
33
|
+
/// `respond_to_missing?`, which may raise just like `to_json` itself.
|
|
34
|
+
pub(super) fn protected_to_json_if_responds(v: VALUE) -> Result<Option<VALUE>, Error> {
|
|
35
|
+
const QUNDEF: VALUE = ruby_special_consts::RUBY_Qundef as VALUE;
|
|
36
|
+
let json = magnus::rb_sys::protect(|| unsafe {
|
|
37
|
+
if rb_sys::rb_respond_to(v, to_json_id()) != 0 {
|
|
38
|
+
rb_sys::rb_funcall(v, to_json_id(), 0)
|
|
39
|
+
} else {
|
|
40
|
+
QUNDEF
|
|
41
|
+
}
|
|
42
|
+
})?;
|
|
43
|
+
Ok((json != QUNDEF).then_some(json))
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
/// `v.as_json`, protected. Argument-less on purpose: ActiveSupport's
|
|
27
47
|
/// JSONGemEncoder#jsonify recursion also calls as_json without
|
|
28
48
|
/// options (only the top-level value receives them).
|
|
@@ -53,33 +73,37 @@ pub(crate) fn warm_up() {
|
|
|
53
73
|
/// first generate is still found; a fragment instance existing implies
|
|
54
74
|
/// its class does. The cached VALUE is a constant of the JSON module,
|
|
55
75
|
/// so it can never be collected.
|
|
56
|
-
pub(super) fn is_json_fragment(v: VALUE) -> bool {
|
|
76
|
+
pub(super) fn is_json_fragment(v: VALUE) -> Result<bool, Error> {
|
|
57
77
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
58
78
|
static FRAGMENT: AtomicUsize = AtomicUsize::new(0);
|
|
59
|
-
let mut cls = FRAGMENT.load(Ordering::Relaxed);
|
|
79
|
+
let mut cls = FRAGMENT.load(Ordering::Relaxed) as VALUE;
|
|
60
80
|
if cls == 0 {
|
|
61
|
-
cls = resolve_json_fragment()
|
|
81
|
+
cls = resolve_json_fragment()?;
|
|
62
82
|
if cls == 0 {
|
|
63
|
-
return false;
|
|
83
|
+
return Ok(false);
|
|
64
84
|
}
|
|
65
|
-
FRAGMENT.store(cls, Ordering::Relaxed);
|
|
85
|
+
FRAGMENT.store(cls as usize, Ordering::Relaxed);
|
|
66
86
|
}
|
|
67
|
-
unsafe { rb_sys::rb_obj_is_kind_of(v, cls
|
|
87
|
+
Ok(unsafe { rb_sys::rb_obj_is_kind_of(v, cls) != QFALSE })
|
|
68
88
|
}
|
|
69
89
|
|
|
70
|
-
|
|
90
|
+
/// `JSON::Fragment`, or 0 while undefined. Only the `rb_const_get`s are
|
|
91
|
+
/// protected: fetching a constant can run its pending autoload (user
|
|
92
|
+
/// code, whose raise propagates as any constant reference's would),
|
|
93
|
+
/// while `rb_const_defined` never loads anything.
|
|
94
|
+
fn resolve_json_fragment() -> Result<VALUE, Error> {
|
|
71
95
|
unsafe {
|
|
72
96
|
let object = rb_sys::rb_cObject;
|
|
73
97
|
let json_id = rb_sys::rb_intern(c"JSON".as_ptr());
|
|
74
98
|
if rb_sys::rb_const_defined(object, json_id) == 0 {
|
|
75
|
-
return 0;
|
|
99
|
+
return Ok(0);
|
|
76
100
|
}
|
|
77
|
-
let json = rb_sys::rb_const_get(object, json_id)
|
|
101
|
+
let json = magnus::rb_sys::protect(|| rb_sys::rb_const_get(object, json_id))?;
|
|
78
102
|
let fragment_id = rb_sys::rb_intern(c"Fragment".as_ptr());
|
|
79
103
|
if rb_sys::rb_const_defined(json, fragment_id) == 0 {
|
|
80
|
-
return 0;
|
|
104
|
+
return Ok(0);
|
|
81
105
|
}
|
|
82
|
-
rb_sys::rb_const_get(json, fragment_id)
|
|
106
|
+
magnus::rb_sys::protect(|| rb_sys::rb_const_get(json, fragment_id))
|
|
83
107
|
}
|
|
84
108
|
}
|
|
85
109
|
|
|
@@ -95,7 +119,7 @@ pub(super) fn protected_encode_utf8(v: VALUE) -> Result<VALUE, Error> {
|
|
|
95
119
|
}
|
|
96
120
|
|
|
97
121
|
/// Interned `to_json` method ID, resolved once per process.
|
|
98
|
-
|
|
122
|
+
fn to_json_id() -> rb_sys::ID {
|
|
99
123
|
static TO_JSON: OnceLock<usize> = OnceLock::new();
|
|
100
124
|
*TO_JSON.get_or_init(|| unsafe { rb_sys::rb_intern(c"to_json".as_ptr()) } as usize)
|
|
101
125
|
as rb_sys::ID
|
|
@@ -109,12 +133,16 @@ pub(super) fn utf8_encindexes() -> (c_int, c_int) {
|
|
|
109
133
|
// Coderange and encoding index live in RBasic flags (public ABI); reading
|
|
110
134
|
// them inline instead of calling rb_enc_str_coderange / rb_enc_get_index is
|
|
111
135
|
// how the gem avoids two C calls per string (RB_ENC_CODERANGE,
|
|
112
|
-
// RB_ENCODING_GET_INLINED).
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
136
|
+
// RB_ENCODING_GET_INLINED). The bit layout comes from rb-sys's bindings,
|
|
137
|
+
// generated from the headers of the Ruby being built against, so it
|
|
138
|
+
// follows any layout change instead of silently misreading flags.
|
|
139
|
+
const CR_MASK: u64 = rb_sys::ruby_coderange_type::RUBY_ENC_CODERANGE_MASK as u64;
|
|
140
|
+
pub(super) const CR_7BIT: u64 = rb_sys::ruby_coderange_type::RUBY_ENC_CODERANGE_7BIT as u64;
|
|
141
|
+
pub(super) const CR_VALID: u64 = rb_sys::ruby_coderange_type::RUBY_ENC_CODERANGE_VALID as u64;
|
|
142
|
+
const ENC_SHIFT: u64 = rb_sys::ruby_encoding_consts::RUBY_ENCODING_SHIFT as u64;
|
|
143
|
+
const ENC_MASK: u64 = rb_sys::ruby_encoding_consts::RUBY_ENCODING_MASK as u64;
|
|
144
|
+
/// Inline encoding-index sentinel: the real index is stored out of line.
|
|
145
|
+
const ENC_INLINE_MAX: c_int = rb_sys::ruby_encoding_consts::RUBY_ENCODING_INLINE_MAX as c_int;
|
|
118
146
|
|
|
119
147
|
#[inline(always)]
|
|
120
148
|
pub(super) fn str_coderange(s: VALUE) -> u64 {
|
|
@@ -131,18 +159,18 @@ pub(super) fn str_coderange(s: VALUE) -> u64 {
|
|
|
131
159
|
pub(super) fn str_enc_index(s: VALUE) -> c_int {
|
|
132
160
|
let flags = unsafe { (*(s as *const rb_sys::RBasic)).flags };
|
|
133
161
|
let idx = ((flags & ENC_MASK) >> ENC_SHIFT) as c_int;
|
|
134
|
-
if idx ==
|
|
135
|
-
// RUBY_ENCODING_INLINE_MAX sentinel: index stored out of line.
|
|
162
|
+
if idx == ENC_INLINE_MAX {
|
|
136
163
|
unsafe { rb_sys::rb_enc_get_index(s) }
|
|
137
164
|
} else {
|
|
138
165
|
idx
|
|
139
166
|
}
|
|
140
167
|
}
|
|
141
168
|
|
|
169
|
+
/// `RB_SPECIAL_CONST_P` (immediates plus Qnil/Qfalse), through rb-sys's
|
|
170
|
+
/// inline versioned stable API rather than a hand-copied bit test.
|
|
142
171
|
#[inline(always)]
|
|
143
172
|
pub(super) fn is_special_const(v: VALUE) -> bool {
|
|
144
|
-
|
|
145
|
-
(v & (ruby_special_consts::RUBY_IMMEDIATE_MASK as VALUE)) != 0 || v == QNIL || v == QFALSE
|
|
173
|
+
rb_sys::macros::SPECIAL_CONST_P(v)
|
|
146
174
|
}
|
|
147
175
|
|
|
148
176
|
/// Borrow a Ruby String's bytes.
|