use oxinum_core::{OxiNumError, OxiNumResult};
use oxinum_float::DBig;
use oxinum_int::IBig;
use oxinum_rational::RBig;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq)]
pub enum ParsedNumber {
Integer(IBig),
Rational(RBig),
Float(DBig),
}
impl std::fmt::Display for ParsedNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Integer(v) => write!(f, "{v}"),
Self::Rational(v) => write!(f, "{v}"),
Self::Float(v) => write!(f, "{v}"),
}
}
}
pub fn parse(s: &str) -> OxiNumResult<ParsedNumber> {
let trimmed = s.trim();
if trimmed.is_empty() {
return Err(OxiNumError::Parse("empty input".into()));
}
if trimmed.contains('/') {
let r = RBig::from_str(trimmed)
.map_err(|e| OxiNumError::Parse(format!("invalid rational '{trimmed}': {e}").into()))?;
return Ok(ParsedNumber::Rational(r));
}
if trimmed.contains('.') || trimmed.contains('e') || trimmed.contains('E') {
let f = DBig::from_str(trimmed)
.map_err(|e| OxiNumError::Parse(format!("invalid float '{trimmed}': {e}").into()))?;
return Ok(ParsedNumber::Float(f));
}
let i = IBig::from_str(trimmed)
.map_err(|e| OxiNumError::Parse(format!("invalid integer '{trimmed}': {e}").into()))?;
Ok(ParsedNumber::Integer(i))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_integer() {
match parse("42").expect("ok") {
ParsedNumber::Integer(v) => assert_eq!(v, IBig::from(42)),
other => panic!("expected Integer, got {other:?}"),
}
}
#[test]
fn parse_negative_integer() {
match parse("-7").expect("ok") {
ParsedNumber::Integer(v) => assert_eq!(v, IBig::from(-7)),
other => panic!("expected Integer, got {other:?}"),
}
}
#[test]
fn parse_rational() {
match parse("3/4").expect("ok") {
ParsedNumber::Rational(v) => {
assert_eq!(v.numerator(), &IBig::from(3));
}
other => panic!("expected Rational, got {other:?}"),
}
}
#[test]
fn parse_float() {
match parse("1.25").expect("ok") {
ParsedNumber::Float(v) => assert!(v.to_string().starts_with("1.25")),
other => panic!("expected Float, got {other:?}"),
}
}
#[test]
fn parse_scientific() {
match parse("1.5e3").expect("ok") {
ParsedNumber::Float(_) => {}
other => panic!("expected Float, got {other:?}"),
}
}
#[test]
fn parse_empty_errors() {
assert!(parse("").is_err());
assert!(parse(" ").is_err());
}
#[test]
fn parse_invalid_errors() {
assert!(parse("hello").is_err());
assert!(parse("1/0/2").is_err());
}
#[test]
fn parse_trims_whitespace() {
match parse(" 42 ").expect("ok") {
ParsedNumber::Integer(v) => assert_eq!(v, IBig::from(42)),
other => panic!("expected Integer, got {other:?}"),
}
}
#[test]
fn parsed_number_display() {
assert_eq!(parse("42").expect("ok").to_string(), "42");
assert_eq!(parse("3/4").expect("ok").to_string(), "3/4");
assert!(parse("1.25").expect("ok").to_string().starts_with("1.25"));
}
}