darklua_core/nodes/expressions/
string_utils.rs1use std::{fmt, iter::Peekable, str::CharIndices};
2
3#[derive(Debug, Clone)]
4enum StringErrorKind {
5 Invalid { message: String },
6 MalformedEscapeSequence { position: usize, message: String },
7}
8
9#[derive(Debug, Clone)]
10pub struct StringError {
11 kind: StringErrorKind,
12}
13
14impl fmt::Display for StringError {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match &self.kind {
17 StringErrorKind::Invalid { message } => {
18 write!(f, "invalid string: {}", message)
19 }
20 StringErrorKind::MalformedEscapeSequence { position, message } => {
21 write!(f, "malformed escape sequence at {}: {}", position, message)
22 }
23 }
24 }
25}
26
27impl StringError {
28 pub(crate) fn invalid(message: impl Into<String>) -> Self {
29 Self {
30 kind: StringErrorKind::Invalid {
31 message: message.into(),
32 },
33 }
34 }
35 pub(crate) fn malformed_escape_sequence(position: usize, message: impl Into<String>) -> Self {
36 Self {
37 kind: StringErrorKind::MalformedEscapeSequence {
38 position,
39 message: message.into(),
40 },
41 }
42 }
43}
44
45pub(crate) fn read_escaped_string(
46 chars: CharIndices,
47 reserve_size: Option<usize>,
48) -> Result<String, StringError> {
49 let mut chars = chars.peekable();
50
51 let mut value = String::new();
52 if let Some(reserve_size) = reserve_size {
53 value.reserve(reserve_size);
54 }
55
56 while let Some((position, char)) = chars.next() {
57 if char == '\\' {
58 if let Some((_, next_char)) = chars.next() {
59 match next_char {
60 '\n' | '"' | '\'' | '\\' => value.push(next_char),
61 'n' => value.push('\n'),
62 't' => value.push('\t'),
63 'a' => value.push('\u{7}'),
64 'b' => value.push('\u{8}'),
65 'v' => value.push('\u{B}'),
66 'f' => value.push('\u{C}'),
67 'r' => value.push('\r'),
68 first_digit if first_digit.is_ascii_digit() => {
69 let number = read_number(&mut chars, Some(first_digit), 10, 3);
70
71 if number < 256 {
72 value.push(number as u8 as char);
73 } else {
74 return Err(StringError::malformed_escape_sequence(
75 position,
76 "cannot escape ascii character greater than 256",
77 ));
78 }
79 }
80 'x' => {
81 if let (Some(first_digit), Some(second_digit)) = (
82 chars.next().map(|(_, c)| c).filter(char::is_ascii_hexdigit),
83 chars.next().map(|(_, c)| c).filter(char::is_ascii_hexdigit),
84 ) {
85 let number = 16 * first_digit.to_digit(16).unwrap()
86 + second_digit.to_digit(16).unwrap();
87
88 if number < 256 {
89 value.push(number as u8 as char);
90 } else {
91 return Err(StringError::malformed_escape_sequence(
92 position,
93 "cannot escape ascii character greater than 256",
94 ));
95 }
96 } else {
97 return Err(StringError::malformed_escape_sequence(
98 position,
99 "exactly two hexadecimal digit expected",
100 ));
101 }
102 }
103 'u' => {
104 if !contains(&chars.next().map(|(_, c)| c), &'{') {
105 return Err(StringError::malformed_escape_sequence(
106 position,
107 "expected opening curly brace",
108 ));
109 }
110
111 let number = read_number(&mut chars, None, 16, 8);
112
113 if !contains(&chars.next().map(|(_, c)| c), &'}') {
114 return Err(StringError::malformed_escape_sequence(
115 position,
116 "expected closing curly brace",
117 ));
118 }
119
120 if number > 0x10FFFF {
121 return Err(StringError::malformed_escape_sequence(
122 position,
123 "invalid unicode value",
124 ));
125 }
126
127 value.push(char::from_u32(number).expect("unable to convert u32 to char"));
128 }
129 'z' => {
130 while chars
131 .peek()
132 .filter(|(_, char)| char.is_ascii_whitespace())
133 .is_some()
134 {
135 chars.next();
136 }
137 }
138 _ => {
139 value.push(next_char);
141 }
142 }
143 } else {
144 return Err(StringError::malformed_escape_sequence(
145 position,
146 "string ended after '\\'",
147 ));
148 }
149 } else {
150 value.push(char);
151 }
152 }
153
154 value.shrink_to_fit();
155
156 Ok(value)
157}
158
159fn read_number(
160 chars: &mut Peekable<CharIndices>,
161 first_digit: Option<char>,
162 radix: u32,
163 max: usize,
164) -> u32 {
165 let filter = match radix {
166 10 => char::is_ascii_digit,
167 16 => char::is_ascii_hexdigit,
168 _ => panic!("unsupported radix {}", radix),
169 };
170 let mut amount = first_digit
171 .map(|char| char.to_digit(radix).unwrap())
172 .unwrap_or(0);
173 let mut iteration_count: usize = first_digit.is_some().into();
174
175 while let Some(next_digit) = chars.peek().map(|(_, c)| *c).filter(filter) {
176 chars.next();
177
178 amount = amount * radix + next_digit.to_digit(radix).unwrap();
179 iteration_count += 1;
180
181 if iteration_count >= max {
182 break;
183 }
184 }
185
186 amount
187}
188
189#[inline]
190fn contains<T, U>(option: &Option<T>, x: &U) -> bool
191where
192 U: PartialEq<T>,
193{
194 match option {
195 Some(y) => x == y,
196 None => false,
197 }
198}