1#![deny(
16 rust_2018_idioms,
17 trivial_casts,
18 trivial_numeric_casts,
19 unreachable_pub,
20 unused_must_use,
21 unused_qualifications
22)]
23#![forbid(unsafe_code)]
24#![no_std]
25
26extern crate alloc;
27
28use alloc::string::{String, ToString as _};
29use alloc::vec::Vec;
30use core::convert::TryFrom as _;
31use core::fmt::Write as _;
32
33#[cfg(test)]
34mod tests;
35
36const _: bool = matches!(0, 0..);
42
43pub fn encode_bool(value: bool) -> &'static str {
49 if value {
50 "true"
51 } else {
52 "false"
53 }
54}
55
56#[inline]
58pub fn encode_i8(value: i8) -> String {
59 value.to_string()
60}
61
62#[inline]
64pub fn encode_i16(value: i16) -> String {
65 value.to_string()
66}
67
68#[inline]
70pub fn encode_i32(value: i32) -> String {
71 value.to_string()
72}
73
74#[inline]
76pub fn encode_i64(value: i64) -> String {
77 value.to_string()
78}
79
80#[inline]
82pub fn encode_i128(value: i128) -> String {
83 value.to_string()
84}
85
86#[inline]
88pub fn encode_u8(value: u8) -> String {
89 value.to_string()
90}
91
92#[inline]
94pub fn encode_u16(value: u16) -> String {
95 value.to_string()
96}
97
98#[inline]
100pub fn encode_u32(value: u32) -> String {
101 value.to_string()
102}
103
104#[inline]
106pub fn encode_u64(value: u64) -> String {
107 value.to_string()
108}
109
110#[inline]
112pub fn encode_u128(value: u128) -> String {
113 value.to_string()
114}
115
116fn reformat_float(s: String) -> String {
117 if s == "NaN" || s == "inf" || s == "-inf" {
118 return s;
119 }
120
121 const ZEROS_THRESHOLD: u32 = 9;
122
123 let mut result = String::new();
124 let s = if let Some(remaining) = s.strip_prefix('-') {
125 result.push('-');
126 remaining
127 } else {
128 s.as_str()
129 };
130
131 if let Some(mut remaining) = s.strip_prefix("0.") {
132 let mut exp_abs: u32 = 1;
133 while let Some(new_remaining) = remaining.strip_prefix('0') {
134 remaining = new_remaining;
135 exp_abs += 1;
136 }
137 if exp_abs > ZEROS_THRESHOLD {
138 result.push_str(&remaining[..1]);
139 if remaining.len() > 1 {
140 result.push('.');
141 result.push_str(&remaining[1..]);
142 } else {
143 result.push_str(".0");
144 }
145 result.push_str("e-");
146 write!(result, "{}", exp_abs).unwrap();
147 } else {
148 result.push_str(s);
149 }
150 } else {
151 let s = s.strip_suffix(".0").unwrap_or(s);
152 if s.contains('.') {
153 result.push_str(s);
154 } else {
155 let mut remaining = s;
156 let mut num_zeros: u32 = 0;
157 while let Some(new_remaining) = remaining.strip_suffix('0') {
158 remaining = new_remaining;
159 num_zeros += 1;
160 }
161 if num_zeros > ZEROS_THRESHOLD {
162 result.push_str(&remaining[..1]);
163 if remaining.len() > 1 {
164 result.push('.');
165 result.push_str(&remaining[1..]);
166 } else {
167 result.push_str(".0");
168 }
169 result.push('e');
170 write!(result, "{}", num_zeros + (remaining.len() as u32 - 1)).unwrap();
171 } else {
172 result.push_str(s);
173 result.push_str(".0");
174 }
175 }
176 }
177
178 result
179}
180
181#[inline]
183pub fn encode_f32(value: f32) -> String {
184 reformat_float(value.to_string())
185}
186
187#[inline]
189pub fn encode_f64(value: f64) -> String {
190 reformat_float(value.to_string())
191}
192
193#[inline]
194fn nibble_to_hex(nibble: u8) -> char {
195 if nibble < 10 {
196 char::from(b'0' + nibble)
197 } else {
198 char::from(b'a' + nibble - 10)
199 }
200}
201
202pub fn encode_byte_string(string: &[u8]) -> String {
203 let mut result = String::with_capacity(string.len() + 2);
204 result.push('"');
205 for &chr in string {
206 #[allow(clippy::match_overlapping_arm)]
207 match chr {
208 b'\0' => result.push_str("\\0"),
209 b'\t' => result.push_str("\\t"),
210 b'\n' => result.push_str("\\n"),
211 b'\r' => result.push_str("\\r"),
212 b'"' => result.push_str("\\\""),
213 b'\\' => result.push_str("\\\\"),
214 0x20..=0x7E => result.push(char::from(chr)),
215 _ => {
216 result.push_str("\\x");
217 result.push(nibble_to_hex(chr >> 4));
218 result.push(nibble_to_hex(chr & 0xF));
219 }
220 }
221 }
222 result.push('"');
223 result
224}
225
226pub fn encode_ascii_string(string: &str) -> String {
227 let mut result = String::with_capacity(string.len() + 2);
228 result.push('"');
229 for &chr in string.as_bytes() {
230 #[allow(clippy::match_overlapping_arm)]
231 match chr {
232 b'\0' => result.push_str("\\0"),
233 b'\t' => result.push_str("\\t"),
234 b'\n' => result.push_str("\\n"),
235 b'\r' => result.push_str("\\r"),
236 b'"' => result.push_str("\\\""),
237 b'\\' => result.push_str("\\\\"),
238 0x20..=0x7E => result.push(char::from(chr)),
239 _ => {
240 assert!(chr <= 0x7F, "Invalid ASCII character");
241 result.push_str("\\x");
242 result.push(nibble_to_hex(chr >> 4));
243 result.push(nibble_to_hex(chr & 0xF));
244 }
245 }
246 }
247 result.push('"');
248 result
249}
250
251pub fn encode_utf8_string(string: &str) -> String {
252 let mut result = String::with_capacity(string.len() + 2);
253 result.push('"');
254 for chr in string.chars() {
255 match chr {
256 '\0' => result.push_str("\\0"),
257 '\t' => result.push_str("\\t"),
258 '\n' => result.push_str("\\n"),
259 '\r' => result.push_str("\\r"),
260 '"' => result.push_str("\\\""),
261 '\\' => result.push_str("\\\\"),
262 '\x20'..='\x7E' => result.push(chr),
263 _ => {
264 result.push_str("\\u{");
265 let code_beginning = result.len();
266 let mut remaining_digits = u32::from(chr);
267 loop {
268 result.insert(
269 code_beginning,
270 nibble_to_hex((remaining_digits & 0xF) as u8),
271 );
272 remaining_digits >>= 4;
273 if remaining_digits == 0 {
274 break;
275 }
276 }
277 result.push('}');
278 }
279 }
280 }
281 result.push('"');
282 result
283}
284
285#[inline]
291pub fn decode_bool(atom: &str) -> Option<bool> {
292 match atom {
293 "false" => Some(false),
294 "true" => Some(true),
295 _ => None,
296 }
297}
298
299#[inline]
301pub fn decode_i8(atom: &str) -> Option<i8> {
302 atom.parse().ok()
303}
304
305#[inline]
307pub fn decode_i16(atom: &str) -> Option<i16> {
308 atom.parse().ok()
309}
310
311#[inline]
313pub fn decode_i32(atom: &str) -> Option<i32> {
314 atom.parse().ok()
315}
316
317#[inline]
319pub fn decode_i64(atom: &str) -> Option<i64> {
320 atom.parse().ok()
321}
322
323#[inline]
325pub fn decode_i128(atom: &str) -> Option<i128> {
326 atom.parse().ok()
327}
328
329#[inline]
331pub fn decode_u8(atom: &str) -> Option<u8> {
332 atom.parse().ok()
333}
334
335#[inline]
337pub fn decode_u16(atom: &str) -> Option<u16> {
338 atom.parse().ok()
339}
340
341#[inline]
343pub fn decode_u32(atom: &str) -> Option<u32> {
344 atom.parse().ok()
345}
346
347#[inline]
349pub fn decode_u64(atom: &str) -> Option<u64> {
350 atom.parse().ok()
351}
352
353#[inline]
355pub fn decode_u128(atom: &str) -> Option<u128> {
356 atom.parse().ok()
357}
358
359#[inline]
361pub fn decode_f32(atom: &str) -> Option<f32> {
362 if atom == "+NaN" || atom == "-NaN" {
363 None
364 } else {
365 atom.parse().ok()
366 }
367}
368
369#[inline]
371pub fn decode_f64(atom: &str) -> Option<f64> {
372 if atom == "+NaN" || atom == "-NaN" {
373 None
374 } else {
375 atom.parse().ok()
376 }
377}
378
379#[inline]
380fn hex_digit_byte_to_u8(chr: u8) -> Option<u8> {
381 match chr {
382 b'0'..=b'9' => Some(chr - b'0'),
383 b'A'..=b'F' => Some(chr - b'A' + 10),
384 b'a'..=b'f' => Some(chr - b'a' + 10),
385 _ => None,
386 }
387}
388
389#[inline]
390fn hex_digit_char_to_u8(chr: char) -> Option<u8> {
391 match chr {
392 '0'..='9' => Some(chr as u8 - b'0'),
393 'A'..='F' => Some(chr as u8 - b'A' + 10),
394 'a'..='f' => Some(chr as u8 - b'a' + 10),
395 _ => None,
396 }
397}
398
399pub fn decode_byte_string(atom: &str) -> Option<Vec<u8>> {
400 let mut iter = atom.bytes();
401 if iter.next() != Some(b'"') {
402 return None;
403 }
404
405 let mut string = Vec::new();
406 loop {
407 match iter.next() {
408 None => return None,
409 Some(b'"') => {
410 if iter.next().is_some() {
411 return None;
412 }
413 break;
414 }
415 Some(b'\\') => match iter.next() {
416 Some(b'0') => string.push(b'\0'),
417 Some(b't') => string.push(b'\t'),
418 Some(b'n') => string.push(b'\n'),
419 Some(b'r') => string.push(b'\r'),
420 Some(b'"') => string.push(b'\"'),
421 Some(b'\\') => string.push(b'\\'),
422 Some(b'x') => {
423 let hex1 = hex_digit_byte_to_u8(iter.next()?)?;
424 let hex2 = hex_digit_byte_to_u8(iter.next()?)?;
425 string.push((hex1 << 4) | hex2);
426 }
427 Some(_) | None => return None,
428 },
429 Some(byte) => string.push(byte),
430 }
431 }
432
433 Some(string)
434}
435
436pub fn decode_ascii_string(atom: &str) -> Option<String> {
437 let mut iter = atom.bytes();
438 if iter.next() != Some(b'"') {
439 return None;
440 }
441
442 let mut string = String::new();
443 loop {
444 match iter.next() {
445 None => return None,
446 Some(b'"') => {
447 if iter.next().is_some() {
448 return None;
449 }
450 break;
451 }
452 Some(b'\\') => match iter.next() {
453 Some(b'0') => string.push('\0'),
454 Some(b't') => string.push('\t'),
455 Some(b'n') => string.push('\n'),
456 Some(b'r') => string.push('\r'),
457 Some(b'"') => string.push('\"'),
458 Some(b'\\') => string.push('\\'),
459 Some(b'x') => {
460 let hex1 = hex_digit_byte_to_u8(iter.next()?)?;
461 let hex2 = hex_digit_byte_to_u8(iter.next()?)?;
462 let chr = (hex1 << 4) | hex2;
463 if chr > 0x7F {
464 return None;
465 }
466 string.push(char::from(chr));
467 }
468 Some(_) | None => return None,
469 },
470 Some(byte @ b'\x00'..=b'\x7F') => string.push(char::from(byte)),
471 Some(_) => return None,
472 }
473 }
474
475 Some(string)
476}
477
478pub fn decode_utf8_string(atom: &str) -> Option<String> {
479 let mut iter = atom.chars();
480 if iter.next() != Some('"') {
481 return None;
482 }
483
484 let mut string = String::new();
485 loop {
486 match iter.next() {
487 None => return None,
488 Some('"') => {
489 if iter.next().is_some() {
490 return None;
491 }
492 break;
493 }
494 Some('\\') => match iter.next() {
495 Some('0') => string.push('\0'),
496 Some('t') => string.push('\t'),
497 Some('n') => string.push('\n'),
498 Some('r') => string.push('\r'),
499 Some('"') => string.push('\"'),
500 Some('\\') => string.push('\\'),
501 Some('x') => {
502 let hex1 = hex_digit_char_to_u8(iter.next()?)?;
503 let hex2 = hex_digit_char_to_u8(iter.next()?)?;
504 let chr = (hex1 << 4) | hex2;
505 if chr > 0x7F {
506 return None;
507 }
508 string.push(char::from(chr));
509 }
510 Some('u') => {
511 if iter.next() != Some('{') {
512 return None;
513 }
514 let mut num_digits = 0;
515 let mut esc_chr: u32 = 0;
516 loop {
517 match iter.next() {
518 Some('}') => {
519 if num_digits == 0 {
520 return None;
521 } else {
522 break;
523 }
524 }
525 Some(chr) => {
526 if num_digits == 6 {
527 return None;
528 }
529 esc_chr <<= 4;
530 esc_chr |= u32::from(hex_digit_char_to_u8(chr)?);
531 num_digits += 1;
532 }
533 None => return None,
534 }
535 }
536 string.push(char::try_from(esc_chr).ok()?);
537 }
538 Some(_) | None => return None,
539 },
540 Some(chr) => string.push(chr),
541 }
542 }
543
544 Some(string)
545}