Skip to main content

moxy_token/lit/
char.rs

1use crate::lex::{Cursor, LexError, Scan};
2use crate::lit::Lit;
3use crate::{Span, Spanner};
4
5#[derive(Debug, Clone)]
6#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(into = "String"))]
7pub struct LitChar {
8    value: char,
9    repr: Box<str>,
10    span: Span,
11}
12
13impl LitChar {
14    #[inline]
15    pub fn new(value: char, span: Span) -> Self {
16        Self {
17            value,
18            repr: format!("{value:?}").into_boxed_str(),
19            span,
20        }
21    }
22
23    #[inline]
24    pub(crate) fn from_parts(value: char, repr: &str, span: Span) -> Self {
25        Self {
26            value,
27            repr: repr.into(),
28            span,
29        }
30    }
31
32    #[inline]
33    pub fn value(&self) -> char {
34        self.value
35    }
36
37    #[inline]
38    pub fn repr(&self) -> &str {
39        &self.repr
40    }
41
42    #[inline]
43    pub fn span(&self) -> Span {
44        self.span
45    }
46
47    #[inline]
48    pub fn set_span(&mut self, span: Span) {
49        self.span = span;
50    }
51}
52
53impl PartialEq for LitChar {
54    fn eq(&self, other: &Self) -> bool {
55        self.value == other.value
56    }
57}
58
59impl Eq for LitChar {}
60
61impl std::hash::Hash for LitChar {
62    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
63        self.value.hash(state);
64    }
65}
66
67impl std::fmt::Display for LitChar {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.write_str(&self.repr)
70    }
71}
72
73impl Spanner for LitChar {
74    fn span(&self) -> Span {
75        self.span
76    }
77}
78
79impl Scan for LitChar {
80    fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
81        let end = scan_quoted(cursor, "'")?;
82        let len = end.offset() as usize - cursor.offset() as usize;
83        let repr = &cursor.rest()[..len];
84        let span = cursor.span_to(&end);
85
86        let inner = repr.strip_prefix('\'').and_then(|r| r.strip_suffix('\''));
87        match inner.and_then(decode_one_char) {
88            Some(value) => Ok((end, Self::from_parts(value, repr, span))),
89            None => cursor.error().into(),
90        }
91    }
92}
93
94impl From<LitChar> for Lit {
95    fn from(value: LitChar) -> Self {
96        Self::Char(value)
97    }
98}
99
100#[cfg(feature = "serde")]
101impl From<LitChar> for String {
102    fn from(value: LitChar) -> Self {
103        value.repr.into_string()
104    }
105}
106
107fn scan_quoted<'a>(c: Cursor<'a>, open: &str) -> Result<Cursor<'a>, LexError> {
108    if !c.starts_with(open) {
109        return c.error().into();
110    }
111
112    let start = c;
113    let c = c.advance_by(open.len());
114    let c = match c.first() {
115        None | Some('\'') => return start.error().into(),
116        Some('\\') => escape(c.advance())?,
117        Some(ch) => c.advance_by(ch.len_utf8()),
118    };
119
120    if !c.starts_with("'") {
121        return start.error().into();
122    }
123
124    Ok(c.advance())
125}
126
127fn escape(c: Cursor<'_>) -> Result<Cursor<'_>, LexError> {
128    match c.first() {
129        None => c.error().into(),
130        Some('n' | 'r' | 't' | '\\' | '\'' | '"' | '0') => Ok(c.advance()),
131        Some('x') => {
132            let c = c.advance();
133            let c = hex_digit(c)?;
134            hex_digit(c)
135        }
136        Some('u') => {
137            let c = c.advance();
138
139            if !c.starts_with("{") {
140                return c.error().into();
141            }
142
143            let mut c = c.advance();
144            let mut count = 0;
145
146            loop {
147                match c.first() {
148                    Some('}') if count > 0 => return Ok(c.advance()),
149                    Some(ch) if ch.is_ascii_hexdigit() && count < 6 => {
150                        count += 1;
151                        c = c.advance();
152                    }
153                    _ => return c.error().into(),
154                }
155            }
156        }
157        _ => c.error().into(),
158    }
159}
160
161fn hex_digit(c: Cursor<'_>) -> Result<Cursor<'_>, LexError> {
162    match c.first() {
163        Some(ch) if ch.is_ascii_hexdigit() => Ok(c.advance()),
164        _ => c.error().into(),
165    }
166}
167
168/// Decode a single (possibly escaped) char from a char-literal inner body.
169fn decode_one_char(inner: &str) -> Option<char> {
170    let mut chars = inner.chars();
171    let value = match chars.next()? {
172        '\\' => decode_escape(&mut chars)?,
173        c => c,
174    };
175
176    if chars.next().is_some() {
177        return None;
178    }
179
180    Some(value)
181}
182
183fn decode_escape(chars: &mut std::str::Chars<'_>) -> Option<char> {
184    match chars.next()? {
185        'n' => Some('\n'),
186        'r' => Some('\r'),
187        't' => Some('\t'),
188        '\\' => Some('\\'),
189        '\'' => Some('\''),
190        '"' => Some('"'),
191        '0' => Some('\0'),
192        'x' => {
193            let hi = chars.next()?.to_digit(16)?;
194            let lo = chars.next()?.to_digit(16)?;
195            char::from_u32(hi * 16 + lo)
196        }
197        'u' => {
198            if chars.next()? != '{' {
199                return None;
200            }
201            let mut value = 0u32;
202            let mut count = 0;
203            for c in chars.by_ref() {
204                if c == '}' {
205                    break;
206                }
207                value = value.checked_mul(16)?.checked_add(c.to_digit(16)?)?;
208                count += 1;
209                if count > 6 {
210                    return None;
211                }
212            }
213            char::from_u32(value)
214        }
215        _ => None,
216    }
217}