Skip to content

Commit 13135ed

Browse files
authored
Inline utf-8 crate (#731)
Signed-off-by: Nico Burns <nico@nicoburns.com>
1 parent 803ad9e commit 13135ed

6 files changed

Lines changed: 144 additions & 3 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ phf = "0.13"
3333
phf_codegen = "0.13"
3434
string_cache = { version = "0.9.0", default-features = false }
3535
string_cache_codegen = "0.6.1"
36-
utf-8 = "0.7"
3736

3837
# Dev dependencies
3938
criterion = "0.8"

tendril/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ encoding_rs = ["dep:encoding_rs"]
1919
[dependencies]
2020
encoding_rs = { workspace = true, optional = true}
2121
new_debug_unreachable = { workspace = true }
22-
utf-8 = { workspace = true }
2322

2423
[dev-dependencies]
2524
rand = { workspace = true }

tendril/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub mod stream;
2525

2626
mod buf32;
2727
mod tendril;
28+
mod utf8;
2829
mod utf8_decode;
2930
mod util;
3031

tendril/src/stream.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use std::io;
1515
use std::marker::PhantomData;
1616
use std::path::Path;
1717

18+
use crate::utf8;
1819
#[cfg(feature = "encoding_rs")]
1920
use encoding_rs::{self, DecoderResult};
20-
use utf8;
2121

2222
/// Trait for types that can process a tendril.
2323
///

tendril/src/utf8.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
4+
// option. This file may not be copied, modified, or distributed
5+
// except according to those terms.
6+
use std::cmp;
7+
use std::str;
8+
9+
/// The replacement character, U+FFFD. In lossy decoding, insert it for every decoding error.
10+
pub(crate) const REPLACEMENT_CHARACTER: &str = "\u{FFFD}";
11+
12+
#[derive(Debug, Copy, Clone)]
13+
pub(crate) enum DecodeError<'a> {
14+
/// In lossy decoding insert `valid_prefix`, then `"\u{FFFD}"`,
15+
/// then call `decode()` again with `remaining_input`.
16+
Invalid {
17+
valid_prefix: &'a str,
18+
invalid_sequence: &'a [u8],
19+
#[allow(unused)]
20+
remaining_input: &'a [u8],
21+
},
22+
23+
/// Call the `incomplete_suffix.try_complete` method with more input when available.
24+
/// If no more input is available, this is an invalid byte sequence.
25+
Incomplete {
26+
valid_prefix: &'a str,
27+
incomplete_suffix: Incomplete,
28+
},
29+
}
30+
31+
#[derive(Debug, Copy, Clone)]
32+
pub(crate) struct Incomplete {
33+
pub(crate) buffer: [u8; 4],
34+
pub(crate) buffer_len: u8,
35+
}
36+
37+
pub(crate) fn decode(input: &[u8]) -> Result<&str, DecodeError<'_>> {
38+
let error = match str::from_utf8(input) {
39+
Ok(valid) => return Ok(valid),
40+
Err(error) => error,
41+
};
42+
43+
// FIXME: separate function from here to guide inlining?
44+
let (valid, after_valid) = input.split_at(error.valid_up_to());
45+
let valid = unsafe { str::from_utf8_unchecked(valid) };
46+
47+
match error.error_len() {
48+
Some(invalid_sequence_length) => {
49+
let (invalid, rest) = after_valid.split_at(invalid_sequence_length);
50+
Err(DecodeError::Invalid {
51+
valid_prefix: valid,
52+
invalid_sequence: invalid,
53+
remaining_input: rest,
54+
})
55+
},
56+
None => Err(DecodeError::Incomplete {
57+
valid_prefix: valid,
58+
incomplete_suffix: Incomplete::new(after_valid),
59+
}),
60+
}
61+
}
62+
63+
impl Incomplete {
64+
fn new(bytes: &[u8]) -> Self {
65+
let mut buffer = [0, 0, 0, 0];
66+
let len = bytes.len();
67+
buffer[..len].copy_from_slice(bytes);
68+
Incomplete {
69+
buffer,
70+
buffer_len: len as u8,
71+
}
72+
}
73+
74+
/// * `None`: still incomplete, call `try_complete` again with more input.
75+
/// If no more input is available, this is invalid byte sequence.
76+
/// * `Some((result, remaining_input))`: We’re done with this `Incomplete`.
77+
/// To keep decoding, pass `remaining_input` to `decode()`.
78+
#[allow(clippy::type_complexity)]
79+
pub(crate) fn try_complete<'input>(
80+
&mut self,
81+
input: &'input [u8],
82+
) -> Option<(Result<&str, &[u8]>, &'input [u8])> {
83+
let (consumed, opt_result) = self.try_complete_offsets(input);
84+
let result = opt_result?;
85+
let remaining_input = &input[consumed..];
86+
let result_bytes = self.take_buffer();
87+
let result = match result {
88+
Ok(()) => Ok(unsafe { str::from_utf8_unchecked(result_bytes) }),
89+
Err(()) => Err(result_bytes),
90+
};
91+
Some((result, remaining_input))
92+
}
93+
94+
fn take_buffer(&mut self) -> &[u8] {
95+
let len = self.buffer_len as usize;
96+
self.buffer_len = 0;
97+
&self.buffer[..len]
98+
}
99+
100+
/// (consumed_from_input, None): not enough input
101+
/// (consumed_from_input, Some(Err(()))): error bytes in buffer
102+
/// (consumed_from_input, Some(Ok(()))): UTF-8 string in buffer
103+
fn try_complete_offsets(&mut self, input: &[u8]) -> (usize, Option<Result<(), ()>>) {
104+
let initial_buffer_len = self.buffer_len as usize;
105+
let copied_from_input;
106+
{
107+
let unwritten = &mut self.buffer[initial_buffer_len..];
108+
copied_from_input = cmp::min(unwritten.len(), input.len());
109+
unwritten[..copied_from_input].copy_from_slice(&input[..copied_from_input]);
110+
}
111+
let spliced = &self.buffer[..initial_buffer_len + copied_from_input];
112+
match str::from_utf8(spliced) {
113+
Ok(_) => {
114+
self.buffer_len = spliced.len() as u8;
115+
(copied_from_input, Some(Ok(())))
116+
},
117+
Err(error) => {
118+
let valid_up_to = error.valid_up_to();
119+
if valid_up_to > 0 {
120+
let consumed = valid_up_to.checked_sub(initial_buffer_len).unwrap();
121+
self.buffer_len = valid_up_to as u8;
122+
(consumed, Some(Ok(())))
123+
} else {
124+
match error.error_len() {
125+
Some(invalid_sequence_length) => {
126+
let consumed = invalid_sequence_length
127+
.checked_sub(initial_buffer_len)
128+
.unwrap();
129+
self.buffer_len = invalid_sequence_length as u8;
130+
(consumed, Some(Err(())))
131+
},
132+
None => {
133+
self.buffer_len = spliced.len() as u8;
134+
(copied_from_input, None)
135+
},
136+
}
137+
}
138+
},
139+
}
140+
}
141+
}

tendril/src/utf8_decode.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// except according to those terms.
66

77
use crate::fmt;
8+
use crate::utf8;
89
use crate::{Atomicity, Tendril};
910

1011
pub struct IncompleteUtf8(utf8::Incomplete);

0 commit comments

Comments
 (0)