Skip to main content

time_macros/helpers/
string.rs

1use std::ops::{Index, RangeFrom};
2
3use proc_macro::Span;
4
5use crate::Error;
6
7pub(crate) fn parse(
8    permit_byte_strings: bool,
9    token: &proc_macro::Literal,
10) -> Result<(Span, Vec<u8>), Error> {
11    let span = token.span();
12    let repr = token.to_string();
13
14    match repr.as_bytes() {
15        [b'"', ..] => Ok((span, parse_lit_str_cooked(&repr[1..]))),
16        [b'b', b'"', ..] if !permit_byte_strings => Err(Error::ByteStringNotPermitted {
17            span_start: Some(span),
18            span_end: Some(span),
19        }),
20        [b'b', b'"', rest @ ..] => Ok((span, parse_lit_byte_str_cooked(rest))),
21        [b'r', rest @ ..] => Ok((span, parse_lit_str_raw(rest))),
22        [b'b', b'r', ..] if !permit_byte_strings => Err(Error::ByteStringNotPermitted {
23            span_start: Some(span),
24            span_end: Some(span),
25        }),
26        [b'b', b'r', rest @ ..] => Ok((span, parse_lit_str_raw(rest))),
27        _ => Err(Error::ExpectedString {
28            span_start: Some(span),
29            span_end: Some(span),
30        }),
31    }
32}
33
34fn byte(s: impl AsRef<[u8]>, idx: usize) -> u8 {
35    s.as_ref().get(idx).copied().unwrap_or_default()
36}
37
38fn parse_lit_str_cooked(mut s: &str) -> Vec<u8> {
39    let mut content = String::new();
40    'outer: loop {
41        let ch = match byte(s, 0) {
42            b'"' => break,
43            b'\\' => {
44                let b = byte(s, 1);
45                s = &s[2..];
46                match b {
47                    b'x' => {
48                        let (byte, rest) = backslash_x(s);
49                        s = rest;
50                        char::from_u32(u32::from(byte)).expect("byte was just validated")
51                    }
52                    b'u' => {
53                        let (chr, rest) = backslash_u(s);
54                        s = rest;
55                        chr
56                    }
57                    b'n' => '\n',
58                    b'r' => '\r',
59                    b't' => '\t',
60                    b'\\' => '\\',
61                    b'0' => '\0',
62                    b'\'' => '\'',
63                    b'"' => '"',
64                    b'\r' | b'\n' => loop {
65                        let ch = s.chars().next().unwrap_or_default();
66                        if ch.is_whitespace() {
67                            s = &s[ch.len_utf8()..];
68                        } else {
69                            continue 'outer;
70                        }
71                    },
72                    _ => bug!("invalid escape"),
73                }
74            }
75            b'\r' => {
76                // bare CR not permitted
77                s = &s[2..];
78                '\n'
79            }
80            _ => {
81                let ch = s.chars().next().unwrap_or_default();
82                s = &s[ch.len_utf8()..];
83                ch
84            }
85        };
86        content.push(ch);
87    }
88
89    content.into_bytes()
90}
91
92fn parse_lit_str_raw(s: &[u8]) -> Vec<u8> {
93    let mut pounds = 0;
94    while byte(s, pounds) == b'#' {
95        pounds += 1;
96    }
97    let close = s
98        .iter()
99        .rposition(|&b| b == b'"')
100        .expect("had a string without trailing \"");
101
102    s[pounds + 1..close].to_owned()
103}
104
105fn parse_lit_byte_str_cooked(mut v: &[u8]) -> Vec<u8> {
106    let mut out = Vec::new();
107    'outer: loop {
108        let byte = match byte(v, 0) {
109            b'"' => break,
110            b'\\' => {
111                let b = byte(v, 1);
112                v = &v[2..];
113                match b {
114                    b'x' => {
115                        let (byte, rest) = backslash_x(v);
116                        v = rest;
117                        byte
118                    }
119                    b'n' => b'\n',
120                    b'r' => b'\r',
121                    b't' => b'\t',
122                    b'\\' => b'\\',
123                    b'0' => b'\0',
124                    b'\'' => b'\'',
125                    b'"' => b'"',
126                    b'\r' | b'\n' => loop {
127                        let byte = byte(v, 0);
128                        let ch = char::from_u32(u32::from(byte)).expect("invalid byte");
129                        if ch.is_whitespace() {
130                            v = &v[1..];
131                        } else {
132                            continue 'outer;
133                        }
134                    },
135                    _ => bug!("invalid escape"),
136                }
137            }
138            b'\r' => {
139                // bare CR not permitted
140                v = &v[2..];
141                b'\n'
142            }
143            b => {
144                v = &v[1..];
145                b
146            }
147        };
148        out.push(byte);
149    }
150
151    out
152}
153
154fn backslash_x<S>(s: &S) -> (u8, &S)
155where
156    S: Index<RangeFrom<usize>, Output = S> + AsRef<[u8]> + ?Sized,
157{
158    let mut ch = 0;
159    let b0 = byte(s, 0);
160    let b1 = byte(s, 1);
161    ch += 0x10 * (b0 - b'0');
162    ch += match b1 {
163        b'0'..=b'9' => b1 - b'0',
164        b'a'..=b'f' => 10 + (b1 - b'a'),
165        b'A'..=b'F' => 10 + (b1 - b'A'),
166        _ => bug!("invalid hex escape"),
167    };
168    (ch, &s[2..])
169}
170
171fn backslash_u(mut s: &str) -> (char, &str) {
172    s = &s[1..];
173
174    let mut ch = 0;
175    let mut digits = 0;
176    loop {
177        let b = byte(s, 0);
178        let digit = match b {
179            b'0'..=b'9' => b - b'0',
180            b'a'..=b'f' => 10 + b - b'a',
181            b'A'..=b'F' => 10 + b - b'A',
182            b'_' if digits > 0 => {
183                s = &s[1..];
184                continue;
185            }
186            b'}' if digits != 0 => break,
187            _ => bug!("invalid unicode escape"),
188        };
189        ch *= 0x10;
190        ch += u32::from(digit);
191        digits += 1;
192        s = &s[1..];
193    }
194    s = &s[1..];
195
196    (
197        char::from_u32(ch).expect("invalid unicode escape passed by compiler"),
198        s,
199    )
200}