use std::{
+ borrow::Cow,
fmt::{Display, Formatter, Result as FmtResult},
ops::{Not, RangeInclusive},
str::{Chars, FromStr},
use unicode_width::UnicodeWidthStr;
use crate::{
- dictionary::VarWidth,
+ dictionary::{Value, VarWidth},
raw::{self, VarType},
};
mod display;
+mod parse;
pub use display::DisplayValue;
#[derive(ThisError, Debug)]
Ok(self)
}
+
+ pub fn default_value(&self) -> Value {
+ match self.var_width() {
+ VarWidth::Numeric => Value::sysmis(),
+ VarWidth::String(width) => Value::String((0..width).map(|_| 0u8).collect()),
+ }
+ }
}
impl Display for Format {
fn new(f: impl Fn(StyleParams) -> NumberStyle) -> Self {
Self(EnumMap::from_fn(f))
}
- fn get(&self, settings: &Settings) -> &NumberStyle {
+ const fn get(&self, settings: &Settings) -> &NumberStyle {
&self.0[settings.into()]
}
}
}
}
fn number_style(&self, type_: Type) -> &NumberStyle {
- static DEFAULT: LazyLock<NumberStyle> =
- LazyLock::new(|| NumberStyle::new("", "", Decimal::Dot, None, false));
+ static DEFAULT: NumberStyle = NumberStyle::new_static("", "", Decimal::Dot, None, false);
match type_ {
Type::F | Type::E => {
static F: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| NumberStyle::new("", "", p.decimal, None, p.leading_zero))
+ StyleSet::new(|p| {
+ NumberStyle::new_static("", "", p.decimal, None, p.leading_zero)
+ })
});
&F.get(self)
}
Type::Comma => {
static COMMA: LazyLock<StyleSet> = LazyLock::new(|| {
StyleSet::new(|p| {
- NumberStyle::new("", "", p.decimal, Some(!p.decimal), p.leading_zero)
+ NumberStyle::new_static("", "", p.decimal, Some(!p.decimal), p.leading_zero)
})
});
&COMMA.get(self)
Type::Dot => {
static DOT: LazyLock<StyleSet> = LazyLock::new(|| {
StyleSet::new(|p| {
- NumberStyle::new("", "", !p.decimal, Some(p.decimal), p.leading_zero)
+ NumberStyle::new_static("", "", !p.decimal, Some(p.decimal), p.leading_zero)
})
});
&DOT.get(self)
}
Type::Dollar => {
static DOLLAR: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| NumberStyle::new("$", "", p.decimal, Some(!p.decimal), false))
+ StyleSet::new(|p| {
+ NumberStyle::new_static("$", "", p.decimal, Some(!p.decimal), false)
+ })
});
&DOLLAR.get(self)
}
Type::Pct => {
static PCT: LazyLock<StyleSet> = LazyLock::new(|| {
- StyleSet::new(|p| NumberStyle::new("", "%", p.decimal, None, false))
+ StyleSet::new(|p| NumberStyle::new_static("", "%", p.decimal, None, false))
});
&PCT.get(self)
}
}
impl NumberStyle {
- fn new(
- prefix: &str,
- suffix: &str,
+ const fn new_static(
+ prefix: &'static str,
+ suffix: &'static str,
decimal: Decimal,
grouping: Option<Decimal>,
leading_zero: bool,
) -> Self {
- // These assertions ensure that zero is correct for `extra_bytes`.
- debug_assert!(prefix.is_ascii());
- debug_assert!(suffix.is_ascii());
+ let neg_prefix = Affix::new_static("-");
+ let prefix = Affix::new_static(prefix);
+ let suffix = Affix::new_static(suffix);
+ let neg_suffix = Affix::new_static("");
+ let extra_bytes = neg_prefix.extra_bytes()
+ + prefix.extra_bytes()
+ + suffix.extra_bytes()
+ + neg_suffix.extra_bytes();
Self {
- neg_prefix: Affix::new("-"),
- prefix: Affix::new(prefix),
- suffix: Affix::new(suffix),
- neg_suffix: Affix::new(""),
+ neg_prefix,
+ prefix,
+ suffix,
+ neg_suffix,
decimal,
grouping,
leading_zero,
- extra_bytes: 0,
+ extra_bytes,
}
}
#[derive(Clone, Debug)]
pub struct Affix {
/// String contents of affix.
- pub s: String,
+ pub s: Cow<'static, str>,
/// Display width in columns (see [unicode_width])
pub width: usize,
}
impl Affix {
- fn new(s: impl Into<String>) -> Self {
- let s = s.into();
+ pub const fn new_static(s: &'static str) -> Self {
+ // [UnicodeWidthStr::width] is non-const, so we use `s.len()` instead,
+ // which is valid if `s` is ASCII.
+ assert!(s.is_ascii());
+ Self {
+ width: s.len(),
+ s: Cow::Borrowed(s),
+ }
+ }
+
+ fn new(s: String) -> Self {
Self {
width: s.width(),
- s,
+ s: Cow::from(s),
}
}
- fn extra_bytes(&self) -> usize {
+ const fn extra_bytes(&self) -> usize {
self.s.len().checked_sub(self.width).unwrap()
}
}
--- /dev/null
+use crate::{
+ dictionary::Value,
+ format::{Format, NumberStyle},
+};
+use encoding_rs::Encoding;
+use thiserror::Error as ThisError;
+
+#[derive(Clone, Debug, ThisError)]
+enum ParseError {}
+
+impl Format {
+ /// Parses `s` as this format. For string formats, `encoding` specifies the
+ /// output encoding.
+ fn parse(&self, s: &str, encoding: &'static Encoding) -> Result<Value, ParseError> {
+ if s.is_empty() {
+ return Ok(self.default_value());
+ }
+ match self.type_ {
+ crate::format::Type::F
+ | crate::format::Type::Comma
+ | crate::format::Type::Dot
+ | crate::format::Type::Dollar
+ | crate::format::Type::Pct
+ | crate::format::Type::E
+ | crate::format::Type::CC(_) => self.parse_number(s),
+ crate::format::Type::N => todo!(),
+ crate::format::Type::Z => todo!(),
+ crate::format::Type::P => todo!(),
+ crate::format::Type::PK => todo!(),
+ crate::format::Type::IB => todo!(),
+ crate::format::Type::PIB => todo!(),
+ crate::format::Type::PIBHex => todo!(),
+ crate::format::Type::RB => todo!(),
+ crate::format::Type::RBHex => todo!(),
+ crate::format::Type::Date => todo!(),
+ crate::format::Type::ADate => todo!(),
+ crate::format::Type::EDate => todo!(),
+ crate::format::Type::JDate => todo!(),
+ crate::format::Type::SDate => todo!(),
+ crate::format::Type::QYr => todo!(),
+ crate::format::Type::MoYr => todo!(),
+ crate::format::Type::WkYr => todo!(),
+ crate::format::Type::DateTime => todo!(),
+ crate::format::Type::YMDHMS => todo!(),
+ crate::format::Type::MTime => todo!(),
+ crate::format::Type::Time => todo!(),
+ crate::format::Type::DTime => todo!(),
+ crate::format::Type::WkDay => todo!(),
+ crate::format::Type::Month => todo!(),
+ crate::format::Type::A => todo!(),
+ crate::format::Type::AHex => todo!(),
+ }
+ }
+
+ fn parse_number(&self, s: &str) -> Result<Value, ParseError> {
+ let s = s.trim();
+ if s.is_empty() || s == "." {
+ return Ok(Value::sysmis());
+ }
+ //let style = NumberStyle
+ todo!()
+ }
+}