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
180fn 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}