1#![expect(unused_assignments)]
2
3use crate::common::*;
4
5#[derive(Debug, Diagnostic, thiserror::Error)]
6pub enum ParseNumberError {
7 #[error("expected unsigned number but found sign")]
8 #[diagnostic()]
9 UnexpectedSign {
10 #[label("occurs here")]
11 span: SourceSpan,
12 },
13 #[error("expected hexadecimal number with 0x prefix, but no prefix was present")]
14 #[diagnostic()]
15 MissingPrefix {
16 #[label("occurs here")]
17 span: SourceSpan,
18 },
19 #[error("input string has incorrect precision: expected {precision} got {actual}")]
20 #[diagnostic()]
21 PrecisionMismatch {
22 #[label("occurs here")]
23 span: SourceSpan,
24 precision: u8,
25 actual: usize,
26 },
27 #[error("input string has incorrect numeric format: {reason:?}")]
28 #[diagnostic()]
29 InvalidFormat {
30 #[label("occurs here")]
31 span: SourceSpan,
32 reason: core::num::IntErrorKind,
33 },
34}
35
36#[derive(Debug, Clone)]
37pub struct Number {
38 pub span: SourceSpan,
39 pub format: NumberFormat,
40 pub value: i64,
41}
42impl Number {
43 pub fn new(span: SourceSpan, value: i64) -> Self {
44 Self {
45 span,
46 format: NumberFormat::default(),
47 value,
48 }
49 }
50
51 pub fn new_with_format(span: SourceSpan, value: i64, format: NumberFormat) -> Self {
52 Self {
53 span,
54 format,
55 value,
56 }
57 }
58
59 pub fn parse_with_format(
60 input: Span<&str>,
61 format: NumberFormat,
62 ) -> Result<Self, ParseNumberError> {
63 let (span, input) = input.into_parts();
64
65 match format {
66 NumberFormat::Unsigned { precision } => {
67 if input.starts_with(['-', '+']) {
68 return Err(ParseNumberError::UnexpectedSign { span });
69 }
70 let value = input
71 .parse::<i64>()
72 .map(|value| Self {
73 span,
74 format,
75 value,
76 })
77 .map_err(|error| ParseNumberError::InvalidFormat {
78 span,
79 reason: *error.kind(),
80 })?;
81 if precision == 0 {
82 return Ok(value);
83 }
84 if input.len() != precision as usize {
85 Err(ParseNumberError::PrecisionMismatch {
86 span,
87 precision,
88 actual: input.len(),
89 })
90 } else {
91 Ok(value)
92 }
93 }
94 NumberFormat::Signed { precision } => {
95 let value = input
96 .parse::<i64>()
97 .map(|value| Self {
98 span,
99 format,
100 value,
101 })
102 .map_err(|error| ParseNumberError::InvalidFormat {
103 span,
104 reason: *error.kind(),
105 })?;
106 if precision == 0 {
107 return Ok(value);
108 }
109 let actual = if let Some(input) = input.strip_prefix(['-', '+']) {
110 input.len()
111 } else {
112 input.len()
113 };
114 if actual != precision as usize {
115 Err(ParseNumberError::PrecisionMismatch {
116 span,
117 precision,
118 actual,
119 })
120 } else {
121 Ok(value)
122 }
123 }
124 NumberFormat::Hex {
125 require_prefix,
126 precision,
127 } => {
128 let input = match input.strip_prefix("0x") {
129 None if require_prefix => return Err(ParseNumberError::MissingPrefix { span }),
130 None => input,
131 Some(input) => input,
132 };
133 let value = i64::from_str_radix(input, 16)
134 .map(|value| Self {
135 span,
136 format,
137 value,
138 })
139 .map_err(|error| ParseNumberError::InvalidFormat {
140 span,
141 reason: *error.kind(),
142 })?;
143 if input.len() != precision as usize {
144 Err(ParseNumberError::PrecisionMismatch {
145 span,
146 precision,
147 actual: input.len(),
148 })
149 } else {
150 Ok(value)
151 }
152 }
153 }
154 }
155}
156impl Eq for Number {}
157impl PartialEq for Number {
158 fn eq(&self, other: &Self) -> bool {
159 self.value == other.value && self.format == other.format
160 }
161}
162impl PartialOrd for Number {
163 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
164 Some(self.cmp(other))
165 }
166}
167impl Ord for Number {
168 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
169 self.value.cmp(&other.value)
170 }
171}
172impl Spanned for Number {
173 fn span(&self) -> SourceSpan {
174 self.span
175 }
176}
177impl fmt::Display for Number {
178 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
179 let value = self.value;
180 match self.format {
181 NumberFormat::Unsigned { precision: 0 } => write!(f, "{}", value as u64),
182 NumberFormat::Unsigned { precision: n } => {
183 write!(f, "{:0n$}", value as u64, n = n as usize)
184 }
185 NumberFormat::Signed { precision: 0 } => write!(f, "{value}"),
186 NumberFormat::Signed { precision: n } => write!(f, "{value:0n$}", n = n as usize),
187 NumberFormat::Hex {
188 require_prefix: true,
189 precision: 0,
190 } => write!(f, "{value:#x?}"),
191 NumberFormat::Hex {
192 require_prefix: false,
193 precision: 0,
194 } => write!(f, "{value:x?}"),
195 NumberFormat::Hex {
196 require_prefix: true,
197 precision: n,
198 } => write!(f, "{value:#0n$x?}", n = n as usize),
199 NumberFormat::Hex {
200 require_prefix: false,
201 precision: n,
202 } => write!(f, "{value:0n$x?}", n = n as usize),
203 }
204 }
205}
206
207#[derive(Debug, Copy, Clone, PartialEq, Eq)]
208#[repr(u8)]
209pub enum NumberFormat {
210 Unsigned { precision: u8 },
211 Signed { precision: u8 },
212 Hex { precision: u8, require_prefix: bool },
213}
214impl NumberFormat {
215 pub fn describe(&self) -> Cow<'static, str> {
216 match self {
217 Self::Unsigned { precision: 0 } => Cow::Borrowed("any unsigned 64-bit integer"),
218 Self::Unsigned { precision } => {
219 Cow::Owned(format!("an unsigned {precision}-digit 64-bit integer"))
220 }
221 Self::Signed { precision: 0 } => Cow::Borrowed("any signed 64-bit integer"),
222 Self::Signed { precision } => {
223 Cow::Owned(format!("a signed {precision}-digit 64-bit integer"))
224 }
225 Self::Hex {
226 require_prefix: true,
227 precision: 0,
228 } => Cow::Borrowed("any 64-bit integer in hex format, prefixed with 0x"),
229 Self::Hex {
230 require_prefix: false,
231 precision: 0,
232 } => Cow::Borrowed("any 64-bit integer in hex format"),
233 Self::Hex {
234 require_prefix: true,
235 precision,
236 } => Cow::Owned(format!(
237 "a {precision}-digit 64-bit integer in hex format, prefixed with 0x"
238 )),
239 Self::Hex { precision, .. } => {
240 Cow::Owned(format!("a {precision}-digit 64-bit integer in hex format"))
241 }
242 }
243 }
244
245 pub fn is_signed(&self) -> bool {
246 matches!(self, Self::Signed { .. })
247 }
248
249 pub fn is_hex(&self) -> bool {
250 matches!(self, Self::Hex { .. })
251 }
252
253 pub fn precision(&self) -> usize {
254 match self {
255 Self::Unsigned { precision }
256 | Self::Signed { precision }
257 | Self::Hex { precision, .. } => *precision as usize,
258 }
259 }
260
261 pub fn discriminant(&self) -> u8 {
262 unsafe { *<*const _>::from(self).cast::<u8>() }
266 }
267
268 pub fn pattern_nocapture(&self) -> Cow<'static, str> {
269 match self {
274 NumberFormat::Signed { precision: 0 } => Cow::Borrowed(r"(?:[-+]?[0-9]{1,19})"),
275 NumberFormat::Signed { precision } => {
276 Cow::Owned(format!("(?:[-+]?[0-9]{{{precision}}})"))
277 }
278 NumberFormat::Unsigned { precision: 0 } => Cow::Borrowed(r"(?:[0-9]{1,19})"),
279 NumberFormat::Unsigned { precision } => Cow::Owned(format!("(?:[0-9]{{{precision}}})")),
280 NumberFormat::Hex {
283 require_prefix: true,
284 precision: 0,
285 } => Cow::Borrowed(r"(?:0x[A-Fa-f0-9]{1,16})"),
286 NumberFormat::Hex {
287 require_prefix: true,
288 precision,
289 } => Cow::Owned(format!("(?:0x[A-Fa-f0-9]{{{precision}}})")),
290 NumberFormat::Hex {
291 require_prefix: false,
292 precision: 0,
293 } => Cow::Borrowed(r"(?:[A-Fa-f0-9]{1,16})"),
294 NumberFormat::Hex {
295 require_prefix: false,
296 precision,
297 } => Cow::Owned(format!("(?:[A-Fa-f0-9]{{{precision}}})")),
298 }
299 }
300
301 pub fn pattern(&self, group_name_override: Option<&str>) -> Cow<'static, str> {
302 let group_name = group_name_override.unwrap_or("digits");
307 match self {
308 NumberFormat::Signed { precision: 0 } => match group_name_override {
309 None => Cow::Borrowed(r"(?P<digits>[-+]?[0-9]{1,19})"),
310 Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[-+]?[0-9]{{1,19}})")),
311 },
312 NumberFormat::Signed { precision } => {
313 Cow::Owned(format!("(?P<{group_name}>[-+]?[0-9]{{{precision}}})"))
314 }
315 NumberFormat::Unsigned { precision: 0 } => match group_name_override {
316 None => Cow::Borrowed(r"(?P<digits>[0-9]{1,19})"),
317 Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[0-9]{{1,19}})")),
318 },
319 NumberFormat::Unsigned { precision } => {
320 Cow::Owned(format!("(?P<{group_name}>[0-9]{{{precision}}})"))
321 }
322 NumberFormat::Hex {
325 require_prefix: true,
326 precision: 0,
327 } => match group_name_override {
328 None => Cow::Borrowed(r"(?P<digits>0x[A-Fa-f0-9]{1,16})"),
329 Some(group_name) => Cow::Owned(format!("(?P<{group_name}>0x[A-Fa-f0-9]{{1,16}})")),
330 },
331 NumberFormat::Hex {
332 require_prefix: true,
333 precision,
334 } => Cow::Owned(format!("(?P<{group_name}>0x[A-Fa-f0-9]{{{precision}}})")),
335 NumberFormat::Hex {
336 require_prefix: false,
337 precision: 0,
338 } => match group_name_override {
339 None => Cow::Borrowed(r"(?P<digits>[A-Fa-f0-9]{1,16})"),
340 Some(group_name) => Cow::Owned(format!("(?P<{group_name}>[A-Fa-f0-9]{{1,16}})")),
341 },
342 NumberFormat::Hex {
343 require_prefix: false,
344 precision,
345 } => Cow::Owned(format!("(?P<{group_name}>[A-Fa-f0-9]{{{precision}}})")),
346 }
347 }
348}
349impl Default for NumberFormat {
350 fn default() -> Self {
351 Self::Unsigned { precision: 0 }
352 }
353}
354
355#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
356pub enum FormatSpecifier {
357 #[default]
358 Unsigned,
359 Signed,
360 Hex,
361}