Initial import of litrs-0.2.3

Bug: 255384162
Test: n/a
Change-Id: I66ab38c4a632133836eb95d2786e6f305f2b46dd
diff --git a/src/escape.rs b/src/escape.rs
new file mode 100644
index 0000000..3e93113
--- /dev/null
+++ b/src/escape.rs
@@ -0,0 +1,255 @@
+use crate::{ParseError, err::{perr, ParseErrorKind::*}, parse::hex_digit_value};
+
+
+/// Must start with `\`
+pub(crate) fn unescape<E: Escapee>(input: &str, offset: usize) -> Result<(E, usize), ParseError> {
+    let first = input.as_bytes().get(1)
+        .ok_or(perr(offset, UnterminatedEscape))?;
+    let out = match first {
+        // Quote escapes
+        b'\'' => (E::from_byte(b'\''), 2),
+        b'"' => (E::from_byte(b'"'), 2),
+
+        // Ascii escapes
+        b'n' => (E::from_byte(b'\n'), 2),
+        b'r' => (E::from_byte(b'\r'), 2),
+        b't' => (E::from_byte(b'\t'), 2),
+        b'\\' => (E::from_byte(b'\\'), 2),
+        b'0' => (E::from_byte(b'\0'), 2),
+        b'x' => {
+            let hex_string = input.get(2..4)
+                .ok_or(perr(offset..offset + input.len(), UnterminatedEscape))?
+                .as_bytes();
+            let first = hex_digit_value(hex_string[0])
+                .ok_or(perr(offset..offset + 4, InvalidXEscape))?;
+            let second = hex_digit_value(hex_string[1])
+                .ok_or(perr(offset..offset + 4, InvalidXEscape))?;
+            let value = second + 16 * first;
+
+            if E::SUPPORTS_UNICODE && value > 0x7F {
+                return Err(perr(offset..offset + 4, NonAsciiXEscape));
+            }
+
+            (E::from_byte(value), 4)
+        },
+
+        // Unicode escape
+        b'u' => {
+            if !E::SUPPORTS_UNICODE {
+                return Err(perr(offset..offset + 2, UnicodeEscapeInByteLiteral));
+            }
+
+            if input.as_bytes().get(2) != Some(&b'{') {
+                return Err(perr(offset..offset + 2, UnicodeEscapeWithoutBrace));
+            }
+
+            let closing_pos = input.bytes().position(|b| b == b'}')
+                .ok_or(perr(offset..offset + input.len(), UnterminatedUnicodeEscape))?;
+
+            let inner = &input[3..closing_pos];
+            if inner.as_bytes().first() == Some(&b'_') {
+                return Err(perr(4, InvalidStartOfUnicodeEscape));
+            }
+
+            let mut v: u32 = 0;
+            let mut digit_count = 0;
+            for (i, b) in inner.bytes().enumerate() {
+                if b == b'_'{
+                    continue;
+                }
+
+                let digit = hex_digit_value(b)
+                    .ok_or(perr(offset + 3 + i, NonHexDigitInUnicodeEscape))?;
+
+                if digit_count == 6 {
+                    return Err(perr(offset + 3 + i, TooManyDigitInUnicodeEscape));
+                }
+                digit_count += 1;
+                v = 16 * v + digit as u32;
+            }
+
+            let c = std::char::from_u32(v)
+                .ok_or(perr(offset..closing_pos + 1, InvalidUnicodeEscapeChar))?;
+
+            (E::from_char(c), closing_pos + 1)
+        }
+
+        _ => return Err(perr(offset..offset + 2, UnknownEscape)),
+    };
+
+    Ok(out)
+}
+
+pub(crate) trait Escapee: Into<char> {
+    const SUPPORTS_UNICODE: bool;
+    fn from_byte(b: u8) -> Self;
+    fn from_char(c: char) -> Self;
+}
+
+impl Escapee for u8 {
+    const SUPPORTS_UNICODE: bool = false;
+    fn from_byte(b: u8) -> Self {
+        b
+    }
+    fn from_char(_: char) -> Self {
+        panic!("bug: `<u8 as Escapee>::from_char` was called");
+    }
+}
+
+impl Escapee for char {
+    const SUPPORTS_UNICODE: bool = true;
+    fn from_byte(b: u8) -> Self {
+        b.into()
+    }
+    fn from_char(c: char) -> Self {
+        c
+    }
+}
+
+/// Checks whether the character is skipped after a string continue start
+/// (unescaped backlash followed by `\n`).
+pub(crate) fn is_string_continue_skipable_whitespace(b: u8) -> bool {
+    b == b' ' || b == b'\t' || b == b'\n' || b == b'\r'
+}
+
+/// Unescapes a whole string or byte string.
+pub(crate) fn unescape_string<E: Escapee>(
+    input: &str,
+    offset: usize,
+) -> Result<Option<String>, ParseError> {
+    let mut i = offset;
+    let mut end_last_escape = offset;
+    let mut value = String::new();
+    while i < input.len() - 1 {
+        match input.as_bytes()[i] {
+            // Handle "string continue".
+            b'\\' if input.as_bytes()[i + 1] == b'\n' => {
+                value.push_str(&input[end_last_escape..i]);
+
+                // Find the first non-whitespace character.
+                let end_escape = input[i + 2..].bytes()
+                    .position(|b| !is_string_continue_skipable_whitespace(b))
+                    .ok_or(perr(None, UnterminatedString))?;
+
+                i += 2 + end_escape;
+                end_last_escape = i;
+            }
+            b'\\' => {
+                let (c, len) = unescape::<E>(&input[i..input.len() - 1], i)?;
+                value.push_str(&input[end_last_escape..i]);
+                value.push(c.into());
+                i += len;
+                end_last_escape = i;
+            }
+            b'\r' => {
+                if input.as_bytes()[i + 1] == b'\n' {
+                    value.push_str(&input[end_last_escape..i]);
+                    value.push('\n');
+                    i += 2;
+                    end_last_escape = i;
+                } else {
+                    return Err(perr(i, IsolatedCr))
+                }
+            }
+            b'"' => return Err(perr(i + 1..input.len(), UnexpectedChar)),
+            b if !E::SUPPORTS_UNICODE && !b.is_ascii()
+                => return Err(perr(i, NonAsciiInByteLiteral)),
+            _ => i += 1,
+        }
+    }
+
+    if input.as_bytes()[input.len() - 1] != b'"' || input.len() == offset {
+        return Err(perr(None, UnterminatedString));
+    }
+
+    // `value` is only empty if there was no escape in the input string
+    // (with the special case of the input being empty). This means the
+    // string value basically equals the input, so we store `None`.
+    let value = if value.is_empty() {
+        None
+    } else {
+        // There was an escape in the string, so we need to push the
+        // remaining unescaped part of the string still.
+        value.push_str(&input[end_last_escape..input.len() - 1]);
+        Some(value)
+    };
+
+    Ok(value)
+}
+
+/// Reads and checks a raw (byte) string literal, converting `\r\n` sequences to
+/// just `\n` sequences. Returns an optional new string (if the input contained
+/// any `\r\n`) and the number of hashes used by the literal.
+pub(crate) fn scan_raw_string<E: Escapee>(
+    input: &str,
+    offset: usize,
+) -> Result<(Option<String>, u32), ParseError> {
+    // Raw string literal
+    let num_hashes = input[offset..].bytes().position(|b| b != b'#')
+        .ok_or(perr(None, InvalidLiteral))?;
+
+    if input.as_bytes().get(offset + num_hashes) != Some(&b'"') {
+        return Err(perr(None, InvalidLiteral));
+    }
+    let start_inner = offset + num_hashes + 1;
+    let hashes = &input[offset..num_hashes + offset];
+
+    let mut closing_quote_pos = None;
+    let mut i = start_inner;
+    let mut end_last_escape = start_inner;
+    let mut value = String::new();
+    while i < input.len() {
+        let b = input.as_bytes()[i];
+        if b == b'"' && input[i + 1..].starts_with(hashes) {
+            closing_quote_pos = Some(i);
+            break;
+        }
+
+        if b == b'\r' {
+            // Convert `\r\n` into `\n`. This is currently not well documented
+            // in the Rust reference, but is done even for raw strings. That's
+            // because rustc simply converts all line endings when reading
+            // source files.
+            if input.as_bytes().get(i + 1) == Some(&b'\n') {
+                value.push_str(&input[end_last_escape..i]);
+                value.push('\n');
+                i += 2;
+                end_last_escape = i;
+                continue;
+            } else if E::SUPPORTS_UNICODE {
+                // If no \n follows the \r and we are scanning a raw string
+                // (not raw byte string), we error.
+                return Err(perr(i, IsolatedCr))
+            }
+        }
+
+        if !E::SUPPORTS_UNICODE {
+            if !b.is_ascii() {
+                return Err(perr(i, NonAsciiInByteLiteral));
+            }
+        }
+
+        i += 1;
+    }
+
+    let closing_quote_pos = closing_quote_pos
+        .ok_or(perr(None, UnterminatedRawString))?;
+
+    if closing_quote_pos + num_hashes != input.len() - 1 {
+        return Err(perr(closing_quote_pos + num_hashes + 1..input.len(), UnexpectedChar));
+    }
+
+    // `value` is only empty if there was no \r\n in the input string (with the
+    // special case of the input being empty). This means the string value
+    // equals the input, so we store `None`.
+    let value = if value.is_empty() {
+        None
+    } else {
+        // There was an \r\n in the string, so we need to push the remaining
+        // unescaped part of the string still.
+        value.push_str(&input[end_last_escape..closing_quote_pos]);
+        Some(value)
+    };
+
+    Ok((value, num_hashes as u32))
+}