use std::borrow::Cow;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum NumeralSystemKind {
Decimal,
Alphabetic,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NumeralMapping {
pub character: Cow<'static, str>,
pub value: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NumeralSystem {
pub script_code: Cow<'static, str>,
pub name: Cow<'static, str>,
pub kind: NumeralSystemKind,
pub mappings: Vec<NumeralMapping>,
}
impl NumeralSystem {
#[must_use]
pub fn value_of(&self, ch: &str) -> Option<u32> {
tracing::trace!(script = %self.script_code, ch, "numeral lookup");
self.mappings
.iter()
.find(|m| m.character == ch)
.map(|m| m.value)
}
#[must_use]
pub fn char_for(&self, value: u32) -> Option<&str> {
self.mappings
.iter()
.find(|m| m.value == value)
.map(|m| m.character.as_ref())
}
#[must_use]
pub fn string_value(&self, s: &str) -> Option<u32> {
let mut total: u32 = 0;
let mut buf = [0u8; 4];
for ch in s.chars() {
let ch_s = ch.encode_utf8(&mut buf);
total = total.checked_add(self.value_of(ch_s)?)?;
}
Some(total)
}
}
#[must_use]
pub fn devanagari_digits() -> NumeralSystem {
NumeralSystem {
script_code: Cow::Borrowed("Deva"),
name: Cow::Borrowed("Devanagari Digits"),
kind: NumeralSystemKind::Decimal,
mappings: vec![
NumeralMapping {
character: Cow::Borrowed("०"),
value: 0,
},
NumeralMapping {
character: Cow::Borrowed("१"),
value: 1,
},
NumeralMapping {
character: Cow::Borrowed("२"),
value: 2,
},
NumeralMapping {
character: Cow::Borrowed("३"),
value: 3,
},
NumeralMapping {
character: Cow::Borrowed("४"),
value: 4,
},
NumeralMapping {
character: Cow::Borrowed("५"),
value: 5,
},
NumeralMapping {
character: Cow::Borrowed("६"),
value: 6,
},
NumeralMapping {
character: Cow::Borrowed("७"),
value: 7,
},
NumeralMapping {
character: Cow::Borrowed("८"),
value: 8,
},
NumeralMapping {
character: Cow::Borrowed("९"),
value: 9,
},
],
}
}
#[must_use]
pub fn greek_isopsephy() -> NumeralSystem {
NumeralSystem {
script_code: Cow::Borrowed("Grek"),
name: Cow::Borrowed("Greek Isopsephy"),
kind: NumeralSystemKind::Alphabetic,
mappings: vec![
NumeralMapping {
character: Cow::Borrowed("α"),
value: 1,
},
NumeralMapping {
character: Cow::Borrowed("β"),
value: 2,
},
NumeralMapping {
character: Cow::Borrowed("γ"),
value: 3,
},
NumeralMapping {
character: Cow::Borrowed("δ"),
value: 4,
},
NumeralMapping {
character: Cow::Borrowed("ε"),
value: 5,
},
NumeralMapping {
character: Cow::Borrowed("ζ"),
value: 7,
},
NumeralMapping {
character: Cow::Borrowed("η"),
value: 8,
},
NumeralMapping {
character: Cow::Borrowed("θ"),
value: 9,
},
NumeralMapping {
character: Cow::Borrowed("ι"),
value: 10,
},
NumeralMapping {
character: Cow::Borrowed("κ"),
value: 20,
},
NumeralMapping {
character: Cow::Borrowed("λ"),
value: 30,
},
NumeralMapping {
character: Cow::Borrowed("μ"),
value: 40,
},
NumeralMapping {
character: Cow::Borrowed("ν"),
value: 50,
},
NumeralMapping {
character: Cow::Borrowed("ξ"),
value: 60,
},
NumeralMapping {
character: Cow::Borrowed("ο"),
value: 70,
},
NumeralMapping {
character: Cow::Borrowed("π"),
value: 80,
},
NumeralMapping {
character: Cow::Borrowed("ρ"),
value: 100,
},
NumeralMapping {
character: Cow::Borrowed("σ"),
value: 200,
},
NumeralMapping {
character: Cow::Borrowed("ς"), value: 200,
},
NumeralMapping {
character: Cow::Borrowed("τ"),
value: 300,
},
NumeralMapping {
character: Cow::Borrowed("υ"),
value: 400,
},
NumeralMapping {
character: Cow::Borrowed("φ"),
value: 500,
},
NumeralMapping {
character: Cow::Borrowed("χ"),
value: 600,
},
NumeralMapping {
character: Cow::Borrowed("ψ"),
value: 700,
},
NumeralMapping {
character: Cow::Borrowed("ω"),
value: 800,
},
],
}
}
#[must_use]
pub fn babylonian_sexagesimal() -> NumeralSystem {
NumeralSystem {
script_code: Cow::Borrowed("Xsux"),
name: Cow::Borrowed("Babylonian Sexagesimal"),
kind: NumeralSystemKind::Other, mappings: vec![
NumeralMapping {
character: Cow::Borrowed("𒐕"),
value: 1,
}, NumeralMapping {
character: Cow::Borrowed("𒐖"),
value: 2,
},
NumeralMapping {
character: Cow::Borrowed("𒐗"),
value: 3,
},
NumeralMapping {
character: Cow::Borrowed("𒐘"),
value: 4,
},
NumeralMapping {
character: Cow::Borrowed("𒐙"),
value: 5,
},
NumeralMapping {
character: Cow::Borrowed("𒐚"),
value: 6,
},
NumeralMapping {
character: Cow::Borrowed("𒐛"),
value: 7,
},
NumeralMapping {
character: Cow::Borrowed("𒐜"),
value: 8,
},
NumeralMapping {
character: Cow::Borrowed("𒐝"),
value: 9,
},
NumeralMapping {
character: Cow::Borrowed("𒌋"),
value: 10,
}, NumeralMapping {
character: Cow::Borrowed("𒌋𒌋"),
value: 20,
},
NumeralMapping {
character: Cow::Borrowed("𒌍"),
value: 30,
},
],
}
}
#[must_use]
pub fn egyptian_hieroglyphic() -> NumeralSystem {
NumeralSystem {
script_code: Cow::Borrowed("Egyp"),
name: Cow::Borrowed("Egyptian Hieroglyphic Numerals"),
kind: NumeralSystemKind::Other, mappings: vec![
NumeralMapping {
character: Cow::Borrowed("𓏺"),
value: 1,
}, NumeralMapping {
character: Cow::Borrowed("𓎆"),
value: 10,
}, NumeralMapping {
character: Cow::Borrowed("𓍢"),
value: 100,
}, NumeralMapping {
character: Cow::Borrowed("𓆼"),
value: 1000,
}, NumeralMapping {
character: Cow::Borrowed("𓂭"),
value: 10000,
}, NumeralMapping {
character: Cow::Borrowed("𓆐"),
value: 100000,
}, NumeralMapping {
character: Cow::Borrowed("𓁨"),
value: 1000000,
}, ],
}
}
#[must_use]
pub fn chinese_rod_numerals() -> NumeralSystem {
NumeralSystem {
script_code: Cow::Borrowed("Hani"),
name: Cow::Borrowed("Chinese Rod Numerals"),
kind: NumeralSystemKind::Decimal,
mappings: vec![
NumeralMapping {
character: Cow::Borrowed("𝍠"),
value: 1,
},
NumeralMapping {
character: Cow::Borrowed("𝍡"),
value: 2,
},
NumeralMapping {
character: Cow::Borrowed("𝍢"),
value: 3,
},
NumeralMapping {
character: Cow::Borrowed("𝍣"),
value: 4,
},
NumeralMapping {
character: Cow::Borrowed("𝍤"),
value: 5,
},
NumeralMapping {
character: Cow::Borrowed("𝍥"),
value: 6,
},
NumeralMapping {
character: Cow::Borrowed("𝍦"),
value: 7,
},
NumeralMapping {
character: Cow::Borrowed("𝍧"),
value: 8,
},
NumeralMapping {
character: Cow::Borrowed("𝍨"),
value: 9,
},
],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_devanagari_digit_lookup() {
let sys = devanagari_digits();
assert_eq!(sys.value_of("०"), Some(0));
assert_eq!(sys.value_of("५"), Some(5));
assert_eq!(sys.value_of("९"), Some(9));
}
#[test]
fn test_devanagari_char_for_value() {
let sys = devanagari_digits();
assert_eq!(sys.char_for(0), Some("०"));
assert_eq!(sys.char_for(7), Some("७"));
assert_eq!(sys.char_for(10), None); }
#[test]
fn test_devanagari_string_value() {
let sys = devanagari_digits();
assert_eq!(sys.string_value("१२३"), Some(6));
}
#[test]
fn test_devanagari_unknown_char() {
let sys = devanagari_digits();
assert_eq!(sys.value_of("X"), None);
assert_eq!(sys.string_value("X"), None);
}
#[test]
fn test_greek_isopsephy_units() {
let sys = greek_isopsephy();
assert_eq!(sys.value_of("α"), Some(1));
assert_eq!(sys.value_of("ε"), Some(5));
assert_eq!(sys.value_of("θ"), Some(9));
}
#[test]
fn test_greek_isopsephy_tens() {
let sys = greek_isopsephy();
assert_eq!(sys.value_of("ι"), Some(10));
assert_eq!(sys.value_of("π"), Some(80));
}
#[test]
fn test_greek_isopsephy_hundreds() {
let sys = greek_isopsephy();
assert_eq!(sys.value_of("ρ"), Some(100));
assert_eq!(sys.value_of("ω"), Some(800));
}
#[test]
fn test_greek_string_value() {
let sys = greek_isopsephy();
assert_eq!(sys.string_value("αω"), Some(801));
assert_eq!(sys.string_value("πι"), Some(90));
}
#[test]
fn test_greek_char_for() {
let sys = greek_isopsephy();
assert_eq!(sys.char_for(1), Some("α"));
assert_eq!(sys.char_for(80), Some("π"));
assert_eq!(sys.char_for(999), None);
}
#[test]
fn test_numeral_system_serde_roundtrip() {
let sys = devanagari_digits();
let json = serde_json::to_string(&sys).unwrap();
let back: NumeralSystem = serde_json::from_str(&json).unwrap();
assert_eq!(sys, back);
}
#[test]
fn test_greek_isopsephy_serde_roundtrip() {
let sys = greek_isopsephy();
let json = serde_json::to_string(&sys).unwrap();
let back: NumeralSystem = serde_json::from_str(&json).unwrap();
assert_eq!(sys, back);
}
#[test]
fn test_babylonian_units() {
let sys = babylonian_sexagesimal();
assert_eq!(sys.value_of("𒐕"), Some(1));
assert_eq!(sys.value_of("𒐝"), Some(9));
assert_eq!(sys.value_of("𒌋"), Some(10));
}
#[test]
fn test_babylonian_char_for() {
let sys = babylonian_sexagesimal();
assert_eq!(sys.char_for(1), Some("𒐕"));
assert_eq!(sys.char_for(30), Some("𒌍"));
}
#[test]
fn test_babylonian_serde_roundtrip() {
let sys = babylonian_sexagesimal();
let json = serde_json::to_string(&sys).unwrap();
let back: NumeralSystem = serde_json::from_str(&json).unwrap();
assert_eq!(sys, back);
}
#[test]
fn test_egyptian_powers_of_ten() {
let sys = egyptian_hieroglyphic();
assert_eq!(sys.value_of("𓏺"), Some(1));
assert_eq!(sys.value_of("𓎆"), Some(10));
assert_eq!(sys.value_of("𓍢"), Some(100));
assert_eq!(sys.value_of("𓆼"), Some(1000));
assert_eq!(sys.value_of("𓁨"), Some(1000000));
}
#[test]
fn test_egyptian_serde_roundtrip() {
let sys = egyptian_hieroglyphic();
let json = serde_json::to_string(&sys).unwrap();
let back: NumeralSystem = serde_json::from_str(&json).unwrap();
assert_eq!(sys, back);
}
#[test]
fn test_rod_numeral_digits() {
let sys = chinese_rod_numerals();
assert_eq!(sys.value_of("𝍠"), Some(1));
assert_eq!(sys.value_of("𝍨"), Some(9));
assert_eq!(sys.kind, NumeralSystemKind::Decimal);
}
#[test]
fn test_rod_serde_roundtrip() {
let sys = chinese_rod_numerals();
let json = serde_json::to_string(&sys).unwrap();
let back: NumeralSystem = serde_json::from_str(&json).unwrap();
assert_eq!(sys, back);
}
}