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