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