use super::super::scanner::Scanner;
use crate::{
haystack::val::Number,
units::{Unit, get_unit},
};
use std::io::{Error, Read};
pub(crate) fn parse_number<R: Read>(scanner: &mut Scanner<R>) -> Result<Number, Error> {
let decimal = parse_decimal(scanner)?;
let mut exponent: Option<String> = None;
let mut unit: Option<&'static Unit> = None;
if !scanner.is_eof && scanner.is_any_of("eE") {
let next = scanner.peek()?;
if next == b'+' || next == b'-' || next.is_ascii_digit() {
exponent = Some(parse_exponent(scanner)?);
}
}
if !scanner.is_eof && is_unit_char(scanner) {
let unit_str = parse_unit(scanner)?;
unit = get_unit(unit_str.as_str());
if unit.is_none() {
return scanner.make_generic_err(&format!("Unit not found: '{unit_str}'"));
}
}
let number = format!(
"{decimal}{exp}",
exp = if let Some(e) = exponent { e } else { "".into() }
);
match number.parse::<f64>() {
Ok(num) => Ok(Number { value: num, unit }),
Err(_) => scanner.make_generic_err(&format!("Invalid number format '{number}'")),
}
}
fn parse_exponent<R: Read>(scanner: &mut Scanner<R>) -> Result<String, Error> {
scanner.expect_and_consume_any_of("eE")?;
let sign = if scanner.is_any_of("+-") {
let sign = String::from_utf8_lossy(&[scanner.cur]).to_string();
scanner.read()?;
sign
} else {
"".to_string()
};
let exponent = parse_decimal(scanner)?;
Ok(format!("e{sign}{exponent}"))
}
fn parse_unit<R: Read>(scanner: &mut Scanner<R>) -> Result<String, Error> {
let mut unit = Vec::new();
while !scanner.is_eof && is_unit_char(scanner) {
unit.push(scanner.cur);
scanner.advance()?
}
Ok(String::from_utf8_lossy(&unit).to_string())
}
pub(crate) fn parse_decimal<R: Read>(scanner: &mut Scanner<R>) -> Result<f64, Error> {
let mut id = Vec::new();
while !scanner.is_eof && (scanner.is_digit() || scanner.is_any_of("_.-")) {
if scanner.cur != b'_' {
id.push(scanner.cur);
}
scanner.advance()?
}
let str = String::from_utf8_lossy(&id).to_string();
match str.parse::<f64>() {
Ok(num) => Ok(num),
Err(err) => {
scanner.make_generic_err(&format!("Invalid decimal '{str}'. Parse error: {err}"))
}
}
}
pub(crate) fn parse_neg_inf<R: Read>(scanner: &mut Scanner<R>) -> Result<Number, Error> {
scanner.expect_and_consume(b'-')?;
scanner.expect_and_consume_seq("INF")?;
Ok(f64::NEG_INFINITY.into())
}
fn is_unit_char<R: Read>(scanner: &mut Scanner<R>) -> bool {
scanner.is_alpha() || scanner.is_any_of("$/%_") || scanner.cur >= 128
}
#[cfg(test)]
mod test {
use super::{parse_decimal, parse_neg_inf, parse_number};
use crate::{haystack::val::Number, units::get_unit_or_default};
use std::io::Cursor;
#[test]
fn test_zinc_parse_decimal() {
let mut input = Cursor::new("120_330.4".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_decimal(&mut scanner);
assert_eq!(num.ok(), Some(120_330.4))
}
#[test]
fn test_zinc_parse_decimal_bad() {
let mut input = Cursor::new(".-120_330.4".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_decimal(&mut scanner);
assert!(num.is_err())
}
#[test]
fn test_zinc_parse_number_neg_inf() {
let mut input = Cursor::new("-INF".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_neg_inf(&mut scanner);
assert_eq!(num.ok(), Some(Number::from(f64::NEG_INFINITY)));
{
let mut input = Cursor::new("-I".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_neg_inf(&mut scanner);
assert!(num.is_err())
}
}
#[test]
fn test_zinc_parse_number() {
let mut input = Cursor::new("42.0e+1%".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_number(&mut scanner);
assert_eq!(
num.ok(),
Some(Number::make_with_unit(42.0e+1, get_unit_or_default("%")))
);
{
let mut input = Cursor::new("1".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_number(&mut scanner);
assert_eq!(num.ok(), Some(Number::make(1.0)));
}
{
let mut input = Cursor::new("-100_233.23".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_number(&mut scanner);
assert_eq!(num.ok(), Some(Number::make(-100233.23)));
}
}
#[test]
fn test_zinc_parse_number_utf8_unit() {
let mut input = Cursor::new("1.4m³_gas".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_number(&mut scanner);
assert_eq!(
num.ok(),
Some(Number::make_with_unit(1.4, get_unit_or_default("m³_gas")))
);
}
#[test]
fn test_zinc_parse_number_bad() {
let mut input = Cursor::new("42.0e".as_bytes());
let mut scanner = super::Scanner::make(&mut input).expect("Scanner");
let num = parse_number(&mut scanner);
assert!(num.is_err());
}
}