mini_racer_rs 1.0.2 → 1.0.4
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 +14 -0
- data/COMPATIBILITY.md +1 -1
- data/Cargo.lock +4 -4
- data/Cargo.toml +1 -1
- data/README.md +19 -13
- data/crates/mini_racer_engine/src/process/code_cache.rs +61 -25
- data/crates/mini_racer_engine/src/process/sources.rs +244 -50
- data/crates/mini_racer_engine/src/runner.rs +20 -10
- data/crates/mini_racer_wire/src/decode.rs +24 -2
- data/crates/mini_racer_wire/src/text.rs +160 -49
- data/ext/mini_racer_rs/src/decode.rs +338 -35
- data/ext/mini_racer_rs/src/gc_root.rs +67 -2
- data/ext/mini_racer_rs/src/lib.rs +4 -0
- data/ext/mini_racer_rs/src/rb_raw.rs +39 -3
- data/lib/mini_racer_rs/version.rb +1 -1
- metadata +2 -2
|
@@ -1,68 +1,130 @@
|
|
|
1
|
-
// Returned HTML is mostly ASCII, which is copied in
|
|
2
|
-
// another byte are converted a byte at a time.
|
|
1
|
+
// Returned HTML is mostly ASCII, which is copied in blocks; only blocks
|
|
2
|
+
// holding another byte are converted a byte at a time.
|
|
3
3
|
const LATIN1_BLOCK: usize = 32;
|
|
4
|
-
//
|
|
5
|
-
|
|
4
|
+
// Lengths are counted a block at a time in a u8, which vectorizes; a UTF-16
|
|
5
|
+
// unit adds at most 3.
|
|
6
|
+
const COUNT_BLOCK: usize = 64;
|
|
7
|
+
const _: () = assert!(COUNT_BLOCK * 3 <= u8::MAX as usize);
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
/// The length in UTF-8 of Latin-1 `bytes`.
|
|
10
|
+
#[must_use]
|
|
11
|
+
pub fn latin1_utf8_len(bytes: &[u8]) -> usize {
|
|
12
|
+
let wide = bytes.chunks(COUNT_BLOCK).fold(0_usize, |wide, block| {
|
|
13
|
+
let count = block.iter().fold(0_u8, |count, &byte| {
|
|
14
|
+
count.wrapping_add(u8::from(!byte.is_ascii()))
|
|
15
|
+
});
|
|
16
|
+
wide.saturating_add(usize::from(count))
|
|
17
|
+
});
|
|
18
|
+
bytes.len().saturating_add(wide)
|
|
10
19
|
}
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.map(|&byte| u8::from(!byte.is_ascii()))
|
|
24
|
-
.sum::<u8>();
|
|
25
|
-
(
|
|
26
|
-
wide.saturating_add(usize::from(count)),
|
|
27
|
-
mixed.saturating_add(usize::from(count > 0)),
|
|
28
|
-
)
|
|
29
|
-
});
|
|
30
|
-
let mut out = String::with_capacity(bytes.len().saturating_add(wide));
|
|
31
|
-
// Text where most blocks need converting anyway, like accented prose, is
|
|
32
|
-
// faster a byte at a time throughout.
|
|
33
|
-
if mixed.saturating_mul(LATIN1_BLOCK).saturating_mul(2) > bytes.len() {
|
|
34
|
-
out.extend(bytes.iter().copied().map(char::from));
|
|
35
|
-
return out;
|
|
36
|
-
}
|
|
37
|
-
let mut span = 0;
|
|
21
|
+
// Writes `c` at `at` and returns where it ends, or `None` when it does not fit.
|
|
22
|
+
fn put(out: &mut [u8], at: usize, c: char) -> Option<usize> {
|
|
23
|
+
let end = at.checked_add(c.len_utf8())?;
|
|
24
|
+
c.encode_utf8(out.get_mut(at..end)?);
|
|
25
|
+
Some(end)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Writes the UTF-8 of Latin-1 `bytes` at the start of `out`, up to the last
|
|
29
|
+
/// character that fits, and returns how many bytes it wrote: all
|
|
30
|
+
/// `latin1_utf8_len(bytes)` of them when `out` is that long.
|
|
31
|
+
pub fn latin1_to_utf8_into(bytes: &[u8], out: &mut [u8]) -> usize {
|
|
38
32
|
let mut at: usize = 0;
|
|
39
33
|
for block in bytes.chunks(LATIN1_BLOCK) {
|
|
40
|
-
let
|
|
41
|
-
if
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
let end = at.saturating_add(block.len());
|
|
35
|
+
if block.is_ascii()
|
|
36
|
+
&& let Some(slots) = out.get_mut(at..end)
|
|
37
|
+
{
|
|
38
|
+
slots.copy_from_slice(block);
|
|
39
|
+
at = end;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
for &byte in block {
|
|
43
|
+
match put(out, at, char::from(byte)) {
|
|
44
|
+
Some(next) => at = next,
|
|
45
|
+
None => return at,
|
|
47
46
|
}
|
|
48
|
-
span = next;
|
|
49
47
|
}
|
|
50
|
-
at = next;
|
|
51
48
|
}
|
|
52
|
-
|
|
53
|
-
out
|
|
49
|
+
at
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
#[must_use]
|
|
57
|
-
|
|
53
|
+
#[expect(clippy::expect_used, reason = "only whole characters are written")]
|
|
54
|
+
pub fn latin1_to_utf8(bytes: &[u8]) -> String {
|
|
55
|
+
let mut out = vec![0; latin1_utf8_len(bytes)];
|
|
56
|
+
let written = latin1_to_utf8_into(bytes, &mut out);
|
|
57
|
+
out.truncate(written);
|
|
58
|
+
String::from_utf8(out).expect("Latin-1 transcodes to UTF-8")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn utf16le_chars(bytes: &[u8]) -> Option<impl Iterator<Item = Result<char, ()>>> {
|
|
62
|
+
let (pairs, rest) = bytes.as_chunks::<2>();
|
|
63
|
+
let units = pairs.iter().copied().map(u16::from_le_bytes);
|
|
64
|
+
rest.is_empty()
|
|
65
|
+
.then(|| char::decode_utf16(units).map(|c| c.map_err(|_| ())))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// The length in UTF-8 of UTF-16LE `bytes`, or `None` when they hold an odd
|
|
69
|
+
/// byte or a lone surrogate.
|
|
70
|
+
#[must_use]
|
|
71
|
+
pub fn utf16le_utf8_len(bytes: &[u8]) -> Option<usize> {
|
|
58
72
|
let (pairs, rest) = bytes.as_chunks::<2>();
|
|
59
73
|
if !rest.is_empty() {
|
|
60
74
|
return None;
|
|
61
75
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
// A unit takes one byte, one more from U+0080 and one more from U+0800,
|
|
77
|
+
// which counts the two units of a surrogate pair as six bytes for its four.
|
|
78
|
+
let mut len: usize = 0;
|
|
79
|
+
let mut surrogates = false;
|
|
80
|
+
for block in pairs.chunks(COUNT_BLOCK) {
|
|
81
|
+
let mut count: u8 = 0;
|
|
82
|
+
for &pair in block {
|
|
83
|
+
let unit = u16::from_le_bytes(pair);
|
|
84
|
+
count = count
|
|
85
|
+
.wrapping_add(1)
|
|
86
|
+
.wrapping_add(u8::from(unit >= 0x80))
|
|
87
|
+
.wrapping_add(u8::from(unit >= 0x800));
|
|
88
|
+
surrogates |= (0xD800..0xE000).contains(&unit);
|
|
89
|
+
}
|
|
90
|
+
len = len.saturating_add(usize::from(count));
|
|
91
|
+
}
|
|
92
|
+
if !surrogates {
|
|
93
|
+
return Some(len);
|
|
94
|
+
}
|
|
95
|
+
let mut surrogate_pairs: usize = 0;
|
|
96
|
+
let mut units = pairs.iter().copied().map(u16::from_le_bytes);
|
|
97
|
+
while let Some(unit) = units.next() {
|
|
98
|
+
if (0xD800..0xDC00).contains(&unit) {
|
|
99
|
+
units.next().filter(|low| (0xDC00..0xE000).contains(low))?;
|
|
100
|
+
surrogate_pairs = surrogate_pairs.saturating_add(1);
|
|
101
|
+
} else if (0xDC00..0xE000).contains(&unit) {
|
|
102
|
+
return None;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
Some(len.saturating_sub(surrogate_pairs.saturating_mul(2)))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Writes the UTF-8 of UTF-16LE `bytes` at the start of `out` and returns how
|
|
109
|
+
/// many bytes it wrote.
|
|
110
|
+
///
|
|
111
|
+
/// `None` when they hold an odd byte or a lone surrogate, or when `out` lacks
|
|
112
|
+
/// room, which three bytes per unit never does.
|
|
113
|
+
pub fn utf16le_to_utf8_into(bytes: &[u8], out: &mut [u8]) -> Option<usize> {
|
|
114
|
+
let mut at = 0;
|
|
115
|
+
for c in utf16le_chars(bytes)? {
|
|
116
|
+
at = put(out, at, c.ok()?)?;
|
|
117
|
+
}
|
|
118
|
+
Some(at)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[must_use]
|
|
122
|
+
#[expect(clippy::expect_used, reason = "only whole characters are written")]
|
|
123
|
+
pub fn utf16le_to_utf8(bytes: &[u8]) -> Option<String> {
|
|
124
|
+
let mut out = vec![0; utf16le_utf8_len(bytes)?];
|
|
125
|
+
let written = utf16le_to_utf8_into(bytes, &mut out)?;
|
|
126
|
+
out.truncate(written);
|
|
127
|
+
Some(String::from_utf8(out).expect("UTF-16 transcodes to UTF-8"))
|
|
66
128
|
}
|
|
67
129
|
|
|
68
130
|
#[cfg(test)]
|
|
@@ -83,8 +145,38 @@ mod tests {
|
|
|
83
145
|
utf16le_to_utf8(&[0x3D, 0xD8, 0x00, 0xDE]).as_deref(),
|
|
84
146
|
Some("😀")
|
|
85
147
|
);
|
|
148
|
+
assert_eq!(
|
|
149
|
+
utf16le_utf8_len(&[0x3D, 0xD8, 0x00, 0xDE, 0x41, 0x00]),
|
|
150
|
+
Some(5)
|
|
151
|
+
);
|
|
86
152
|
assert_eq!(utf16le_to_utf8(&[0x41]), None);
|
|
153
|
+
assert_eq!(utf16le_utf8_len(&[0x41]), None);
|
|
87
154
|
assert_eq!(utf16le_to_utf8(&[0x3D, 0xD8]), None);
|
|
155
|
+
assert_eq!(utf16le_utf8_len(&[0x41, 0x00, 0x00, 0xDC]), None);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[test]
|
|
159
|
+
fn writers_stop_where_text_does_not_decode_or_fit() {
|
|
160
|
+
let mut out = [0; 8];
|
|
161
|
+
assert_eq!(
|
|
162
|
+
utf16le_to_utf8_into(&[0x41, 0x00, 0x00, 0xDC], &mut out),
|
|
163
|
+
None
|
|
164
|
+
);
|
|
165
|
+
assert_eq!(utf16le_to_utf8_into(&[0x41, 0x00, 0x42], &mut out), None);
|
|
166
|
+
assert_eq!(
|
|
167
|
+
utf16le_to_utf8_into(&[0x3D, 0xD8, 0x00, 0xDE], out.get_mut(..3).unwrap()),
|
|
168
|
+
None
|
|
169
|
+
);
|
|
170
|
+
assert_eq!(
|
|
171
|
+
utf16le_to_utf8_into(&[0x3D, 0xD8, 0x00, 0xDE], &mut out),
|
|
172
|
+
Some(4)
|
|
173
|
+
);
|
|
174
|
+
let written = latin1_to_utf8_into(b"ab\xE9c", out.get_mut(..3).unwrap());
|
|
175
|
+
assert_eq!(out.get(..written), Some(b"ab".as_slice()));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn utf16le(units: &[u16]) -> Vec<u8> {
|
|
179
|
+
units.iter().flat_map(|unit| unit.to_le_bytes()).collect()
|
|
88
180
|
}
|
|
89
181
|
|
|
90
182
|
proptest! {
|
|
@@ -100,7 +192,26 @@ mod tests {
|
|
|
100
192
|
)) {
|
|
101
193
|
let bytes: Vec<u8> = segments.into_iter().flat_map(|(run, wide)| run.into_iter().chain(wide)).collect();
|
|
102
194
|
let by_byte: String = bytes.iter().copied().map(char::from).collect();
|
|
195
|
+
prop_assert_eq!(latin1_utf8_len(&bytes), by_byte.len());
|
|
196
|
+
let mut out = vec![0; bytes.len().saturating_mul(2)];
|
|
197
|
+
let written = latin1_to_utf8_into(&bytes, &mut out);
|
|
198
|
+
prop_assert_eq!(out.get(..written), Some(by_byte.as_bytes()));
|
|
103
199
|
prop_assert_eq!(latin1_to_utf8(&bytes), by_byte);
|
|
104
200
|
}
|
|
201
|
+
|
|
202
|
+
// Units from each range UTF-8 sizes differently, surrogates included,
|
|
203
|
+
// so pairs, lone halves and pairs in the wrong order all come up.
|
|
204
|
+
#[test]
|
|
205
|
+
fn utf16_matches_std(units in proptest::collection::vec(
|
|
206
|
+
prop_oneof![0u16..0x80, 0x80u16..0x800, 0x800u16..0xD800, 0xD800u16..0xE000, 0xE000u16..=0xFFFF],
|
|
207
|
+
0..64,
|
|
208
|
+
)) {
|
|
209
|
+
let expected = String::from_utf16(&units).ok();
|
|
210
|
+
prop_assert_eq!(utf16le_utf8_len(&utf16le(&units)), expected.as_ref().map(String::len));
|
|
211
|
+
let mut out = vec![0; units.len().saturating_mul(3)];
|
|
212
|
+
let written = utf16le_to_utf8_into(&utf16le(&units), &mut out);
|
|
213
|
+
prop_assert_eq!(written.and_then(|n| out.get(..n)), expected.as_ref().map(String::as_bytes));
|
|
214
|
+
prop_assert_eq!(utf16le_to_utf8(&utf16le(&units)), expected);
|
|
215
|
+
}
|
|
105
216
|
}
|
|
106
217
|
}
|