commit 55b870b7b136c61c4a5ec2d2c1888fe94c1ff9d1 Author: FliegendeWurst <2012gdwu@web.de> Date: Mon May 18 09:55:43 2020 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7ce308f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "decimal-code" +version = "0.1.0" +dependencies = [ + "itertools", +] + +[[package]] +name = "either" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ed49c44 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "decimal-code" +version = "0.1.0" +authors = ["FliegendeWurst <2012gdwu@web.de>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +itertools = "0.9.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f7c370c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,166 @@ +use itertools::join; + +use std::env; +use std::error::Error; +use std::{convert::{TryInto, TryFrom}, io::{self, BufRead, stdin}, iter::once}; + +fn main() -> Result<(), Box> { + let args = env::args().skip(1).collect::>(); + if args.is_empty() { + // batch mode + for line in stdin().lock().lines() { + let line = line?; + let mut parts = line.split_ascii_whitespace(); + let format = parts.next().ok_or(io::Error::new(io::ErrorKind::Other, "no encoding"))?; + let value = parts.next().ok_or(io::Error::new(io::ErrorKind::Other, "no value"))?; + let number = match format { + "decimal" => parse_decimal(value)?, + "bcd" => parse_bcd(value)?, + "aiken" => parse_aiken(value)?, + "stibitz" => parse_stibitz(value)?, + _ => Err("unknown encoding")? + }; + let x = convert(&number); + println!("{}\t{}\t{}\t{}", x.0, x.1, x.2, x.3); + } + Ok(()) + } else { + Err("too many arguments".into()) + } +} + +fn convert(number: &Decimal) -> (String, String, String, String) { + ( + number.format_decimal(), + number.format_bcd(), + number.format_aiken(), + number.format_stibitz() + ) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct Decimal { + digits: Vec, + digits_fractional: Vec +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +enum Digit { + Zero, One, Two, Three, Four, Five, Six, Seven, Eight, Nine +} + +use Digit::*; + +impl TryFrom for Digit { + type Error = Box; + + fn try_from(x: char) -> Result { + match x { + '0' => Ok(Zero), + '1' => Ok(One), + '2' => Ok(Two), + '3' => Ok(Three), + '4' => Ok(Four), + '5' => Ok(Five), + '6' => Ok(Six), + '7' => Ok(Seven), + '8' => Ok(Eight), + '9' => Ok(Nine), + x => Err(format!("unknown digit {:?}", x).into()) + } + } +} + +impl Digit { + fn value(&self) -> usize { + match self { + Zero => 0, + One => 1, + Two => 2, + Three => 3, + Four => 4, + Five => 5, + Six => 6, + Seven => 7, + Eight => 8, + Nine => 9 + } + } +} + +fn parse_decimal(value: &str) -> Result> { + let mut digits = Vec::new(); + let mut digits_fractional = Vec::new(); + + let mut fractional = false; + for c in value.chars() { + if !fractional && (c == '.' || c == ',') { + fractional = true; + continue; + } + let digit: Digit = c.try_into()?; + if !fractional { + digits.push(digit); + } else { + digits_fractional.push(digit); + } + } + + Ok(Decimal { + digits, digits_fractional + }) +} + +#[test] +fn parse_decimal_test() { + assert_eq!(parse_decimal("42.35").unwrap(), Decimal { digits: vec![Four, Two], digits_fractional: vec![Three, Five] }); +} + +fn parse_bcd(value: &str) -> Result> { + todo!("BCD parsing"); +} + +fn parse_aiken(value: &str) -> Result> { + todo!("AIKEN parsing"); +} + +fn parse_stibitz(value: &str) -> Result> { + todo!("STIBITZ parsing"); +} + +impl Decimal { + fn format_decimal(&self) -> String { + self.format_specified(&["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]) + } + + fn format_bcd(&self) -> String { + self.format_specified(&["0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001"]) + } + + fn format_aiken(&self) -> String { + self.format_specified(&["0000", "0001", "0010", "0011", "0100", "1011", "1100", "1101", "1110", "1111"]) + } + + fn format_stibitz(&self) -> String { + self.format_specified(&["0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100"]) + } + + fn format_specified(&self, table: &[&str]) -> String { + if self.digits_fractional.is_empty() { + join(self.digits.iter().map(|x| table[x.value()]), " ") + } else { + join( + self.digits.iter().map(|x| table[x.value()]) + .chain(once(".")) + .chain(self.digits_fractional.iter().map(|x| table[x.value()])), + " " + ) + } + } +} + +#[test] +fn format_decimal_test() { + let number = Decimal { digits: vec![Four, Four], digits_fractional: vec![Five, One] }; + assert_eq!(convert(&number), ("4 4 . 5 1".into(), "0100 0100 . 0101 0001".into(), "0100 0100 . 1011 0001".into(), "0111 0111 . 1000 0100".into())); +}