Skip to main content

moxy_token/lit/float/
mod.rs

1mod 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/// A parsed Rust float literal token.
11#[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    /// The value widened to `f64`.
44    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    /// Build a float literal from a scanned `repr`. Accepts an `f32`/`f64` suffix or an
52    /// unsuffixed fractional/exponent form (→ `F64`). Returns `None` otherwise.
53    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
139/// Split a numeric repr into its `digits` body and a trailing type suffix.
140fn 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
157/// `true` when the repr (after suffix split) is a float form.
158fn 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}