2 fmt::{Display, Formatter, Result as FmtResult},
6 use thiserror::Error as ThisError;
13 #[derive(ThisError, Debug)]
15 #[error("Unknown format type {value}.")]
16 UnknownFormat { value: u16 },
18 #[error("Output format {0} specifies width {}, but {} requires an even width.", .0.w, .0.format)]
19 OddWidthNotAllowed(UncheckedSpec),
21 #[error("Output format {0} specifies width {}, but {} requires a width between {} and {}.", .0.w, .0.format, .0.format.min_width(), .0.format.max_width())]
22 BadWidth(UncheckedSpec),
24 #[error("Output format {0} specifies decimal places, but {} format does not allow any decimals.", .0.format)]
25 DecimalsNotAllowedForFormat(UncheckedSpec),
27 #[error("Output format {0} specifies {} decimal places, but with a width of {}, {} does not allow any decimal places.", .0.d, .0.w, .0.format)]
28 DecimalsNotAllowedForWidth(UncheckedSpec),
30 #[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)]
31 TooManyDecimalsForWidth {
36 #[error("String variable is not compatible with numeric format {0}.")]
37 UnnamedVariableNotCompatibleWithNumericFormat(Format),
39 #[error("Numeric variable is not compatible with string format {0}.")]
40 UnnamedVariableNotCompatibleWithStringFormat(Format),
42 #[error("String variable {variable} with width {width} is not compatible with format {bad_spec}. Use format {good_spec} instead.")]
43 NamedStringVariableBadSpecWidth {
50 #[error("String variable with width {width} is not compatible with format {bad_spec}. Use format {good_spec} instead.")]
51 UnnamedStringVariableBadSpecWidth {
58 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
74 impl From<Format> for Category {
75 fn from(source: Format) -> Self {
77 Format::F | Format::Comma | Format::Dot | Format::Dollar | Format::Pct | Format::E => {
80 Format::CC(_) => Self::Custom,
81 Format::N | Format::Z => Self::Legacy,
82 Format::P | Format::PK | Format::IB | Format::PIB | Format::RB => Self::Binary,
83 Format::PIBHex | Format::RBHex => Self::Hex,
93 | Format::YMDHMS => Self::Date,
94 Format::MTime | Format::Time | Format::DTime => Self::Time,
95 Format::WkDay | Format::Month => Self::DateComponent,
96 Format::A | Format::AHex => Self::String,
101 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
110 impl Display for CC {
111 fn fmt(&self, f: &mut Formatter) -> FmtResult {
123 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
125 // Basic numeric formats.
133 // Custom currency formats.
136 // Legacy numeric formats.
140 // Binary and hexadecimal formats.
149 // Time and date formats.
164 // Date component formats.
173 pub type Width = u16;
174 pub type SignedWidth = i16;
176 pub type Decimals = u8;
179 pub fn max_width(self) -> Width {
181 Self::P | Self::PK | Self::PIBHex | Self::RBHex => 16,
182 Self::IB | Self::PIB | Self::RB => 8,
184 Self::AHex => 32767 * 2,
189 pub fn min_width(self) -> Width {
191 // Basic numeric formats.
199 // Custom currency formats.
202 // Legacy numeric formats.
206 // Binary and hexadecimal formats.
215 // Time and date formats.
224 Self::DateTime => 17,
230 // Date component formats.
240 pub fn width_range(self) -> RangeInclusive<Width> {
241 self.min_width()..=self.max_width()
244 pub fn max_decimals(self, width: Width) -> Decimals {
245 let width = width.clamp(1, 40) as SignedWidth;
246 let max = match self {
247 Self::F | Self::Comma | Self::Dot | Self::CC(_) => width - 1,
248 Self::Dollar | Self::Pct => width - 2,
249 Self::E => width - 7,
250 Self::N | Self::Z => width,
251 Self::P => width * 2 - 1,
252 Self::PK => width * 2,
253 Self::IB | Self::PIB => max_digits_for_bytes(width as usize) as SignedWidth,
255 Self::RB | Self::RBHex => 16,
264 Self::DateTime => width - 21,
265 Self::YMDHMS => width - 20,
266 Self::MTime => width - 6,
267 Self::Time => width - 9,
268 Self::DTime => width - 12,
269 Self::WkDay | Self::Month | Self::A | Self::AHex => 0,
271 max.clamp(0, 16) as Decimals
274 pub fn takes_decimals(self) -> bool {
275 self.max_decimals(Width::MAX) > 0
278 pub fn category(self) -> Category {
282 pub fn width_step(self) -> Width {
283 if self.category() == Category::Hex || self == Self::AHex {
290 pub fn clamp_width(self, width: Width) -> Width {
291 let (min, max) = self.width_range().into_inner();
292 let width = width.clamp(min, max);
293 if self.width_step() == 2 {
300 pub fn var_type(self) -> VarType {
302 Self::A | Self::AHex => VarType::String,
303 _ => VarType::Numeric,
307 /// Checks whether this format is valid for a variable with the given
309 pub fn check_type_compatibility(self, var_type: VarType) -> Result<(), Error> {
310 let my_type = self.var_type();
311 match (my_type, var_type) {
312 (VarType::Numeric, VarType::String) => {
313 Err(Error::UnnamedVariableNotCompatibleWithNumericFormat(self))
315 (VarType::String, VarType::Numeric) => {
316 Err(Error::UnnamedVariableNotCompatibleWithStringFormat(self))
323 impl Display for Format {
324 fn fmt(&self, f: &mut Formatter) -> FmtResult {
327 Self::Comma => "COMMA",
329 Self::Dollar => "DOLLAR",
332 Self::CC(cc) => return write!(f, "{}", cc),
339 Self::PIBHex => "PIBHEX",
341 Self::RBHex => "RBHEX",
342 Self::Date => "DATE",
343 Self::ADate => "ADATE",
344 Self::EDate => "EDATE",
345 Self::JDate => "JDATE",
346 Self::SDate => "SDATE",
348 Self::MoYr => "MOYR",
349 Self::WkYr => "WKYR",
350 Self::DateTime => "DATETIME",
351 Self::YMDHMS => "YMDHMS",
352 Self::MTime => "MTIME",
353 Self::Time => "TIME",
354 Self::DTime => "DTIME",
355 Self::WkDay => "WKDAY",
356 Self::Month => "MONTH",
358 Self::AHex => "AHEX",
364 fn max_digits_for_bytes(bytes: usize) -> usize {
365 *[0, 3, 5, 8, 10, 13, 15, 17].get(bytes).unwrap_or(&20)
368 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
376 pub fn format(self) -> Format {
379 pub fn w(self) -> Width {
382 pub fn d(self) -> Decimals {
386 pub fn default_for_width(var_width: VarWidth) -> Self {
388 VarWidth::Numeric => Spec {
393 VarWidth::String(w) => Spec {
401 pub fn fixed_from(source: &UncheckedSpec) -> Self {
402 let UncheckedSpec { format, w, d } = *source;
403 let (min, max) = format.width_range().into_inner();
404 let mut w = w.clamp(min, max);
405 if d <= format.max_decimals(Width::MAX) {
406 while d > format.max_decimals(w) {
411 let d = d.clamp(0, format.max_decimals(w));
412 Self { format, w, d }
415 pub fn var_width(self) -> VarWidth {
417 Format::A => VarWidth::String(self.w),
418 Format::AHex => VarWidth::String(self.w / 2),
419 _ => VarWidth::Numeric,
423 pub fn var_type(self) -> VarType {
424 self.format.var_type()
427 /// Checks whether this format specification is valid for a variable with
428 /// width `var_width`.
429 pub fn check_width_compatibility(self, var_width: VarWidth) -> Result<Self, Error> {
430 // Verify that the format is right for the variable's type.
432 .check_type_compatibility(var_width.into())?;
434 if let VarWidth::String(w) = var_width {
435 if var_width != self.var_width() {
437 let good_spec = if self.format == Format::A {
440 Spec { w: w * 2, ..self }
442 return Err(Error::UnnamedStringVariableBadSpecWidth {
454 impl Display for Spec {
455 fn fmt(&self, f: &mut Formatter) -> FmtResult {
456 write!(f, "{}{}", self.format, self.w)?;
457 if self.format.takes_decimals() || self.d > 0 {
458 write!(f, ".{}", self.d)?;
464 impl TryFrom<UncheckedSpec> for Spec {
467 fn try_from(source: UncheckedSpec) -> Result<Self, Self::Error> {
468 let UncheckedSpec { format, w, d } = source;
469 let max_d = format.max_decimals(w);
470 if w % format.width_step() != 0 {
471 Err(Error::OddWidthNotAllowed(source))
472 } else if !format.width_range().contains(&w) {
473 Err(Error::BadWidth(source))
474 } else if d > max_d {
475 if format.takes_decimals() {
476 Err(Error::DecimalsNotAllowedForFormat(source))
477 } else if max_d > 0 {
478 Err(Error::TooManyDecimalsForWidth {
483 Err(Error::DecimalsNotAllowedForWidth(source))
486 Ok(Spec { format, w, d })
491 impl TryFrom<u16> for Format {
494 fn try_from(source: u16) -> Result<Self, Self::Error> {
498 3 => Ok(Self::Comma),
499 4 => Ok(Self::Dollar),
502 7 => Ok(Self::PIBHex),
507 12 => Ok(Self::RBHex),
511 20 => Ok(Self::Date),
512 21 => Ok(Self::Time),
513 22 => Ok(Self::DateTime),
514 23 => Ok(Self::ADate),
515 24 => Ok(Self::JDate),
516 25 => Ok(Self::DTime),
517 26 => Ok(Self::WkDay),
518 27 => Ok(Self::Month),
519 28 => Ok(Self::MoYr),
521 30 => Ok(Self::WkYr),
524 33 => Ok(Self::CC(CC::A)),
525 34 => Ok(Self::CC(CC::B)),
526 35 => Ok(Self::CC(CC::C)),
527 36 => Ok(Self::CC(CC::D)),
528 37 => Ok(Self::CC(CC::E)),
529 38 => Ok(Self::EDate),
530 39 => Ok(Self::SDate),
531 40 => Ok(Self::MTime),
532 41 => Ok(Self::YMDHMS),
533 _ => Err(Error::UnknownFormat { value: source }),
538 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
539 pub struct UncheckedSpec {
547 impl TryFrom<raw::Spec> for UncheckedSpec {
550 fn try_from(raw: raw::Spec) -> Result<Self, Self::Error> {
552 let raw_format = (raw >> 16) as u16;
553 let format = raw_format.try_into()?;
554 let w = ((raw >> 8) & 0xff) as Width;
555 let d = (raw & 0xff) as Decimals;
556 Ok(Self { format, w, d })
560 impl Display for UncheckedSpec {
561 fn fmt(&self, f: &mut Formatter) -> FmtResult {
562 write!(f, "{}{}", self.format, self.w)?;
563 if self.format.takes_decimals() || self.d > 0 {
564 write!(f, ".{}", self.d)?;