figment/value/
escape.rs

1// This file is a partial, reduced, extended, and modified reproduction of
2// `toml-rs`, Copyright (c) 2014 Alex Crichton, The MIT License, reproduced and
3// relicensed, under the rights granted by The MIT License, under The MIT
4// License or Apache License, Version 2.0, January 2004, at the user's
5// discretion, Copyright (c) 2020 Sergio Benitez.
6//
7// See README.md, LICENSE-MIT, LICENSE-APACHE.
8
9use core::fmt;
10use std::borrow::Cow;
11
12#[derive(Eq, PartialEq, Debug)]
13pub enum Error {
14    InvalidCharInString(usize, char),
15    InvalidEscape(usize, char),
16    InvalidHexEscape(usize, char),
17    InvalidEscapeValue(usize, u32),
18    UnterminatedString(usize),
19}
20
21pub fn escape(string: &str) -> Result<Cow<'_, str>, Error> {
22    let mut chars = string.chars().enumerate();
23    let mut output = Cow::from(string);
24    while let Some((i, ch)) = chars.next() {
25        match ch {
26            '\\' => {
27                if let Cow::Borrowed(_) = output {
28                    output = Cow::Owned(string[..i].into());
29                }
30
31                let val = output.to_mut();
32                match chars.next() {
33                    Some((_, '"')) => val.push('"'),
34                    Some((_, '\\')) => val.push('\\'),
35                    Some((_, 'b')) => val.push('\u{8}'),
36                    Some((_, 'f')) => val.push('\u{c}'),
37                    Some((_, 'n')) => val.push('\n'),
38                    Some((_, 'r')) => val.push('\r'),
39                    Some((_, 't')) => val.push('\t'),
40                    Some((i, c @ 'u')) | Some((i, c @ 'U')) => {
41                        let len = if c == 'u' { 4 } else { 8 };
42                        val.push(hex(&mut chars, i, len)?);
43                    }
44                    Some((i, c)) => return Err(Error::InvalidEscape(i, c)),
45                    None => return Err(Error::UnterminatedString(0)),
46                }
47            },
48            ch if ch == '\u{09}' || ('\u{20}' <= ch && ch <= '\u{10ffff}' && ch != '\u{7f}') => {
49                // if we haven't allocated, the string contains the value
50                if let Cow::Owned(ref mut val) = output {
51                    val.push(ch);
52                }
53            },
54            _ => return Err(Error::InvalidCharInString(i, ch)),
55        }
56    }
57
58    Ok(output)
59}
60
61fn hex<I>(mut chars: I, i: usize, len: usize) -> Result<char, Error>
62    where I: Iterator<Item = (usize, char)>
63{
64    let mut buf = String::with_capacity(len);
65    for _ in 0..len {
66        match chars.next() {
67            Some((_, ch)) if ch as u32 <= 0x7F && ch.is_digit(16) => buf.push(ch),
68            Some((i, ch)) => return Err(Error::InvalidHexEscape(i, ch)),
69            None => return Err(Error::UnterminatedString(0)),
70        }
71    }
72
73    let val = u32::from_str_radix(&buf, 16).unwrap();
74    match std::char::from_u32(val) {
75        Some(ch) => Ok(ch),
76        None => Err(Error::InvalidEscapeValue(i, val)),
77    }
78}
79
80impl fmt::Display for Error {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match self {
83            Error::InvalidCharInString(_, ch) => write!(f, "invalid char `{:?}`", ch),
84            Error::InvalidEscape(_, ch) => write!(f, "invalid escape `\\{:?}`", ch),
85            Error::InvalidHexEscape(_, ch) => write!(f, "invalid hex escape `{:?}`", ch),
86            Error::InvalidEscapeValue(_, ch) => write!(f, "invalid escaped value `{:?}`", ch),
87            Error::UnterminatedString(_) => write!(f, "unterminated"),
88        }
89    }
90}