187 lines
5.4 KiB
Rust
187 lines
5.4 KiB
Rust
// `bytes`, `next_chr`, `parse_lit_str`, `parse_lit_str_cooked` and `parse_lit_str_raw` are adapted
|
|
// from syn:
|
|
// https://github.com/dtolnay/syn/blob/362ee2d02df3f1b2e74c7b7a4cf2ed3c106404c9/src/lit.rs#L1062-L1167
|
|
// and
|
|
// https://github.com/dtolnay/syn/blob/362ee2d02df3f1b2e74c7b7a4cf2ed3c106404c9/src/lit.rs#L1327-L1388
|
|
|
|
/// Get the byte at offset idx, or a default of `b'\0'` if we're looking
|
|
/// past the end of the input buffer.
|
|
fn byte(s: &str, idx: usize) -> u8 {
|
|
if idx < s.len() {
|
|
s.as_bytes()[idx]
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
fn next_chr(s: &str) -> char {
|
|
s.chars().next().unwrap_or('\0')
|
|
}
|
|
|
|
// Returns (content, suffix).
|
|
fn parse_lit_str(s: &str) -> String {
|
|
match byte(s, 0) {
|
|
b'"' => parse_lit_str_cooked(s),
|
|
b'r' => parse_lit_str_raw(s),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
// Clippy false positive
|
|
// https://github.com/rust-lang-nursery/rust-clippy/issues/2329
|
|
#[allow(clippy::needless_continue)]
|
|
fn parse_lit_str_cooked(mut s: &str) -> String {
|
|
assert_eq!(byte(s, 0), b'"');
|
|
s = &s[1..];
|
|
|
|
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;
|
|
assert!(byte <= 0x80, "Invalid \\x byte in string literal");
|
|
char::from_u32(u32::from(byte)).unwrap()
|
|
}
|
|
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 = next_chr(s);
|
|
if ch.is_whitespace() {
|
|
s = &s[ch.len_utf8()..];
|
|
} else {
|
|
continue 'outer;
|
|
}
|
|
},
|
|
b => panic!("unexpected byte {b:?} after \\ character in byte literal"),
|
|
}
|
|
}
|
|
b'\r' => {
|
|
assert_eq!(byte(s, 1), b'\n', "Bare CR not allowed in string");
|
|
s = &s[2..];
|
|
'\n'
|
|
}
|
|
_ => {
|
|
let ch = next_chr(s);
|
|
s = &s[ch.len_utf8()..];
|
|
ch
|
|
}
|
|
};
|
|
content.push(ch);
|
|
}
|
|
|
|
assert!(s.starts_with('"'));
|
|
content
|
|
}
|
|
|
|
fn parse_lit_str_raw(mut s: &str) -> String {
|
|
assert_eq!(byte(s, 0), b'r');
|
|
s = &s[1..];
|
|
|
|
let mut pounds = 0;
|
|
while byte(s, pounds) == b'#' {
|
|
pounds += 1;
|
|
}
|
|
assert_eq!(byte(s, pounds), b'"');
|
|
let close = s.rfind('"').unwrap();
|
|
for end in s[close + 1..close + 1 + pounds].bytes() {
|
|
assert_eq!(end, b'#');
|
|
}
|
|
|
|
s[pounds + 1..close].to_owned()
|
|
}
|
|
|
|
fn backslash_x(s: &str) -> (u8, &str) {
|
|
let mut ch = 0;
|
|
let b0 = byte(s, 0);
|
|
let b1 = byte(s, 1);
|
|
ch += 0x10
|
|
* match b0 {
|
|
b'0'..=b'9' => b0 - b'0',
|
|
b'a'..=b'f' => 10 + (b0 - b'a'),
|
|
b'A'..=b'F' => 10 + (b0 - b'A'),
|
|
_ => panic!("unexpected non-hex character after \\x"),
|
|
};
|
|
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'),
|
|
_ => panic!("unexpected non-hex character after \\x"),
|
|
};
|
|
(ch, &s[2..])
|
|
}
|
|
|
|
fn backslash_u(mut s: &str) -> (char, &str) {
|
|
if byte(s, 0) != b'{' {
|
|
panic!("{}", "expected { after \\u");
|
|
}
|
|
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 => panic!("invalid empty unicode escape"),
|
|
b'}' => break,
|
|
_ => panic!("unexpected non-hex character after \\u"),
|
|
};
|
|
if digits == 6 {
|
|
panic!("overlong unicode escape (must have at most 6 hex digits)");
|
|
}
|
|
ch *= 0x10;
|
|
ch += u32::from(digit);
|
|
digits += 1;
|
|
s = &s[1..];
|
|
}
|
|
assert!(byte(s, 0) == b'}');
|
|
s = &s[1..];
|
|
|
|
if let Some(ch) = char::from_u32(ch) {
|
|
(ch, s)
|
|
} else {
|
|
panic!("character code {ch:x} is not a valid unicode character");
|
|
}
|
|
}
|
|
|
|
// End of code adapted from syn
|
|
|
|
pub fn parse_lit_str_token(mut stream: proc_macro::TokenStream) -> String {
|
|
loop {
|
|
let mut iter = stream.into_iter();
|
|
let token = iter.next().expect("expected string argument");
|
|
assert!(iter.next().is_none(), "unexpected trailing token");
|
|
let literal = match token {
|
|
proc_macro::TokenTree::Literal(literal) => literal,
|
|
proc_macro::TokenTree::Group(group) => {
|
|
stream = group.stream();
|
|
continue;
|
|
}
|
|
_ => panic!("expected string argument found `{token:?}`"),
|
|
};
|
|
return parse_lit_str(&literal.to_string());
|
|
}
|
|
}
|