moxy_token/lit/float/
mod.rs1mod f32;
2mod f64;
3
4pub use f32::*;
5pub use f64::*;
6
7use crate::lex::{Cursor, LexError, Scan};
8use crate::{Lit, Span, Spanner, ToTokens, TokenStream, TokenTree};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
13pub enum LitFloat {
14 F32(LitF32),
15 F64(LitF64),
16}
17
18impl LitFloat {
19 #[inline]
20 pub fn repr(&self) -> &str {
21 match self {
22 Self::F32(v) => v.repr(),
23 Self::F64(v) => v.repr(),
24 }
25 }
26
27 #[inline]
28 pub fn span(&self) -> Span {
29 match self {
30 Self::F32(v) => v.span(),
31 Self::F64(v) => v.span(),
32 }
33 }
34
35 #[inline]
36 pub fn set_span(&mut self, span: Span) {
37 match self {
38 Self::F32(v) => v.set_span(span),
39 Self::F64(v) => v.set_span(span),
40 }
41 }
42
43 pub fn as_f64(&self) -> f64 {
45 match self {
46 Self::F32(v) => v.value() as f64,
47 Self::F64(v) => v.value(),
48 }
49 }
50
51 pub(crate) fn from_repr(repr: &str, span: Span) -> Option<Self> {
54 let (digits, suffix) = split_suffix(repr);
55
56 if !is_float(digits, suffix) {
57 return None;
58 }
59
60 let suffixed = !suffix.is_empty();
61
62 match suffix {
63 "f32" => {
64 let value = digits.replace('_', "").parse::<f32>().ok()?;
65 Some(Self::F32(LitF32::from_parts(value, suffixed, repr, span)))
66 }
67 "f64" | "" => {
68 let value = digits.replace('_', "").parse::<f64>().ok()?;
69 Some(Self::F64(LitF64::from_parts(value, suffixed, repr, span)))
70 }
71 _ => None,
72 }
73 }
74}
75
76impl Scan for LitFloat {
77 fn scan(cursor: Cursor<'_>) -> Result<(Cursor<'_>, Self), LexError> {
78 let end = scan_number(cursor)?;
79 let len = end.offset() as usize - cursor.offset() as usize;
80 let repr = &cursor.rest()[..len];
81 let span = cursor.span_to(&end);
82
83 match Self::from_repr(repr, span) {
84 Some(float) => Ok((end, float)),
85 None => cursor.error().into(),
86 }
87 }
88}
89
90impl Spanner for LitFloat {
91 fn span(&self) -> Span {
92 self.span()
93 }
94}
95
96impl ToTokens for LitFloat {
97 fn to_tokens(&self, tokens: &mut TokenStream) {
98 tokens.extend_one(TokenTree::Literal(self.clone().into()));
99 }
100}
101
102impl std::fmt::Display for LitFloat {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.write_str(self.repr())
105 }
106}
107
108impl From<LitFloat> for Lit {
109 fn from(value: LitFloat) -> Self {
110 Self::Float(value)
111 }
112}
113
114#[cfg(feature = "serde")]
115impl From<LitFloat> for String {
116 fn from(value: LitFloat) -> Self {
117 value.repr().to_string()
118 }
119}
120
121impl Lit {
122 pub fn f32_suffixed(value: f32) -> Self {
123 LitF32::new(value, true, Span::default()).into()
124 }
125
126 pub fn f32_unsuffixed(value: f32) -> Self {
127 LitF32::new(value, false, Span::default()).into()
128 }
129
130 pub fn f64_suffixed(value: f64) -> Self {
131 LitF64::new(value, true, Span::default()).into()
132 }
133
134 pub fn f64_unsuffixed(value: f64) -> Self {
135 LitF64::new(value, false, Span::default()).into()
136 }
137}
138
139fn split_suffix(repr: &str) -> (&str, &str) {
141 const SUFFIXES: [&str; 12] = [
142 "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize", "f32", "f64",
143 ];
144
145 for s in SUFFIXES {
146 if let Some(body) = repr.strip_suffix(s) {
147 if (s == "f32" || s == "f64") && (body.starts_with("0x") || body.starts_with("0X")) {
148 continue;
149 }
150 return (body, s);
151 }
152 }
153
154 (repr, "")
155}
156
157fn is_float(digits: &str, suffix: &str) -> bool {
159 suffix.starts_with('f')
160 || (!digits.starts_with("0x")
161 && !digits.starts_with("0o")
162 && !digits.starts_with("0b")
163 && (digits.contains('.') || digits.contains('e') || digits.contains('E')))
164}
165
166fn scan_number(c: Cursor<'_>) -> Result<Cursor<'_>, LexError> {
167 let first = c.first().ok_or(c.error())?;
168
169 if !first.is_ascii_digit() {
170 return c.error().into();
171 }
172
173 let mut cur = c;
174
175 if first == '0' {
176 let next = cur.advance();
177
178 match next.first() {
179 Some('x' | 'X') => {
180 cur = digits(next.advance(), |ch| ch.is_ascii_hexdigit())?;
181 return Ok(suffix(cur));
182 }
183 Some('o' | 'O') => {
184 cur = digits(next.advance(), |ch| matches!(ch, '0'..='7'))?;
185 return Ok(suffix(cur));
186 }
187 Some('b' | 'B') => {
188 cur = digits(next.advance(), |ch| matches!(ch, '0' | '1'))?;
189 return Ok(suffix(cur));
190 }
191 _ => {}
192 }
193 }
194
195 cur = digits(cur, |ch| ch.is_ascii_digit())?;
196
197 if cur.starts_with(".") {
198 let after_dot = cur.advance();
199
200 if let Some(ch) = after_dot.first() {
201 if ch.is_ascii_digit() {
202 cur = digits_opt(after_dot, |ch| ch.is_ascii_digit());
203 }
204 }
205 }
206
207 if let Some('e' | 'E') = cur.first() {
208 cur = cur.advance();
209
210 if let Some('+' | '-') = cur.first() {
211 cur = cur.advance();
212 }
213
214 cur = digits(cur, |ch| ch.is_ascii_digit())?;
215 }
216
217 Ok(suffix(cur))
218}
219
220fn digits(c: Cursor<'_>, pred: fn(char) -> bool) -> Result<Cursor<'_>, LexError> {
221 let mut cur = c;
222 let mut found = false;
223
224 loop {
225 match cur.first() {
226 Some('_') => cur = cur.advance(),
227 Some(ch) if pred(ch) => {
228 found = true;
229 cur = cur.advance_by(ch.len_utf8());
230 }
231 _ => break,
232 }
233 }
234
235 if !found {
236 return c.error().into();
237 }
238
239 Ok(cur)
240}
241
242fn digits_opt(c: Cursor<'_>, pred: fn(char) -> bool) -> Cursor<'_> {
243 digits(c, pred).unwrap_or(c)
244}
245
246fn suffix(c: Cursor<'_>) -> Cursor<'_> {
247 match c.first() {
248 Some(ch) if ch == '_' || unicode_ident::is_xid_start(ch) => {
249 c.advance_by(ch.len_utf8()).skip_while(unicode_ident::is_xid_continue)
250 }
251 _ => c,
252 }
253}