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};
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 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 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
126fn 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
144fn 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}