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)]
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 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
127fn 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
145fn 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}