Skip to main content

moxy_token/lit/
char.rs

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