mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 12:37:49 +00:00
uucore: format: num_parser: Turn parser into a trait
We call the function extended_parse, so that we do not clash with other parsing functions in other traits. - Also implement parser for ExtendedBigDecimal (straightforward). - Base doesn't need to be public anymore. - Rename the error to ExtendedParserError.
This commit is contained in:
parent
8bbec16115
commit
40a7c65980
2 changed files with 293 additions and 288 deletions
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::set_exit_code,
|
error::set_exit_code,
|
||||||
features::format::num_parser::{ParseError, ParsedNumber},
|
features::format::num_parser::{ExtendedParser, ExtendedParserError},
|
||||||
quoting_style::{Quotes, QuotingStyle, escape_name},
|
quoting_style::{Quotes, QuotingStyle, escape_name},
|
||||||
show_error, show_warning,
|
show_error, show_warning,
|
||||||
};
|
};
|
||||||
|
@ -56,7 +56,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
};
|
};
|
||||||
match next {
|
match next {
|
||||||
FormatArgument::UnsignedInt(n) => *n,
|
FormatArgument::UnsignedInt(n) => *n,
|
||||||
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_u64(s), s),
|
FormatArgument::Unparsed(s) => extract_value(u64::extended_parse(s), s),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
};
|
};
|
||||||
match next {
|
match next {
|
||||||
FormatArgument::SignedInt(n) => *n,
|
FormatArgument::SignedInt(n) => *n,
|
||||||
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_i64(s), s),
|
FormatArgument::Unparsed(s) => extract_value(i64::extended_parse(s), s),
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
};
|
};
|
||||||
match next {
|
match next {
|
||||||
FormatArgument::Float(n) => *n,
|
FormatArgument::Float(n) => *n,
|
||||||
FormatArgument::Unparsed(s) => extract_value(ParsedNumber::parse_f64(s), s),
|
FormatArgument::Unparsed(s) => extract_value(f64::extended_parse(s), s),
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ impl<'a, T: Iterator<Item = &'a FormatArgument>> ArgumentIter<'a> for T {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_value<T: Default>(p: Result<T, ParseError<'_, T>>, input: &str) -> T {
|
fn extract_value<T: Default>(p: Result<T, ExtendedParserError<'_, T>>, input: &str) -> T {
|
||||||
match p {
|
match p {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -103,15 +103,15 @@ fn extract_value<T: Default>(p: Result<T, ParseError<'_, T>>, input: &str) -> T
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
match e {
|
match e {
|
||||||
ParseError::Overflow => {
|
ExtendedParserError::Overflow => {
|
||||||
show_error!("{}: Numerical result out of range", input.quote());
|
show_error!("{}: Numerical result out of range", input.quote());
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
ParseError::NotNumeric => {
|
ExtendedParserError::NotNumeric => {
|
||||||
show_error!("{}: expected a numeric value", input.quote());
|
show_error!("{}: expected a numeric value", input.quote());
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
ParseError::PartialMatch(v, rest) => {
|
ExtendedParserError::PartialMatch(v, rest) => {
|
||||||
let bytes = input.as_encoded_bytes();
|
let bytes = input.as_encoded_bytes();
|
||||||
if !bytes.is_empty() && bytes[0] == b'\'' {
|
if !bytes.is_empty() && bytes[0] == b'\'' {
|
||||||
show_warning!(
|
show_warning!(
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
// spell-checker:ignore powf copysign prec inity bigdecimal extendedbigdecimal biguint
|
// spell-checker:ignore powf copysign prec inity bigdecimal extendedbigdecimal biguint
|
||||||
|
|
||||||
use bigdecimal::{
|
use bigdecimal::{
|
||||||
num_bigint::{BigInt, BigUint, Sign},
|
|
||||||
BigDecimal,
|
BigDecimal,
|
||||||
|
num_bigint::{BigInt, BigUint, Sign},
|
||||||
};
|
};
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
|
@ -18,7 +18,7 @@ use crate::format::extendedbigdecimal::ExtendedBigDecimal;
|
||||||
|
|
||||||
/// Base for number parsing
|
/// Base for number parsing
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum Base {
|
enum Base {
|
||||||
/// Binary base
|
/// Binary base
|
||||||
Binary = 2,
|
Binary = 2,
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ impl Base {
|
||||||
|
|
||||||
/// Type returned if a number could not be parsed in its entirety
|
/// Type returned if a number could not be parsed in its entirety
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ParseError<'a, T> {
|
pub enum ExtendedParserError<'a, T> {
|
||||||
/// The input as a whole makes no sense
|
/// The input as a whole makes no sense
|
||||||
NotNumeric,
|
NotNumeric,
|
||||||
/// The beginning of the input made sense and has been parsed,
|
/// The beginning of the input made sense and has been parsed,
|
||||||
|
@ -65,11 +65,14 @@ pub enum ParseError<'a, T> {
|
||||||
Overflow,
|
Overflow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> ParseError<'a, T> {
|
impl<'a, T> ExtendedParserError<'a, T> {
|
||||||
fn map<U>(self, f: impl FnOnce(T, &'a str) -> ParseError<'a, U>) -> ParseError<'a, U> {
|
fn map<U>(
|
||||||
|
self,
|
||||||
|
f: impl FnOnce(T, &'a str) -> ExtendedParserError<'a, U>,
|
||||||
|
) -> ExtendedParserError<'a, U> {
|
||||||
match self {
|
match self {
|
||||||
Self::NotNumeric => ParseError::NotNumeric,
|
Self::NotNumeric => ExtendedParserError::NotNumeric,
|
||||||
Self::Overflow => ParseError::Overflow,
|
Self::Overflow => ExtendedParserError::Overflow,
|
||||||
Self::PartialMatch(v, s) => f(v, s),
|
Self::PartialMatch(v, s) => f(v, s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,375 +80,377 @@ impl<'a, T> ParseError<'a, T> {
|
||||||
|
|
||||||
/// A number parser for binary, octal, decimal, hexadecimal and single characters.
|
/// A number parser for binary, octal, decimal, hexadecimal and single characters.
|
||||||
///
|
///
|
||||||
/// TODO: we just keep an ExtendedBigDecimal internally, so we don't really need this
|
/// It is implemented for `u64`/`i64`, where no fractional part is parsed,
|
||||||
/// struct actually.
|
/// and `f64` float, where octal is not allowed.
|
||||||
///
|
pub trait ExtendedParser {
|
||||||
/// If the fractional part cannot be represented on a `u64`, parsing continues
|
// We pick a hopefully different name for our parser, to avoid clash with standard traits.
|
||||||
/// silently by ignoring non-significant digits.
|
fn extended_parse(input: &str) -> Result<Self, ExtendedParserError<'_, Self>>
|
||||||
pub struct ParsedNumber {
|
where
|
||||||
number: ExtendedBigDecimal,
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParsedNumber {
|
impl ExtendedParser for i64 {
|
||||||
fn into_i64(self) -> Option<i64> {
|
|
||||||
match self.number {
|
|
||||||
ExtendedBigDecimal::BigDecimal(bd) => {
|
|
||||||
let (digits, scale) = bd.into_bigint_and_scale();
|
|
||||||
if scale == 0 {
|
|
||||||
i64::try_from(digits).ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExtendedBigDecimal::MinusZero => Some(0),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a number as i64. No fractional part is allowed.
|
/// Parse a number as i64. No fractional part is allowed.
|
||||||
pub fn parse_i64(input: &str) -> Result<i64, ParseError<'_, i64>> {
|
fn extended_parse(input: &str) -> Result<i64, ExtendedParserError<'_, i64>> {
|
||||||
match Self::parse(input, true) {
|
fn into_i64(ebd: ExtendedBigDecimal) -> Option<i64> {
|
||||||
Ok(v) => v.into_i64().ok_or(ParseError::Overflow),
|
match ebd {
|
||||||
|
ExtendedBigDecimal::BigDecimal(bd) => {
|
||||||
|
let (digits, scale) = bd.into_bigint_and_scale();
|
||||||
|
if scale == 0 {
|
||||||
|
i64::try_from(digits).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExtendedBigDecimal::MinusZero => Some(0),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match parse(input, true) {
|
||||||
|
Ok(v) => into_i64(v).ok_or(ExtendedParserError::Overflow),
|
||||||
Err(e) => Err(e.map(|v, rest| {
|
Err(e) => Err(e.map(|v, rest| {
|
||||||
v.into_i64()
|
into_i64(v)
|
||||||
.map(|v| ParseError::PartialMatch(v, rest))
|
.map(|v| ExtendedParserError::PartialMatch(v, rest))
|
||||||
.unwrap_or(ParseError::Overflow)
|
.unwrap_or(ExtendedParserError::Overflow)
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn into_u64(self) -> Option<u64> {
|
impl ExtendedParser for u64 {
|
||||||
match self.number {
|
|
||||||
ExtendedBigDecimal::BigDecimal(bd) => {
|
|
||||||
let (digits, scale) = bd.into_bigint_and_scale();
|
|
||||||
if scale == 0 {
|
|
||||||
u64::try_from(digits).ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a number as u64. No fractional part is allowed.
|
/// Parse a number as u64. No fractional part is allowed.
|
||||||
pub fn parse_u64(input: &str) -> Result<u64, ParseError<'_, u64>> {
|
fn extended_parse(input: &str) -> Result<u64, ExtendedParserError<'_, u64>> {
|
||||||
match Self::parse(input, true) {
|
fn into_u64(ebd: ExtendedBigDecimal) -> Option<u64> {
|
||||||
Ok(v) => v.into_u64().ok_or(ParseError::Overflow),
|
match ebd {
|
||||||
|
ExtendedBigDecimal::BigDecimal(bd) => {
|
||||||
|
let (digits, scale) = bd.into_bigint_and_scale();
|
||||||
|
if scale == 0 {
|
||||||
|
u64::try_from(digits).ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match parse(input, true) {
|
||||||
|
Ok(v) => into_u64(v).ok_or(ExtendedParserError::Overflow),
|
||||||
Err(e) => Err(e.map(|v, rest| {
|
Err(e) => Err(e.map(|v, rest| {
|
||||||
v.into_u64()
|
into_u64(v)
|
||||||
.map(|v| ParseError::PartialMatch(v, rest))
|
.map(|v| ExtendedParserError::PartialMatch(v, rest))
|
||||||
.unwrap_or(ParseError::Overflow)
|
.unwrap_or(ExtendedParserError::Overflow)
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn into_f64(self) -> f64 {
|
impl ExtendedParser for f64 {
|
||||||
match self.number {
|
|
||||||
ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(),
|
|
||||||
ExtendedBigDecimal::MinusZero => -0.0,
|
|
||||||
ExtendedBigDecimal::Nan => f64::NAN,
|
|
||||||
ExtendedBigDecimal::MinusNan => -f64::NAN,
|
|
||||||
ExtendedBigDecimal::Infinity => f64::INFINITY,
|
|
||||||
ExtendedBigDecimal::MinusInfinity => -f64::INFINITY,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a number as f64
|
/// Parse a number as f64
|
||||||
pub fn parse_f64(input: &str) -> Result<f64, ParseError<'_, f64>> {
|
fn extended_parse(input: &str) -> Result<f64, ExtendedParserError<'_, f64>> {
|
||||||
match Self::parse(input, false) {
|
// TODO: This is generic, so this should probably be implemented as an ExtendedBigDecimal trait (ToPrimitive).
|
||||||
Ok(v) => Ok(v.into_f64()),
|
fn into_f64(ebd: ExtendedBigDecimal) -> f64 {
|
||||||
Err(e) => Err(e.map(|v, rest| ParseError::PartialMatch(v.into_f64(), rest))),
|
match ebd {
|
||||||
}
|
ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(),
|
||||||
}
|
ExtendedBigDecimal::MinusZero => -0.0,
|
||||||
|
ExtendedBigDecimal::Nan => f64::NAN,
|
||||||
fn parse_special_value(input: &str, negative: bool) -> Result<Self, ParseError<'_, Self>> {
|
ExtendedBigDecimal::MinusNan => -f64::NAN,
|
||||||
let prefix = input
|
ExtendedBigDecimal::Infinity => f64::INFINITY,
|
||||||
.chars()
|
ExtendedBigDecimal::MinusInfinity => -f64::INFINITY,
|
||||||
.take(3)
|
|
||||||
.map(|c| c.to_ascii_lowercase())
|
|
||||||
.collect::<String>();
|
|
||||||
let special = Self {
|
|
||||||
number: match prefix.as_str() {
|
|
||||||
"inf" => {
|
|
||||||
if negative {
|
|
||||||
ExtendedBigDecimal::MinusInfinity
|
|
||||||
} else {
|
|
||||||
ExtendedBigDecimal::Infinity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"nan" => {
|
|
||||||
if negative {
|
|
||||||
ExtendedBigDecimal::MinusNan
|
|
||||||
} else {
|
|
||||||
ExtendedBigDecimal::Nan
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return Err(ParseError::NotNumeric),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if input.len() == 3 {
|
|
||||||
Ok(special)
|
|
||||||
} else {
|
|
||||||
Err(ParseError::PartialMatch(special, &input[3..]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
|
||||||
fn parse(input: &str, integral_only: bool) -> Result<Self, ParseError<'_, Self>> {
|
|
||||||
// Parse the "'" prefix separately
|
|
||||||
if let Some(rest) = input.strip_prefix('\'') {
|
|
||||||
let mut chars = rest.char_indices().fuse();
|
|
||||||
let v = chars.next().map(|(_, c)| Self {
|
|
||||||
number: ExtendedBigDecimal::BigDecimal(u32::from(c).into()),
|
|
||||||
});
|
|
||||||
return match (v, chars.next()) {
|
|
||||||
(Some(v), None) => Ok(v),
|
|
||||||
(Some(v), Some((i, _))) => Err(ParseError::PartialMatch(v, &rest[i..])),
|
|
||||||
(None, _) => Err(ParseError::NotNumeric),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let trimmed_input = input.trim_ascii_start();
|
|
||||||
|
|
||||||
// Initial minus sign
|
|
||||||
let (negative, unsigned) = if let Some(trimmed_input) = trimmed_input.strip_prefix('-') {
|
|
||||||
(true, trimmed_input)
|
|
||||||
} else {
|
|
||||||
(false, trimmed_input)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse an optional base prefix ("0b" / "0B" / "0" / "0x" / "0X"). "0" is octal unless a
|
|
||||||
// fractional part is allowed in which case it is an insignificant leading 0. A "0" prefix
|
|
||||||
// will not be consumed in case the parsable string contains only "0": the leading extra "0"
|
|
||||||
// will have no influence on the result.
|
|
||||||
let (base, rest) = if let Some(rest) = unsigned.strip_prefix('0') {
|
|
||||||
if let Some(rest) = rest.strip_prefix(['b', 'B']) {
|
|
||||||
(Base::Binary, rest)
|
|
||||||
} else if let Some(rest) = rest.strip_prefix(['x', 'X']) {
|
|
||||||
(Base::Hexadecimal, rest)
|
|
||||||
} else if integral_only {
|
|
||||||
(Base::Octal, unsigned)
|
|
||||||
} else {
|
|
||||||
(Base::Decimal, unsigned)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match parse(input, false) {
|
||||||
|
Ok(v) => Ok(into_f64(v)),
|
||||||
|
Err(e) => Err(e.map(|v, rest| ExtendedParserError::PartialMatch(into_f64(v), rest))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_special_value(
|
||||||
|
input: &str,
|
||||||
|
negative: bool,
|
||||||
|
) -> Result<ExtendedBigDecimal, ExtendedParserError<'_, ExtendedBigDecimal>> {
|
||||||
|
let prefix = input
|
||||||
|
.chars()
|
||||||
|
.take(3)
|
||||||
|
.map(|c| c.to_ascii_lowercase())
|
||||||
|
.collect::<String>();
|
||||||
|
let special = match prefix.as_str() {
|
||||||
|
"inf" => {
|
||||||
|
if negative {
|
||||||
|
ExtendedBigDecimal::MinusInfinity
|
||||||
|
} else {
|
||||||
|
ExtendedBigDecimal::Infinity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"nan" => {
|
||||||
|
if negative {
|
||||||
|
ExtendedBigDecimal::MinusNan
|
||||||
|
} else {
|
||||||
|
ExtendedBigDecimal::Nan
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(ExtendedParserError::NotNumeric),
|
||||||
|
};
|
||||||
|
if input.len() == 3 {
|
||||||
|
Ok(special)
|
||||||
|
} else {
|
||||||
|
Err(ExtendedParserError::PartialMatch(special, &input[3..]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cognitive_complexity)]
|
||||||
|
fn parse(
|
||||||
|
input: &str,
|
||||||
|
integral_only: bool,
|
||||||
|
) -> Result<ExtendedBigDecimal, ExtendedParserError<'_, ExtendedBigDecimal>> {
|
||||||
|
// Parse the "'" prefix separately
|
||||||
|
if let Some(rest) = input.strip_prefix('\'') {
|
||||||
|
let mut chars = rest.char_indices().fuse();
|
||||||
|
let v = chars
|
||||||
|
.next()
|
||||||
|
.map(|(_, c)| ExtendedBigDecimal::BigDecimal(u32::from(c).into()));
|
||||||
|
return match (v, chars.next()) {
|
||||||
|
(Some(v), None) => Ok(v),
|
||||||
|
(Some(v), Some((i, _))) => Err(ExtendedParserError::PartialMatch(v, &rest[i..])),
|
||||||
|
(None, _) => Err(ExtendedParserError::NotNumeric),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let trimmed_input = input.trim_ascii_start();
|
||||||
|
|
||||||
|
// Initial minus sign
|
||||||
|
let (negative, unsigned) = if let Some(trimmed_input) = trimmed_input.strip_prefix('-') {
|
||||||
|
(true, trimmed_input)
|
||||||
|
} else {
|
||||||
|
(false, trimmed_input)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse an optional base prefix ("0b" / "0B" / "0" / "0x" / "0X"). "0" is octal unless a
|
||||||
|
// fractional part is allowed in which case it is an insignificant leading 0. A "0" prefix
|
||||||
|
// will not be consumed in case the parsable string contains only "0": the leading extra "0"
|
||||||
|
// will have no influence on the result.
|
||||||
|
let (base, rest) = if let Some(rest) = unsigned.strip_prefix('0') {
|
||||||
|
if let Some(rest) = rest.strip_prefix(['b', 'B']) {
|
||||||
|
(Base::Binary, rest)
|
||||||
|
} else if let Some(rest) = rest.strip_prefix(['x', 'X']) {
|
||||||
|
(Base::Hexadecimal, rest)
|
||||||
|
} else if integral_only {
|
||||||
|
(Base::Octal, unsigned)
|
||||||
} else {
|
} else {
|
||||||
(Base::Decimal, unsigned)
|
(Base::Decimal, unsigned)
|
||||||
};
|
|
||||||
if rest.is_empty() {
|
|
||||||
return Err(ParseError::NotNumeric);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
(Base::Decimal, unsigned)
|
||||||
|
};
|
||||||
|
if rest.is_empty() {
|
||||||
|
return Err(ExtendedParserError::NotNumeric);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the integral part of the number
|
// Parse the integral part of the number
|
||||||
let mut chars = rest.chars().enumerate().fuse().peekable();
|
let mut chars = rest.chars().enumerate().fuse().peekable();
|
||||||
let mut digits = BigUint::zero();
|
let mut digits = BigUint::zero();
|
||||||
let mut scale = 0i64;
|
let mut scale = 0i64;
|
||||||
|
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
|
||||||
|
chars.next();
|
||||||
|
digits = digits * base as u8 + d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the fractional part of the number if there can be one and the input contains
|
||||||
|
// a '.' decimal separator.
|
||||||
|
if matches!(chars.peek(), Some(&(_, '.')))
|
||||||
|
&& matches!(base, Base::Decimal | Base::Hexadecimal)
|
||||||
|
&& !integral_only
|
||||||
|
{
|
||||||
|
chars.next();
|
||||||
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
|
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
|
||||||
chars.next();
|
chars.next();
|
||||||
digits = digits * base as u8 + d;
|
(digits, scale) = (digits * base as u8 + d, scale + 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the fractional part of the number if there can be one and the input contains
|
// If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful
|
||||||
// a '.' decimal separator.
|
if let Some((0, _)) = chars.peek() {
|
||||||
if matches!(chars.peek(), Some(&(_, '.')))
|
if integral_only {
|
||||||
&& matches!(base, Base::Decimal | Base::Hexadecimal)
|
return Err(ExtendedParserError::NotNumeric);
|
||||||
&& !integral_only
|
|
||||||
{
|
|
||||||
chars.next();
|
|
||||||
while let Some(d) = chars.peek().and_then(|&(_, c)| base.digit(c)) {
|
|
||||||
chars.next();
|
|
||||||
(digits, scale) = (digits * base as u8 + d, scale + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If nothing has been parsed, check if this is a special value, or declare the parsing unsuccessful
|
|
||||||
if let Some((0, _)) = chars.peek() {
|
|
||||||
if integral_only {
|
|
||||||
return Err(ParseError::NotNumeric);
|
|
||||||
} else {
|
|
||||||
return Self::parse_special_value(unsigned, negative);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Might be nice to implement a ExtendedBigDecimal copysign or negation function to move away some of this logic...
|
|
||||||
let ebd = if digits == BigUint::zero() && negative {
|
|
||||||
ExtendedBigDecimal::MinusZero
|
|
||||||
} else {
|
} else {
|
||||||
let sign = if negative { Sign::Minus } else { Sign::Plus };
|
return parse_special_value(unsigned, negative);
|
||||||
let signed_digits = BigInt::from_biguint(sign, digits);
|
}
|
||||||
let bd = if scale == 0 {
|
}
|
||||||
BigDecimal::from_bigint(signed_digits, 0)
|
|
||||||
} else if base == Base::Decimal {
|
// TODO: Might be nice to implement a ExtendedBigDecimal copysign or negation function to move away some of this logic...
|
||||||
BigDecimal::from_bigint(signed_digits, scale)
|
let ebd = if digits == BigUint::zero() && negative {
|
||||||
} else {
|
ExtendedBigDecimal::MinusZero
|
||||||
// Base is not 10, init at scale 0 then divide by base**scale.
|
} else {
|
||||||
BigDecimal::from_bigint(signed_digits, 0) / (base as u64).pow(scale as u32)
|
let sign = if negative { Sign::Minus } else { Sign::Plus };
|
||||||
};
|
let signed_digits = BigInt::from_biguint(sign, digits);
|
||||||
ExtendedBigDecimal::BigDecimal(bd)
|
let bd = if scale == 0 {
|
||||||
|
BigDecimal::from_bigint(signed_digits, 0)
|
||||||
|
} else if base == Base::Decimal {
|
||||||
|
BigDecimal::from_bigint(signed_digits, scale)
|
||||||
|
} else {
|
||||||
|
// Base is not 10, init at scale 0 then divide by base**scale.
|
||||||
|
BigDecimal::from_bigint(signed_digits, 0) / (base as u64).pow(scale as u32)
|
||||||
};
|
};
|
||||||
|
ExtendedBigDecimal::BigDecimal(bd)
|
||||||
|
};
|
||||||
|
|
||||||
// Return what has been parsed so far. It there are extra characters, mark the
|
// Return what has been parsed so far. It there are extra characters, mark the
|
||||||
// parsing as a partial match.
|
// parsing as a partial match.
|
||||||
let parsed = Self { number: ebd };
|
if let Some((first_unparsed, _)) = chars.next() {
|
||||||
if let Some((first_unparsed, _)) = chars.next() {
|
Err(ExtendedParserError::PartialMatch(
|
||||||
Err(ParseError::PartialMatch(parsed, &rest[first_unparsed..]))
|
ebd,
|
||||||
} else {
|
&rest[first_unparsed..],
|
||||||
Ok(parsed)
|
))
|
||||||
}
|
} else {
|
||||||
|
Ok(ebd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{ParseError, ParsedNumber};
|
use super::{ExtendedParser, ExtendedParserError};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decimal_u64() {
|
fn test_decimal_u64() {
|
||||||
assert_eq!(Ok(123), ParsedNumber::parse_u64("123"));
|
assert_eq!(Ok(123), u64::extended_parse("123"));
|
||||||
assert_eq!(
|
assert_eq!(Ok(u64::MAX), u64::extended_parse(&format!("{}", u64::MAX)));
|
||||||
Ok(u64::MAX),
|
|
||||||
ParsedNumber::parse_u64(&format!("{}", u64::MAX))
|
|
||||||
);
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_u64("-123"),
|
u64::extended_parse("-123"),
|
||||||
Err(ParseError::Overflow)
|
Err(ExtendedParserError::Overflow)
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_u64(""),
|
u64::extended_parse(""),
|
||||||
Err(ParseError::NotNumeric)
|
Err(ExtendedParserError::NotNumeric)
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_u64("123.15"),
|
u64::extended_parse("123.15"),
|
||||||
Err(ParseError::PartialMatch(123, ".15"))
|
Err(ExtendedParserError::PartialMatch(123, ".15"))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decimal_i64() {
|
fn test_decimal_i64() {
|
||||||
assert_eq!(Ok(123), ParsedNumber::parse_i64("123"));
|
assert_eq!(Ok(123), i64::extended_parse("123"));
|
||||||
assert_eq!(Ok(-123), ParsedNumber::parse_i64("-123"));
|
assert_eq!(Ok(-123), i64::extended_parse("-123"));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_i64("--123"),
|
i64::extended_parse("--123"),
|
||||||
Err(ParseError::NotNumeric)
|
Err(ExtendedParserError::NotNumeric)
|
||||||
));
|
));
|
||||||
assert_eq!(
|
assert_eq!(Ok(i64::MAX), i64::extended_parse(&format!("{}", i64::MAX)));
|
||||||
Ok(i64::MAX),
|
assert_eq!(Ok(i64::MIN), i64::extended_parse(&format!("{}", i64::MIN)));
|
||||||
ParsedNumber::parse_i64(&format!("{}", i64::MAX))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Ok(i64::MIN),
|
|
||||||
ParsedNumber::parse_i64(&format!("{}", i64::MIN))
|
|
||||||
);
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_i64(&format!("{}", u64::MAX)),
|
i64::extended_parse(&format!("{}", u64::MAX)),
|
||||||
Err(ParseError::Overflow)
|
Err(ExtendedParserError::Overflow)
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_i64(&format!("{}", i64::MAX as u64 + 1)),
|
i64::extended_parse(&format!("{}", i64::MAX as u64 + 1)),
|
||||||
Err(ParseError::Overflow)
|
Err(ExtendedParserError::Overflow)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_decimal_f64() {
|
fn test_decimal_f64() {
|
||||||
assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123"));
|
assert_eq!(Ok(123.0), f64::extended_parse("123"));
|
||||||
assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123"));
|
assert_eq!(Ok(-123.0), f64::extended_parse("-123"));
|
||||||
assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123."));
|
assert_eq!(Ok(123.0), f64::extended_parse("123."));
|
||||||
assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123."));
|
assert_eq!(Ok(-123.0), f64::extended_parse("-123."));
|
||||||
assert_eq!(Ok(123.0), ParsedNumber::parse_f64("123.0"));
|
assert_eq!(Ok(123.0), f64::extended_parse("123.0"));
|
||||||
assert_eq!(Ok(-123.0), ParsedNumber::parse_f64("-123.0"));
|
assert_eq!(Ok(-123.0), f64::extended_parse("-123.0"));
|
||||||
assert_eq!(Ok(123.15), ParsedNumber::parse_f64("123.15"));
|
assert_eq!(Ok(123.15), f64::extended_parse("123.15"));
|
||||||
assert_eq!(Ok(-123.15), ParsedNumber::parse_f64("-123.15"));
|
assert_eq!(Ok(-123.15), f64::extended_parse("-123.15"));
|
||||||
assert_eq!(Ok(0.15), ParsedNumber::parse_f64(".15"));
|
assert_eq!(Ok(0.15), f64::extended_parse(".15"));
|
||||||
assert_eq!(Ok(-0.15), ParsedNumber::parse_f64("-.15"));
|
assert_eq!(Ok(-0.15), f64::extended_parse("-.15"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Ok(0.15),
|
Ok(0.15),
|
||||||
ParsedNumber::parse_f64(".150000000000000000000000000231313")
|
f64::extended_parse(".150000000000000000000000000231313")
|
||||||
);
|
);
|
||||||
assert!(matches!(ParsedNumber::parse_f64("1.2.3"),
|
assert!(matches!(f64::extended_parse("1.2.3"),
|
||||||
Err(ParseError::PartialMatch(f, ".3")) if f == 1.2));
|
Err(ExtendedParserError::PartialMatch(f, ".3")) if f == 1.2));
|
||||||
assert_eq!(Ok(f64::INFINITY), ParsedNumber::parse_f64("inf"));
|
assert_eq!(Ok(f64::INFINITY), f64::extended_parse("inf"));
|
||||||
assert_eq!(Ok(f64::NEG_INFINITY), ParsedNumber::parse_f64("-inf"));
|
assert_eq!(Ok(f64::NEG_INFINITY), f64::extended_parse("-inf"));
|
||||||
assert!(ParsedNumber::parse_f64("NaN").unwrap().is_nan());
|
assert!(f64::extended_parse("NaN").unwrap().is_nan());
|
||||||
assert!(ParsedNumber::parse_f64("NaN").unwrap().is_sign_positive());
|
assert!(f64::extended_parse("NaN").unwrap().is_sign_positive());
|
||||||
assert!(ParsedNumber::parse_f64("-NaN").unwrap().is_nan());
|
assert!(f64::extended_parse("-NaN").unwrap().is_nan());
|
||||||
assert!(ParsedNumber::parse_f64("-NaN").unwrap().is_sign_negative());
|
assert!(f64::extended_parse("-NaN").unwrap().is_sign_negative());
|
||||||
assert!(matches!(ParsedNumber::parse_f64("-infinity"),
|
assert!(matches!(f64::extended_parse("-infinity"),
|
||||||
Err(ParseError::PartialMatch(f, "inity")) if f == f64::NEG_INFINITY));
|
Err(ExtendedParserError::PartialMatch(f, "inity")) if f == f64::NEG_INFINITY));
|
||||||
assert!(ParsedNumber::parse_f64(&format!("{}", u64::MAX)).is_ok());
|
assert!(f64::extended_parse(&format!("{}", u64::MAX)).is_ok());
|
||||||
assert!(ParsedNumber::parse_f64(&format!("{}", i64::MIN)).is_ok());
|
assert!(f64::extended_parse(&format!("{}", i64::MIN)).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hexadecimal() {
|
fn test_hexadecimal() {
|
||||||
assert_eq!(Ok(0x123), ParsedNumber::parse_u64("0x123"));
|
assert_eq!(Ok(0x123), u64::extended_parse("0x123"));
|
||||||
assert_eq!(Ok(0x123), ParsedNumber::parse_u64("0X123"));
|
assert_eq!(Ok(0x123), u64::extended_parse("0X123"));
|
||||||
assert_eq!(Ok(0xfe), ParsedNumber::parse_u64("0xfE"));
|
assert_eq!(Ok(0xfe), u64::extended_parse("0xfE"));
|
||||||
assert_eq!(Ok(-0x123), ParsedNumber::parse_i64("-0x123"));
|
assert_eq!(Ok(-0x123), i64::extended_parse("-0x123"));
|
||||||
|
|
||||||
assert_eq!(Ok(0.5), ParsedNumber::parse_f64("0x.8"));
|
assert_eq!(Ok(0.5), f64::extended_parse("0x.8"));
|
||||||
assert_eq!(Ok(0.0625), ParsedNumber::parse_f64("0x.1"));
|
assert_eq!(Ok(0.0625), f64::extended_parse("0x.1"));
|
||||||
assert_eq!(Ok(15.007_812_5), ParsedNumber::parse_f64("0xf.02"));
|
assert_eq!(Ok(15.007_812_5), f64::extended_parse("0xf.02"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_octal() {
|
fn test_octal() {
|
||||||
assert_eq!(Ok(0), ParsedNumber::parse_u64("0"));
|
assert_eq!(Ok(0), u64::extended_parse("0"));
|
||||||
assert_eq!(Ok(0o123), ParsedNumber::parse_u64("0123"));
|
assert_eq!(Ok(0o123), u64::extended_parse("0123"));
|
||||||
assert_eq!(Ok(0o123), ParsedNumber::parse_u64("00123"));
|
assert_eq!(Ok(0o123), u64::extended_parse("00123"));
|
||||||
assert_eq!(Ok(0), ParsedNumber::parse_u64("00"));
|
assert_eq!(Ok(0), u64::extended_parse("00"));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_u64("008"),
|
u64::extended_parse("008"),
|
||||||
Err(ParseError::PartialMatch(0, "8"))
|
Err(ExtendedParserError::PartialMatch(0, "8"))
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_u64("08"),
|
u64::extended_parse("08"),
|
||||||
Err(ParseError::PartialMatch(0, "8"))
|
Err(ExtendedParserError::PartialMatch(0, "8"))
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
ParsedNumber::parse_u64("0."),
|
u64::extended_parse("0."),
|
||||||
Err(ParseError::PartialMatch(0, "."))
|
Err(ExtendedParserError::PartialMatch(0, "."))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_binary() {
|
fn test_binary() {
|
||||||
assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0b1011"));
|
assert_eq!(Ok(0b1011), u64::extended_parse("0b1011"));
|
||||||
assert_eq!(Ok(0b1011), ParsedNumber::parse_u64("0B1011"));
|
assert_eq!(Ok(0b1011), u64::extended_parse("0B1011"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parsing_with_leading_whitespace() {
|
fn test_parsing_with_leading_whitespace() {
|
||||||
assert_eq!(Ok(1), ParsedNumber::parse_u64(" 0x1"));
|
assert_eq!(Ok(1), u64::extended_parse(" 0x1"));
|
||||||
assert_eq!(Ok(-2), ParsedNumber::parse_i64(" -0x2"));
|
assert_eq!(Ok(-2), i64::extended_parse(" -0x2"));
|
||||||
assert_eq!(Ok(-3), ParsedNumber::parse_i64(" \t-0x3"));
|
assert_eq!(Ok(-3), i64::extended_parse(" \t-0x3"));
|
||||||
assert_eq!(Ok(-4), ParsedNumber::parse_i64(" \n-0x4"));
|
assert_eq!(Ok(-4), i64::extended_parse(" \n-0x4"));
|
||||||
assert_eq!(Ok(-5), ParsedNumber::parse_i64(" \n\t\u{000d}-0x5"));
|
assert_eq!(Ok(-5), i64::extended_parse(" \n\t\u{000d}-0x5"));
|
||||||
|
|
||||||
// Ensure that trailing whitespace is still a partial match
|
// Ensure that trailing whitespace is still a partial match
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(ParseError::PartialMatch(6, " ")),
|
Err(ExtendedParserError::PartialMatch(6, " ")),
|
||||||
ParsedNumber::parse_u64("0x6 ")
|
u64::extended_parse("0x6 ")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(ParseError::PartialMatch(7, "\t")),
|
Err(ExtendedParserError::PartialMatch(7, "\t")),
|
||||||
ParsedNumber::parse_u64("0x7\t")
|
u64::extended_parse("0x7\t")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(ParseError::PartialMatch(8, "\n")),
|
Err(ExtendedParserError::PartialMatch(8, "\n")),
|
||||||
ParsedNumber::parse_u64("0x8\n")
|
u64::extended_parse("0x8\n")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that unicode non-ascii whitespace is a partial match
|
// Ensure that unicode non-ascii whitespace is a partial match
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(ParseError::NotNumeric),
|
Err(ExtendedParserError::NotNumeric),
|
||||||
ParsedNumber::parse_i64("\u{2029}-0x9")
|
i64::extended_parse("\u{2029}-0x9")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure that whitespace after the number has "started" is not allowed
|
// Ensure that whitespace after the number has "started" is not allowed
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Err(ParseError::NotNumeric),
|
Err(ExtendedParserError::NotNumeric),
|
||||||
ParsedNumber::parse_i64("- 0x9")
|
i64::extended_parse("- 0x9")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue