quickjs_oxide/source/
unicode.rs1pub use oxide_regexp::unicode::{is_id_continue, is_id_start, property};
7
8use crate::engine::value::{JsString, JsStringError};
9use oxide_regexp::unicode::{CodeUnitBuf, StringError};
10
11const _: () = assert!(JsString::MAX_LEN == oxide_regexp::unicode::MAX_STRING_LEN);
12
13fn with_code_units<R>(
17 input: &JsString,
18 latin1: impl FnOnce(&[u8]) -> R,
19 utf16: impl FnOnce(&[u16]) -> R,
20) -> R {
21 if let Some(units) = input.flat_latin1() {
22 latin1(units)
23 } else if let Some(units) = input.flat_utf16() {
24 utf16(units)
25 } else {
26 utf16(&input.utf16_units().collect::<Vec<_>>())
27 }
28}
29
30fn into_js_string(output: Result<CodeUnitBuf, StringError>) -> Result<JsString, JsStringError> {
31 match output {
32 Ok(CodeUnitBuf::Latin1(units)) => Ok(JsString::from_owned_latin1(units)),
33 Ok(CodeUnitBuf::Utf16(units)) => Ok(JsString::from_owned_utf16(units)),
34 Err(error) => Err(into_js_string_error(error)),
35 }
36}
37
38const fn into_js_string_error(error: StringError) -> JsStringError {
39 match error {
40 StringError::TooLong => JsStringError::TooLong,
41 StringError::OutOfMemory => JsStringError::OutOfMemory,
42 }
43}
44
45pub mod case {
46 pub use oxide_regexp::unicode::case::*;
49
50 use crate::engine::value::{JsString, JsStringError};
51
52 pub fn convert_case_with_limit(
56 input: &JsString,
57 upper: bool,
58 max_len: usize,
59 ) -> Result<JsString, JsStringError> {
60 if input.is_empty() {
61 return Ok(input.clone());
62 }
63 super::into_js_string(super::with_code_units(
64 input,
65 |units| oxide_regexp::unicode::case::convert_case_with_limit(units, upper, max_len),
66 |units| oxide_regexp::unicode::case::convert_case_with_limit(units, upper, max_len),
67 ))
68 }
69}
70
71pub mod normalize {
72 pub use oxide_regexp::unicode::normalize::*;
76
77 use crate::engine::value::{JsString, JsStringError};
78
79 pub fn normalize_code_points(
81 input: &JsString,
82 form: NormalizationForm,
83 ) -> Result<Vec<u32>, JsStringError> {
84 super::with_code_units(
85 input,
86 |units| oxide_regexp::unicode::normalize::normalize_code_points(units, form),
87 |units| oxide_regexp::unicode::normalize::normalize_code_points(units, form),
88 )
89 .map_err(super::into_js_string_error)
90 }
91
92 pub fn normalize_with_limit(
94 input: &JsString,
95 form: NormalizationForm,
96 max_len: usize,
97 ) -> Result<JsString, JsStringError> {
98 super::into_js_string(super::with_code_units(
99 input,
100 |units| oxide_regexp::unicode::normalize::normalize_with_limit(units, form, max_len),
101 |units| oxide_regexp::unicode::normalize::normalize_with_limit(units, form, max_len),
102 ))
103 }
104
105 pub fn normalize(input: &JsString, form: NormalizationForm) -> Result<JsString, JsStringError> {
109 normalize_with_limit(input, form, JsString::MAX_LEN)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::case::{convert_case_with_limit, fail_next_case_reservation_for_test};
116 use super::normalize::{NormalizationForm, normalize, normalize_code_points};
117 use crate::engine::value::{JsString, JsStringError};
118
119 #[test]
120 fn empty_case_conversion_returns_the_input_without_reserving() {
121 let empty = JsString::from_static("");
122 fail_next_case_reservation_for_test();
123 let converted_empty = convert_case_with_limit(&empty, true, 0).unwrap();
124 assert!(converted_empty.same_representation(&empty));
125
126 let ascii = JsString::from_static("a");
128 assert_eq!(
129 convert_case_with_limit(&ascii, true, 1),
130 Err(JsStringError::OutOfMemory)
131 );
132 }
133
134 #[test]
135 fn wrappers_read_latin1_utf16_and_rope_strings() {
136 let latin1 = JsString::try_from_utf8("ÿ").unwrap();
137 assert!(!latin1.is_wide());
138 let upper = convert_case_with_limit(&latin1, true, JsString::MAX_LEN).unwrap();
139 assert_eq!(upper.to_utf8_lossy(), "Ÿ");
140 assert!(upper.is_wide());
141
142 let wide = JsString::try_from_utf8("Ÿ").unwrap();
143 let lower = convert_case_with_limit(&wide, false, JsString::MAX_LEN).unwrap();
144 assert_eq!(lower.to_utf8_lossy(), "ÿ");
145 assert!(!lower.is_wide());
146
147 let rope = JsString::try_from_utf8(&"e".repeat(600))
148 .unwrap()
149 .try_concat(&JsString::try_from_utf8("\u{301}").unwrap())
150 .unwrap();
151 let normalized = normalize(&rope, NormalizationForm::Nfc).unwrap();
152 assert_eq!(normalized.len(), 600);
153 assert_eq!(normalized.code_unit_at(599), Some(0xe9));
154 assert_eq!(
155 normalize_code_points(&rope, NormalizationForm::Nfd)
156 .unwrap()
157 .len(),
158 601
159 );
160 }
161}