use std::{
cmp::min,
fmt::{Display, Error as FmtError, Formatter, Result as FmtResult, Write},
- io::Write as _,
+ io::{Error as IoError, Write as IoWrite},
ops::{Not, RangeInclusive},
str::{from_utf8_unchecked, Chars, FromStr},
sync::LazyLock,
fn missing(&self, f: &mut Formatter<'_>) -> FmtResult {
match self.format.type_ {
+ //Type::P => return self.p(f, -f64::MAX),
Type::RBHex => return self.rbhex(f, -f64::MAX),
_ => (),
}
self.missing(f)
}
}
+
+ pub fn write_binary<W>(&self, w: W) -> Result<bool, IoError>
+ where
+ W: IoWrite,
+ {
+ let Some(number) = self.value.as_number() else {
+ return Ok(false);
+ };
+ match self.format.type_() {
+ Type::P => self.p(w, number).map(|()| true),
+ Type::PK => self.pk(w, number).map(|()| true),
+ _ => Ok(false),
+ }
+ }
+
+ fn bcd(&self, number: Option<f64>, digits: usize) -> (bool, SmallVec<[u8; 16]>) {
+ let legacy = LegacyFormat::new(number.unwrap_or_default(), self.format.d());
+ let len = legacy.len();
+
+ let mut output = SmallVec::new();
+ if len > digits {
+ output.resize(digits.div_ceil(2), 0);
+ (false, output)
+ } else {
+ let mut decimal = SmallString::<[u8; 16]>::new();
+ write!(
+ &mut decimal,
+ "{}{legacy}",
+ Zeros(digits.saturating_sub(len))
+ )
+ .unwrap();
+
+ let mut src = decimal.bytes();
+ for _ in 0..digits / 2 {
+ let d0 = src.next().unwrap() - b'0';
+ let d1 = src.next().unwrap() - b'0';
+ output.push((d0 << 4) + d1);
+ }
+ if digits % 2 != 0 {
+ let d = src.next().unwrap() - b'0';
+ output.push(d << 4);
+ }
+ (true, output)
+ }
+ }
+
+ fn p<W>(&self, mut w: W, number: Option<f64>) -> Result<(), IoError>
+ where
+ W: IoWrite,
+ {
+ let (valid, mut output) = self.bcd(number, self.format.w() * 2 - 1);
+ if valid && number.is_some_and(|number| number < 0.0) {
+ *output.last_mut().unwrap() |= 0xd;
+ } else {
+ *output.last_mut().unwrap() |= 0xf;
+ }
+ w.write_all(&*output)
+ }
+
+ fn pk<W>(&self, mut w: W, number: Option<f64>) -> Result<(), IoError>
+ where
+ W: IoWrite,
+ {
+ let number = match number {
+ Some(number) if number < 0.0 => None,
+ other => other,
+ };
+ let (_valid, output) = self.bcd(number, self.format.w() * 2);
+ w.write_all(&*output)
+ }
}
struct LegacyFormat {
#[cfg(test)]
mod test {
- use std::{fs::File, io::BufRead, path::Path};
+ use std::{fmt::Write, fs::File, io::BufRead, path::Path};
use binrw::io::BufReader;
use encoding_rs::UTF_8;
+ use smallstr::SmallString;
+ use smallvec::SmallVec;
use crate::{
dictionary::Value,
test(&settings, 1.5e10, " ¥2E+010€ ");
test(&settings, -1.5e10, "«¥2E+010€»");
}
+
+ fn test_binhex(name: &str) {
+ let filename = Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join("src/format/testdata")
+ .join(name);
+ let input = BufReader::new(File::open(&filename).unwrap());
+ let mut value = None;
+ let mut value_name = String::new();
+ for (line_number, line) in input.lines().map(|r| r.unwrap()).enumerate() {
+ let line = line.trim();
+ let line_number = line_number + 1;
+ let tokens = StringScanner::new(&line, Syntax::Interactive, true)
+ .unwrapped()
+ .collect::<Vec<_>>();
+ match &tokens[0] {
+ Token::Number(number) => {
+ value = Some(*number);
+ value_name = String::from(line);
+ }
+ Token::End => {
+ value = None;
+ value_name = String::from(line);
+ }
+ Token::Id(id) => {
+ let format: UncheckedFormat =
+ id.0.as_str()
+ .parse::<AbstractFormat>()
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let format: Format = format.try_into().unwrap();
+ assert_eq!(tokens.get(1), Some(&Token::Punct(Punct::Colon)));
+ let expected = tokens[2].as_string().unwrap();
+ let mut actual = SmallVec::<[u8; 16]>::new();
+ assert!(Value::Number(value)
+ .display(format, UTF_8)
+ .write_binary(&mut actual)
+ .unwrap());
+ let mut actual_s = SmallString::<[u8; 32]>::new();
+ for b in actual {
+ write!(&mut actual_s, "{:02x}", b).unwrap();
+ }
+ assert_eq!(
+ expected,
+ &*actual_s,
+ "{}:{line_number}: Error formatting {value_name} as {format}",
+ filename.display()
+ );
+ }
+ _ => panic!(),
+ }
+ }
+ }
+
+ #[test]
+ fn test_p() {
+ test_binhex("p.txt");
+ }
+
+ #[test]
+ fn test_pk() {
+ test_binhex("pk.txt");
+ }
}
--- /dev/null
+#! /usr/bin/python3
+
+import sys
+import pathlib
+
+outputs = {}
+for format in ["P", "PK", "IB", "PIB", "PIBHEX"]:
+ outputs[format] = open(format.lower() + '.txt', 'w')
+
+values = [
+ ".",
+ "2",
+ "11",
+ "123",
+ "1234",
+ "913",
+ "3.14159",
+ "777",
+ "82",
+ "690",
+ "-2",
+ "-11",
+ "-123",
+ "-1234",
+ "-913",
+ "-3.14159",
+ "-777",
+ "-82",
+ "-690",
+ "-.1",
+ "-.5",
+ "-.9",
+ "9999.1",
+ "9999.5",
+ "9999.9",
+ "10000",
+ "18231237",
+ "-9999.1",
+ "-9999.5",
+ "-9999.9",
+ "-10000",
+ "-8231237",
+ "999.1",
+ "999.5",
+ "999.9",
+ "1000",
+ "8231237",
+ "-999.1",
+ "-999.5",
+ "-999.9",
+ "-1000",
+ "-8231237",
+ "99.1",
+ "99.5",
+ "99.9",
+ "100",
+ "821237",
+ "-99.1",
+ "-99.5",
+ "-99.9",
+ "-100",
+ "-831237",
+ "9.1",
+ "9.5",
+ "9.9",
+ "10",
+ "81237",
+ "-9.1",
+ "-9.5",
+ "-9.9",
+ "-10",
+ "-81237",
+ "1.1",
+ "-1.1",
+ "1.5",
+ "-1.5",
+ "1.9",
+ "-1.9",
+]
+
+b = pathlib.Path('binhex-out.expected').read_bytes()
+ofs = 0
+for value in values:
+ for f in outputs.values():
+ f.write(f"{value}\n")
+ x = ofs
+ for d in range(4):
+ for w in range(d + 1, 5):
+ outputs["P"].write(f"P{w}.{d}: \"{b[x:x + w].hex()}\"\n")
+ x += w
+ for d in range(4):
+ for w in range(d + 1, 5):
+ outputs["PK"].write(f"PK{w}.{d}: \"{b[x:x + w].hex()}\"\n")
+ x += w
+ for d in range(11):
+ for w in range([1, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4][d], 5):
+ outputs["IB"].write(f"IB{w}.{d}: \"{b[x:x + w].hex()}\"\n")
+ x += w
+ for d in range(11):
+ for w in range([1, 1, 1, 1, 2, 2, 3, 3, 3, 4, 4][d], 5):
+ outputs["PIB"].write(f"PIB{w}.{d}: \"{b[x:x + w].hex()}\"\n")
+ x += w
+ for w in [2,4,6,8]:
+ outputs["PIBHEX"].write(f"PIBHEX{w}: \"{b[x:x + w]}\"\n")
+ x += w
+
+ ofs += 256