use std::ops::{Index, RangeFrom};
use proc_macro::Span;
use crate::Error;
pub(crate) fn parse(token: &proc_macro::Literal) -> Result<(Span, Vec<u8>), Error> {
let span = token.span();
let repr = token.to_string();
match repr.as_bytes() {
[b'"', ..] => Ok((span, parse_lit_str_cooked(&repr[1..]))),
[b'b', b'"', rest @ ..] => Ok((span, parse_lit_byte_str_cooked(rest))),
[b'r', rest @ ..] | [b'b', b'r', rest @ ..] => Ok((span, parse_lit_str_raw(rest))),
_ => Err(Error::ExpectedString {
span_start: Some(span),
span_end: Some(span),
}),
}
}
fn byte(s: impl AsRef<[u8]>, idx: usize) -> u8 {
s.as_ref().get(idx).copied().unwrap_or_default()
}
fn parse_lit_str_cooked(mut s: &str) -> Vec<u8> {
let mut content = String::new();
'outer: loop {
let ch = match byte(s, 0) {
b'"' => break,
b'\\' => {
let b = byte(s, 1);
s = &s[2..];
match b {
b'x' => {
let (byte, rest) = backslash_x(s);
s = rest;
char::from_u32(u32::from(byte)).expect("byte was just validated")
}
b'u' => {
let (chr, rest) = backslash_u(s);
s = rest;
chr
}
b'n' => '\n',
b'r' => '\r',
b't' => '\t',
b'\\' => '\\',
b'0' => '\0',
b'\'' => '\'',
b'"' => '"',
b'\r' | b'\n' => loop {
let ch = s.chars().next().unwrap_or_default();
if ch.is_whitespace() {
s = &s[ch.len_utf8()..];
} else {
continue 'outer;
}
},
_ => bug!("invalid escape"),
}
}
b'\r' => {
s = &s[2..];
'\n'
}
_ => {
let ch = s.chars().next().unwrap_or_default();
s = &s[ch.len_utf8()..];
ch
}
};
content.push(ch);
}
content.into_bytes()
}
fn parse_lit_str_raw(s: &[u8]) -> Vec<u8> {
let mut pounds = 0;
while byte(s, pounds) == b'#' {
pounds += 1;
}
let close = s
.iter()
.rposition(|&b| b == b'"')
.expect("had a string without trailing \"");
s[pounds + 1..close].to_owned()
}
fn parse_lit_byte_str_cooked(mut v: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
'outer: loop {
let byte = match byte(v, 0) {
b'"' => break,
b'\\' => {
let b = byte(v, 1);
v = &v[2..];
match b {
b'x' => {
let (byte, rest) = backslash_x(v);
v = rest;
byte
}
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'\\' => b'\\',
b'0' => b'\0',
b'\'' => b'\'',
b'"' => b'"',
b'\r' | b'\n' => loop {
let byte = byte(v, 0);
let ch = char::from_u32(u32::from(byte)).expect("invalid byte");
if ch.is_whitespace() {
v = &v[1..];
} else {
continue 'outer;
}
},
_ => bug!("invalid escape"),
}
}
b'\r' => {
v = &v[2..];
b'\n'
}
b => {
v = &v[1..];
b
}
};
out.push(byte);
}
out
}
fn backslash_x<S>(s: &S) -> (u8, &S)
where
S: Index<RangeFrom<usize>, Output = S> + AsRef<[u8]> + ?Sized,
{
let mut ch = 0;
let b0 = byte(s, 0);
let b1 = byte(s, 1);
ch += 0x10 * (b0 - b'0');
ch += match b1 {
b'0'..=b'9' => b1 - b'0',
b'a'..=b'f' => 10 + (b1 - b'a'),
b'A'..=b'F' => 10 + (b1 - b'A'),
_ => bug!("invalid hex escape"),
};
(ch, &s[2..])
}
fn backslash_u(mut s: &str) -> (char, &str) {
s = &s[1..];
let mut ch = 0;
let mut digits = 0;
loop {
let b = byte(s, 0);
let digit = match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => 10 + b - b'a',
b'A'..=b'F' => 10 + b - b'A',
b'_' if digits > 0 => {
s = &s[1..];
continue;
}
b'}' if digits != 0 => break,
_ => bug!("invalid unicode escape"),
};
ch *= 0x10;
ch += u32::from(digit);
digits += 1;
s = &s[1..];
}
s = &s[1..];
(
char::from_u32(ch).expect("invalid unicode escape passed by compiler"),
s,
)
}