2 fmt::{Display, Formatter, Result as FmtResult},
6 use thiserror::Error as ThisError;
8 use crate::raw::{VarType, self};
10 #[derive(ThisError, Debug)]
12 #[error("Unknown format type {value}")]
13 UnknownFormat { value: u16 },
15 #[error("Output format {0} specifies width {}, but {} requires an even width.", .0.w, .0.format)]
16 OddWidthNotAllowed(UncheckedSpec),
18 #[error("Output format {0} specifies width {}, but {} requires a width between {} and {}.", .0.w, .0.format, .0.format.min_width(), .0.format.max_width())]
19 BadWidth(UncheckedSpec),
21 #[error("Output format {0} specifies decimal places, but {} format does not allow any decimals.", .0.format)]
22 DecimalsNotAllowedForFormat(UncheckedSpec),
24 #[error("Output format {0} specifies {} decimal places, but with a width of {}, {} does not allow any decimal places.", .0.d, .0.w, .0.format)]
25 DecimalsNotAllowedForWidth(UncheckedSpec),
27 #[error("Output format {spec} specifies {} decimal places but, with a width of {}, {} allows at most {max_d} decimal places.", .spec.d, .spec.w, .spec.format)]
28 TooManyDecimalsForWidth {
33 #[error("String variable is not compatible with numeric format {0}.")]
34 UnnamedVariableNotCompatibleWithNumericFormat(Format),
36 #[error("Numeric variable is not compatible with string format {0}.")]
37 UnnamedVariableNotCompatibleWithStringFormat(Format),
39 #[error("String variable {variable} is not compatible with numeric format {format}.")]
40 NamedVariableNotCompatibleWithNumericFormat { variable: String, format: Format },
42 #[error("Numeric variable {variable} is not compatible with string format {format}.")]
43 NamedVariableNotCompatibleWithStringFormat { variable: String, format: Format },
45 #[error("String variable {variable} with width {width} is not compatible with format {bad_spec}. Use format {good_spec} instead.")]
46 NamedStringVariableBadSpecWidth {
53 #[error("String variable with width {width} is not compatible with format {bad_spec}. Use format {good_spec} instead.")]
54 UnnamedStringVariableBadSpecWidth {
61 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
77 impl From<Format> for Category {
78 fn from(source: Format) -> Self {
80 Format::F | Format::Comma | Format::Dot | Format::Dollar | Format::Pct | Format::E => {
83 Format::CC(_) => Self::Custom,
84 Format::N | Format::Z => Self::Legacy,
85 Format::P | Format::PK | Format::IB | Format::PIB | Format::RB => Self::Binary,
86 Format::PIBHex | Format::RBHex => Self::Hex,
96 | Format::YMDHMS => Self::Date,
97 Format::MTime | Format::Time | Format::DTime => Self::Time,
98 Format::WkDay | Format::Month => Self::DateComponent,
99 Format::A | Format::AHex => Self::String,
104 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
113 impl Display for CC {
114 fn fmt(&self, f: &mut Formatter) -> FmtResult {
126 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
128 // Basic numeric formats.
136 // Custom currency formats.
139 // Legacy numeric formats.
143 // Binary and hexadecimal formats.
152 // Time and date formats.
167 // Date component formats.
176 pub const MAX_STRING: Width = 32767;
178 pub type Width = u16;
179 pub type SignedWidth = i16;
181 pub type Decimals = u8;
184 pub fn max_width(self) -> Width {
186 Self::P | Self::PK | Self::PIBHex | Self::RBHex => 16,
187 Self::IB | Self::PIB | Self::RB => 8,
188 Self::A => MAX_STRING,
189 Self::AHex => MAX_STRING * 2,
194 pub fn min_width(self) -> Width {
196 // Basic numeric formats.
204 // Custom currency formats.
207 // Legacy numeric formats.
211 // Binary and hexadecimal formats.
220 // Time and date formats.
229 Self::DateTime => 17,
235 // Date component formats.
245 pub fn width_range(self) -> RangeInclusive<Width> {
246 self.min_width()..=self.max_width()
249 pub fn max_decimals(self, width: Width) -> Decimals {
250 let width = width.clamp(1, 40) as SignedWidth;
251 let max = match self {
252 Self::F | Self::Comma | Self::Dot | Self::CC(_) => width - 1,
253 Self::Dollar | Self::Pct => width - 2,
254 Self::E => width - 7,
255 Self::N | Self::Z => width,
256 Self::P => width * 2 - 1,
257 Self::PK => width * 2,
258 Self::IB | Self::PIB => max_digits_for_bytes(width as usize) as SignedWidth,
260 Self::RB | Self::RBHex => 16,
269 Self::DateTime => width - 21,
270 Self::YMDHMS => width - 20,
271 Self::MTime => width - 6,
272 Self::Time => width - 9,
273 Self::DTime => width - 12,
274 Self::WkDay | Self::Month | Self::A | Self::AHex => 0,
276 max.clamp(0, 16) as Decimals
279 pub fn takes_decimals(self) -> bool {
280 self.max_decimals(Width::MAX) > 0
283 pub fn category(self) -> Category {
287 pub fn width_step(self) -> Width {
288 if self.category() == Category::Hex || self == Self::AHex {
295 pub fn clamp_width(self, width: Width) -> Width {
296 let (min, max) = self.width_range().into_inner();
297 let width = width.clamp(min, max);
298 if self.width_step() == 2 {
305 pub fn var_type(self) -> VarType {
307 Self::A | Self::AHex => VarType::String,
308 _ => VarType::Number,
312 pub fn check_type_compatibility(
314 variable: Option<&str>,
316 ) -> Result<(), Error> {
317 let my_type = self.var_type();
318 match (my_type, var_type) {
319 (VarType::Number, VarType::String) => {
320 if let Some(variable) = variable {
321 Err(Error::NamedVariableNotCompatibleWithNumericFormat {
322 variable: variable.into(),
326 Err(Error::UnnamedVariableNotCompatibleWithNumericFormat(self))
329 (VarType::String, VarType::Number) => {
330 if let Some(variable) = variable {
331 Err(Error::NamedVariableNotCompatibleWithStringFormat {
332 variable: variable.into(),
336 Err(Error::UnnamedVariableNotCompatibleWithStringFormat(self))
344 impl Display for Format {
345 fn fmt(&self, f: &mut Formatter) -> FmtResult {
348 Self::Comma => "COMMA",
350 Self::Dollar => "DOLLAR",
353 Self::CC(cc) => return write!(f, "{}", cc),
360 Self::PIBHex => "PIBHEX",
362 Self::RBHex => "RBHEX",
363 Self::Date => "DATE",
364 Self::ADate => "ADATE",
365 Self::EDate => "EDATE",
366 Self::JDate => "JDATE",
367 Self::SDate => "SDATE",
369 Self::MoYr => "MOYR",
370 Self::WkYr => "WKYR",
371 Self::DateTime => "DATETIME",
372 Self::YMDHMS => "YMDHMS",
373 Self::MTime => "MTIME",
374 Self::Time => "TIME",
375 Self::DTime => "DTIME",
376 Self::WkDay => "WKDAY",
377 Self::Month => "MONTH",
379 Self::AHex => "AHEX",
385 fn max_digits_for_bytes(bytes: usize) -> usize {
386 *[0, 3, 5, 8, 10, 13, 15, 17].get(bytes).unwrap_or(&20)
389 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
397 pub fn format(self) -> Format {
400 pub fn w(self) -> Width {
403 pub fn d(self) -> Decimals {
407 pub fn default_for_width(w: Width) -> Self {
409 0 => Spec { format: Format::F, w: 8, d: 2 },
410 _ => Spec { format: Format::A, w: w, d: 0 },
414 pub fn fixed_from(source: &UncheckedSpec) -> Self {
415 let UncheckedSpec { format, w, d } = *source;
416 let (min, max) = format.width_range().into_inner();
417 let mut w = w.clamp(min, max);
418 if d <= format.max_decimals(Width::MAX) {
419 while d > format.max_decimals(w) {
424 let d = d.clamp(0, format.max_decimals(w));
425 Self { format, w, d }
428 pub fn var_width(self) -> Width {
431 Format::AHex => self.w / 2,
436 pub fn var_type(self) -> VarType {
437 self.format.var_type()
440 pub fn check_width_compatibility(self, variable: Option<&str>, w: Width) -> Result<Self, Error> {
441 self.format.check_type_compatibility(variable, self.var_type())?;
442 let expected_width = self.var_width();
443 if w != expected_width {
445 let good_spec = if self.format == Format::A {
448 Spec { w: w * 2, ..self }
450 if let Some(variable) = variable {
451 Err(Error::NamedStringVariableBadSpecWidth {
452 variable: variable.into(),
458 Err(Error::UnnamedStringVariableBadSpecWidth {
470 impl Display for Spec {
471 fn fmt(&self, f: &mut Formatter) -> FmtResult {
472 write!(f, "{}{}", self.format, self.w)?;
473 if self.format.takes_decimals() || self.d > 0 {
474 write!(f, ".{}", self.d)?;
480 impl TryFrom<UncheckedSpec> for Spec {
483 fn try_from(source: UncheckedSpec) -> Result<Self, Self::Error> {
484 let UncheckedSpec { format, w, d } = source;
485 let max_d = format.max_decimals(w);
486 if w % format.width_step() != 0 {
487 Err(Error::OddWidthNotAllowed(source))
488 } else if !format.width_range().contains(&w) {
489 Err(Error::BadWidth(source))
490 } else if d > max_d {
491 if format.takes_decimals() {
492 Err(Error::DecimalsNotAllowedForFormat(source))
493 } else if max_d > 0 {
494 Err(Error::TooManyDecimalsForWidth {
499 Err(Error::DecimalsNotAllowedForWidth(source))
502 Ok(Spec { format, w, d })
507 impl TryFrom<u16> for Format {
510 fn try_from(source: u16) -> Result<Self, Self::Error> {
514 3 => Ok(Self::Comma),
515 4 => Ok(Self::Dollar),
518 7 => Ok(Self::PIBHex),
523 12 => Ok(Self::RBHex),
527 20 => Ok(Self::Date),
528 21 => Ok(Self::Time),
529 22 => Ok(Self::DateTime),
530 23 => Ok(Self::ADate),
531 24 => Ok(Self::JDate),
532 25 => Ok(Self::DTime),
533 26 => Ok(Self::WkDay),
534 27 => Ok(Self::Month),
535 28 => Ok(Self::MoYr),
537 30 => Ok(Self::WkYr),
540 33 => Ok(Self::CC(CC::A)),
541 34 => Ok(Self::CC(CC::B)),
542 35 => Ok(Self::CC(CC::C)),
543 36 => Ok(Self::CC(CC::D)),
544 37 => Ok(Self::CC(CC::E)),
545 38 => Ok(Self::EDate),
546 39 => Ok(Self::SDate),
547 40 => Ok(Self::MTime),
548 41 => Ok(Self::YMDHMS),
549 _ => Err(Error::UnknownFormat { value: source }),
554 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
555 pub struct UncheckedSpec {
563 impl TryFrom<raw::Spec> for UncheckedSpec {
566 fn try_from(raw: raw::Spec) -> Result<Self, Self::Error> {
568 let raw_format = (raw >> 16) as u16;
569 let format = raw_format.try_into()?;
570 let w = ((raw >> 8) & 0xff) as Width;
571 let d = (raw & 0xff) as Decimals;
572 Ok(Self { format, w, d })
576 impl Display for UncheckedSpec {
577 fn fmt(&self, f: &mut Formatter) -> FmtResult {
578 write!(f, "{}{}", self.format, self.w)?;
579 if self.format.takes_decimals() || self.d > 0 {
580 write!(f, ".{}", self.d)?;