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};
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 std::fmt::Display for LitFloat {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.write_str(self.repr())
99    }
100}
101
102#[cfg(feature = "serde")]
103impl From<LitFloat> for String {
104    fn from(value: LitFloat) -> Self {
105        value.repr().to_string()
106    }
107}
108
109impl Lit {
110    pub fn f32_suffixed(value: f32) -> Self {
111        LitF32::new(value, true, Span::default()).into()
112    }
113
114    pub fn f32_unsuffixed(value: f32) -> Self {
115        LitF32::new(value, false, Span::default()).into()
116    }
117
118    pub fn f64_suffixed(value: f64) -> Self {
119        LitF64::new(value, true, Span::default()).into()
120    }
121
122    pub fn f64_unsuffixed(value: f64) -> Self {
123        LitF64::new(value, false, Span::default()).into()
124    }
125}
126
127/// Split a numeric repr into its `digits` body and a trailing type suffix.
128fn split_suffix(repr: &str) -> (&str, &str) {
129    const SUFFIXES: [&str; 12] = [
130        "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize", "f32", "f64",
131    ];
132
133    for s in SUFFIXES {
134        if let Some(body) = repr.strip_suffix(s) {
135            if (s == "f32" || s == "f64") && (body.starts_with("0x") || body.starts_with("0X")) {
136                continue;
137            }
138            return (body, s);
139        }
140    }
141
142    (repr, "")
143}
144
145/// `true` when the repr (after suffix split) is a float form.
146fn is_float(digits: &str, suffix: &str) -> bool {
147    suffix.starts_with('f')
148        || (!digits.starts_with("0x")
149            && !digits.starts_with("0o")
150            && !digits.starts_with("0b")
151            && (digits.contains('.') || digits.contains('e') || digits.contains('E')))
152}
153
154fn scan_number(c: Cursor<'_>) -> Result<Cursor<'_>, LexError> {
155    let first = c.first().ok_or(c.error())?;
156
157    if !first.is_ascii_digit() {
158        return c.error().into();
159    }
160
161    let mut cur = c;
162
163    if first == '0' {
164        let next = cur.advance();
165
166        match next.first() {
167            Some('x' | 'X') => {
168                cur = digits(next.advance(), |ch| ch.is_ascii_hexdigit())?;
169                return Ok(suffix(cur));
170            }
171            Some('o' | 'O') => {
172                cur = digits(next.advance(), |ch| matches!(ch, '0'..='7'))?;
173                return Ok(suffix(cur));
174            }
175            Some('b' | 'B') => {
176                cur = digits(next.advance(), |ch| matches!(ch, '0' | '1'))?;
177                return Ok(suffix(cur));
178            }
179            _ => {}
180        }
181    }
182
183    cur = digits(cur, |ch| ch.is_ascii_digit())?;
184
185    if cur.starts_with(".") {
186        let after_dot = cur.advance();
187
188        if let Some(ch) = after_dot.first() {
189            if ch.is_ascii_digit() {
190                cur = digits_opt(after_dot, |ch| ch.is_ascii_digit());
191            }
192        }
193    }
194
195    if let Some('e' | 'E') = cur.first() {
196        cur = cur.advance();
197
198        if let Some('+' | '-') = cur.first() {
199            cur = cur.advance();
200        }
201
202        cur = digits(cur, |ch| ch.is_ascii_digit())?;
203    }
204
205    Ok(suffix(cur))
206}
207
208fn digits(c: Cursor<'_>, pred: fn(char) -> bool) -> Result<Cursor<'_>, LexError> {
209    let mut cur = c;
210    let mut found = false;
211
212    loop {
213        match cur.first() {
214            Some('_') => cur = cur.advance(),
215            Some(ch) if pred(ch) => {
216                found = true;
217                cur = cur.advance_by(ch.len_utf8());
218            }
219            _ => break,
220        }
221    }
222
223    if !found {
224        return c.error().into();
225    }
226
227    Ok(cur)
228}
229
230fn digits_opt(c: Cursor<'_>, pred: fn(char) -> bool) -> Cursor<'_> {
231    digits(c, pred).unwrap_or(c)
232}
233
234fn suffix(c: Cursor<'_>) -> Cursor<'_> {
235    match c.first() {
236        Some(ch) if ch == '_' || unicode_ident::is_xid_start(ch) => {
237            c.advance_by(ch.len_utf8()).skip_while(unicode_ident::is_xid_continue)
238        }
239        _ => c,
240    }
241}