sim_lib_lang_javascript/
text.rs1#[derive(Clone, Debug, Eq, PartialEq)]
5pub enum JavascriptTextError {
6 LoneSurrogate {
8 index: usize,
10 unit: u16,
12 },
13}
14
15#[derive(Clone, Debug, Default, Eq, PartialEq)]
20pub struct JavascriptCodeUnitString {
21 units: Vec<u16>,
22}
23impl JavascriptCodeUnitString {
24 pub fn from_scalar(text: &str) -> Self {
26 Self {
27 units: text.encode_utf16().collect(),
28 }
29 }
30 pub fn from_code_units(units: Vec<u16>) -> Self {
32 Self { units }
33 }
34 pub fn len(&self) -> usize {
36 self.units.len()
37 }
38 pub fn is_empty(&self) -> bool {
40 self.units.is_empty()
41 }
42 pub fn code_unit_at(&self, index: usize) -> Option<u16> {
44 self.units.get(index).copied()
45 }
46 pub fn slice(&self, start: usize, end: usize) -> Self {
48 let start = start.min(self.len());
49 let end = end.max(start).min(self.len());
50 Self::from_code_units(self.units[start..end].to_vec())
51 }
52 pub fn code_units(&self) -> impl Iterator<Item = u16> + '_ {
54 self.units.iter().copied()
55 }
56 pub fn iter_strings(&self) -> JavascriptStringIterator<'_> {
58 JavascriptStringIterator {
59 units: &self.units,
60 at: 0,
61 }
62 }
63 pub fn to_scalar(&self) -> Result<String, JavascriptTextError> {
65 String::from_utf16(&self.units).map_err(|_| {
66 let (index, unit) =
67 first_lone(&self.units).expect("invalid UTF-16 has a lone surrogate");
68 JavascriptTextError::LoneSurrogate { index, unit }
69 })
70 }
71}
72pub struct JavascriptStringIterator<'a> {
74 units: &'a [u16],
75 at: usize,
76}
77impl Iterator for JavascriptStringIterator<'_> {
78 type Item = JavascriptCodeUnitString;
79 fn next(&mut self) -> Option<Self::Item> {
80 let first = *self.units.get(self.at)?;
81 let width = if (0xd800..=0xdbff).contains(&first)
82 && self
83 .units
84 .get(self.at + 1)
85 .is_some_and(|u| (0xdc00..=0xdfff).contains(u))
86 {
87 2
88 } else {
89 1
90 };
91 let out = JavascriptCodeUnitString::from_code_units(
92 self.units[self.at..self.at + width].to_vec(),
93 );
94 self.at += width;
95 Some(out)
96 }
97}
98fn first_lone(units: &[u16]) -> Option<(usize, u16)> {
99 let mut i = 0;
100 while i < units.len() {
101 let u = units[i];
102 if (0xd800..=0xdbff).contains(&u) {
103 if units
104 .get(i + 1)
105 .is_some_and(|v| (0xdc00..=0xdfff).contains(v))
106 {
107 i += 2;
108 continue;
109 }
110 return Some((i, u));
111 }
112 if (0xdc00..=0xdfff).contains(&u) {
113 return Some((i, u));
114 }
115 i += 1;
116 }
117 None
118}
119
120#[cfg(test)]
121mod law_fixtures {
122 use super::*;
123 #[test]
124 fn length_index_slice_and_code_unit_iteration_are_utf16() {
125 let s = JavascriptCodeUnitString::from_scalar("A😀B");
126 assert_eq!(s.len(), 4);
127 assert_eq!(s.code_unit_at(1), Some(0xd83d));
128 assert_eq!(
129 s.slice(1, 3).code_units().collect::<Vec<_>>(),
130 vec![0xd83d, 0xde00]
131 );
132 assert_eq!(s.code_units().count(), 4);
133 }
134 #[test]
135 fn scalar_conversion_and_paired_iteration_are_exact() {
136 let s = JavascriptCodeUnitString::from_code_units(vec![0xd83d, 0xde00]);
137 assert_eq!(s.to_scalar().unwrap(), "😀");
138 assert_eq!(s.iter_strings().next().unwrap().len(), 2);
139 }
140 #[test]
141 fn lone_high_surrogate_is_preserved_and_rejected_by_scalar_face() {
142 let s = JavascriptCodeUnitString::from_code_units(vec![0xd800]);
143 assert_eq!(s.code_unit_at(0), Some(0xd800));
144 assert_eq!(
145 s.iter_strings()
146 .next()
147 .unwrap()
148 .code_units()
149 .collect::<Vec<_>>(),
150 vec![0xd800]
151 );
152 assert_eq!(
153 s.to_scalar(),
154 Err(JavascriptTextError::LoneSurrogate {
155 index: 0,
156 unit: 0xd800
157 })
158 );
159 }
160 #[test]
161 fn lone_low_surrogate_is_preserved_and_rejected_by_scalar_face() {
162 let s = JavascriptCodeUnitString::from_code_units(vec![0xdc00]);
163 assert_eq!(s.slice(0, 1), s);
164 assert_eq!(
165 s.to_scalar(),
166 Err(JavascriptTextError::LoneSurrogate {
167 index: 0,
168 unit: 0xdc00
169 })
170 );
171 }
172}