diff --git a/Cargo.toml b/Cargo.toml index fce4778..836200d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ span = [] [dependencies] miette = "7.2.0" +num = "0.4.2" thiserror = "1.0.40" winnow = { version = "0.6.20", features = ["alloc", "unstable-recover"] } diff --git a/src/error.rs b/src/error.rs index 544b4c3..50b7d21 100644 --- a/src/error.rs +++ b/src/error.rs @@ -78,7 +78,7 @@ pub struct KdlDiagnostic { pub kind: KdlErrorKind, } -/// A type reprenting additional information specific to the type of error being returned. +/// A type representing additional information specific to the type of error being returned. #[derive(Debug, Diagnostic, Clone, Eq, PartialEq, Error)] pub enum KdlErrorKind { /// An error occurred while parsing an integer. @@ -91,6 +91,11 @@ pub enum KdlErrorKind { #[diagnostic(code(kdl::parse_float))] ParseFloatError(ParseFloatError), + /// Tried to parse a negative number as an unsigned integer. + #[error("Tried to parse a negative number as an unsigned integer.")] + #[diagnostic(code(kdl::negative_unsigned))] + NegativeUnsignedError, + /// Generic parsing error. The given context string denotes the component /// that failed to parse. #[error("Expected {0}.")] diff --git a/src/v2_parser.rs b/src/v2_parser.rs index f9697bd..0aabeb9 100644 --- a/src/v2_parser.rs +++ b/src/v2_parser.rs @@ -5,6 +5,7 @@ use std::{ use miette::{Severity, SourceSpan}; +use num::CheckedMul; use winnow::{ ascii::{digit1, hex_digit1, oct_digit1, Caseless}, combinator::{ @@ -128,6 +129,24 @@ impl<'a> FromExternalError, ParseFloatError> for KdlParseError { } } +struct NegativeUnsignedError; + +impl<'a> FromExternalError, NegativeUnsignedError> for KdlParseError { + fn from_external_error( + _input: &Input<'a>, + _kind: ErrorKind, + _e: NegativeUnsignedError, + ) -> Self { + KdlParseError { + span: None, + label: None, + help: None, + context: None, + kind: Some(KdlErrorKind::NegativeUnsignedError), + } + } +} + impl FromRecoverableError for KdlParseError { #[inline] fn from_recoverable_error( @@ -639,7 +658,7 @@ fn signed_ident(input: &mut Input<'_>) -> PResult<()> { /// `dotted-ident := sign? '.' ((identifier-char - digit) identifier-char*)?` fn dotted_ident(input: &mut Input<'_>) -> PResult<()> { ( - opt(sign), + opt(signum), ".", not(digit1), repeat(0.., identifier_char).map(|_: ()| ()), @@ -1214,30 +1233,30 @@ fn multi_line_comment_test() { /// `number := keyword-number | hex | octal | binary | decimal` fn number(input: &mut Input<'_>) -> PResult { - alt((hex, octal, binary, float, integer)).parse_next(input) + alt((float_value, integer_value)).parse_next(input) } /// ```text /// decimal := sign? integer ('.' integer)? exponent? /// exponent := ('e' | 'E') sign? integer /// ``` -fn float(input: &mut Input<'_>) -> PResult { +fn float_value(input: &mut Input<'_>) -> PResult { + float.map(KdlValue::Float).parse_next(input) +} + +fn float(input: &mut Input<'_>) -> PResult { alt(( ( - integer, - opt(preceded('.', cut_err(integer_base))), + decimal::, + opt(preceded('.', cut_err(udecimal::))), Caseless("e"), opt(one_of(['-', '+'])), - cut_err(integer_base), + cut_err(udecimal::), ) .take(), - (integer, '.', cut_err(integer_base)).take(), + (decimal::, '.', cut_err(udecimal::)).take(), )) - .try_map(|float_str| { - str::replace(float_str, "_", "") - .parse::() - .map(KdlValue::Float) - }) + .try_map(|float_str| T::parse_float(&str::replace(float_str, "_", ""))) .context(lbl("float")) .parse_next(input) } @@ -1248,19 +1267,21 @@ fn float_test() { use winnow::token::take; assert_eq!( - float.parse(new_input("12_34.56")).unwrap(), + float_value.parse(new_input("12_34.56")).unwrap(), KdlValue::Float(1234.56) ); assert_eq!( - float.parse(new_input("1234_.56")).unwrap(), + float_value.parse(new_input("1234_.56")).unwrap(), KdlValue::Float(1234.56) ); assert_eq!( - (float, take(1usize)).parse(new_input("1234.56c")).unwrap(), + (float_value, take(1usize)) + .parse(new_input("1234.56c")) + .unwrap(), (KdlValue::Float(1234.56), "c") ); - assert!(float.parse(new_input("_1234.56")).is_err()); - assert!(float.parse(new_input("1234a.56")).is_err()); + assert!(float_value.parse(new_input("_1234.56")).is_err()); + assert!(float_value.parse(new_input("1234a.56")).is_err()); assert_eq!( value .parse(new_input("2.5")) @@ -1270,32 +1291,37 @@ fn float_test() { ); } +fn integer_value(input: &mut Input<'_>) -> PResult { + alt((hex, octal, binary, decimal)) + .map(KdlValue::Integer) + .parse_next(input) +} + /// Non-float decimal -fn integer(input: &mut Input<'_>) -> PResult { - let mult = sign.parse_next(input)?; - integer_base - .map(|x| KdlValue::Integer(x * mult)) - .context(lbl("integer")) +fn decimal(input: &mut Input<'_>) -> PResult { + let positive = signum.parse_next(input)?; + udecimal:: + .try_map(|x| { + if positive { + Ok(x) + } else { + x.negated().ok_or(NegativeUnsignedError) + } + }) .parse_next(input) } #[cfg(test)] #[test] -fn integer_test() { - assert_eq!( - integer.parse(new_input("12_34")).unwrap(), - KdlValue::Integer(1234) - ); - assert_eq!( - integer.parse(new_input("1234_")).unwrap(), - KdlValue::Integer(1234) - ); - assert!(integer.parse(new_input("_1234")).is_err()); - assert!(integer.parse(new_input("1234a")).is_err()); +fn decimal_test() { + assert_eq!(decimal::.parse(new_input("12_34")).unwrap(), 1234); + assert_eq!(decimal::.parse(new_input("1234_")).unwrap(), 1234); + assert!(decimal::.parse(new_input("_1234")).is_err()); + assert!(decimal::.parse(new_input("1234a")).is_err()); } /// `integer := digit (digit | '_')*` -fn integer_base(input: &mut Input<'_>) -> PResult { +fn udecimal(input: &mut Input<'_>) -> PResult { ( digit1, cut_err(repeat( @@ -1304,14 +1330,26 @@ fn integer_base(input: &mut Input<'_>) -> PResult { )), ) .try_map(|(l, r): (&str, Vec<&str>)| { - format!("{l}{}", str::replace(&r.join(""), "_", "")).parse() + T::from_str_radix(&format!("{l}{}", str::replace(&r.join(""), "_", "")), 10) }) .parse_next(input) } /// `hex := sign? '0x' hex-digit (hex-digit | '_')*` -fn hex(input: &mut Input<'_>) -> PResult { - let mult = sign.parse_next(input)?; +fn hex(input: &mut Input<'_>) -> PResult { + let positive = signum.parse_next(input)?; + uhex:: + .try_map(|x| { + if positive { + Ok(x) + } else { + x.negated().ok_or(NegativeUnsignedError) + } + }) + .parse_next(input) +} + +fn uhex(input: &mut Input<'_>) -> PResult { alt(("0x", "0X")).parse_next(input)?; cut_err(( hex_digit1, @@ -1321,9 +1359,7 @@ fn hex(input: &mut Input<'_>) -> PResult { ), )) .try_map(|(l, r): (&str, Vec<&str>)| { - i128::from_str_radix(&format!("{l}{}", str::replace(&r.join(""), "_", "")), 16) - .map(|x| x * mult) - .map(KdlValue::Integer) + T::from_str_radix(&format!("{l}{}", str::replace(&r.join(""), "_", "")), 16) }) .context(lbl("hexadecimal")) .parse_next(input) @@ -1333,30 +1369,43 @@ fn hex(input: &mut Input<'_>) -> PResult { #[test] fn test_hex() { assert_eq!( - hex.parse(new_input("0xdead_beef123")).unwrap(), - KdlValue::Integer(0xdeadbeef123) + hex::.parse(new_input("0xdead_beef123")).unwrap(), + 0xdeadbeef123 ); assert_eq!( - hex.parse(new_input("0xDeAd_BeEf123")).unwrap(), - KdlValue::Integer(0xdeadbeef123) + hex::.parse(new_input("0xDeAd_BeEf123")).unwrap(), + 0xdeadbeef123 ); assert_eq!( - hex.parse(new_input("0xdeadbeef123_")).unwrap(), - KdlValue::Integer(0xdeadbeef123) + hex::.parse(new_input("0xdeadbeef123_")).unwrap(), + 0xdeadbeef123 ); assert!( - hex.parse(new_input("0xABCDEF0123456789abcdef0123456789")) + hex:: + .parse(new_input("0xABCDEF0123456789abcdef0123456789")) .is_err(), - "i128 overflow" + "i128 overflow" ); - assert!(hex.parse(new_input("0x_deadbeef123")).is_err()); + assert!(hex::.parse(new_input("0x_deadbeef123")).is_err()); - assert!(hex.parse(new_input("0xbeefg1")).is_err()); + assert!(hex::.parse(new_input("0xbeefg1")).is_err()); } /// `octal := sign? '0o' [0-7] [0-7_]*` -fn octal(input: &mut Input<'_>) -> PResult { - let mult = sign.parse_next(input)?; +fn octal(input: &mut Input<'_>) -> PResult { + let positive = signum.parse_next(input)?; + uoctal:: + .try_map(|x| { + if positive { + Ok(x) + } else { + x.negated().ok_or(NegativeUnsignedError) + } + }) + .parse_next(input) +} + +fn uoctal(input: &mut Input<'_>) -> PResult { alt(("0o", "0O")).parse_next(input)?; cut_err(( oct_digit1, @@ -1366,9 +1415,7 @@ fn octal(input: &mut Input<'_>) -> PResult { ), )) .try_map(|(l, r): (&str, Vec<&str>)| { - i128::from_str_radix(&format!("{l}{}", str::replace(&r.join(""), "_", "")), 8) - .map(|x| x * mult) - .map(KdlValue::Integer) + T::from_str_radix(&format!("{l}{}", str::replace(&r.join(""), "_", "")), 8) }) .context(lbl("octal")) .parse_next(input) @@ -1377,28 +1424,32 @@ fn octal(input: &mut Input<'_>) -> PResult { #[cfg(test)] #[test] fn test_octal() { - assert_eq!( - octal.parse(new_input("0o12_34")).unwrap(), - KdlValue::Integer(0o1234) - ); - assert_eq!( - octal.parse(new_input("0o1234_")).unwrap(), - KdlValue::Integer(0o1234) - ); - assert!(octal.parse(new_input("0o_12_34")).is_err()); - assert!(octal.parse(new_input("0o89")).is_err()); + assert_eq!(octal::.parse(new_input("0o12_34")).unwrap(), 0o1234); + assert_eq!(octal::.parse(new_input("0o1234_")).unwrap(), 0o1234); + assert!(octal::.parse(new_input("0o_12_34")).is_err()); + assert!(octal::.parse(new_input("0o89")).is_err()); } /// `binary := sign? '0b' ('0' | '1') ('0' | '1' | '_')*` -fn binary(input: &mut Input<'_>) -> PResult { - let mult = sign.parse_next(input)?; +fn binary(input: &mut Input<'_>) -> PResult { + let positive = signum.parse_next(input)?; + ubinary:: + .try_map(|x| { + if positive { + Ok(x) + } else { + x.negated().ok_or(NegativeUnsignedError) + } + }) + .parse_next(input) +} + +fn ubinary(input: &mut Input<'_>) -> PResult { alt(("0b", "0B")).parse_next(input)?; cut_err( (alt(("0", "1")), repeat(0.., alt(("0", "1", "_")))).try_map( move |(x, xs): (&str, Vec<&str>)| { - i128::from_str_radix(&format!("{x}{}", str::replace(&xs.join(""), "_", "")), 2) - .map(|x| x * mult) - .map(KdlValue::Integer) + T::from_str_radix(&format!("{x}{}", str::replace(&xs.join(""), "_", "")), 2) }, ), ) @@ -1411,32 +1462,96 @@ fn binary(input: &mut Input<'_>) -> PResult { fn test_binary() { use winnow::token::take; + assert_eq!(binary::.parse(new_input("0b10_01")).unwrap(), 0b1001); + assert_eq!(binary::.parse(new_input("0b1001_")).unwrap(), 0b1001); + assert!(binary::.parse(new_input("0b_10_01")).is_err()); assert_eq!( - binary.parse(new_input("0b10_01")).unwrap(), - KdlValue::Integer(0b1001) + (binary::, take(4usize)) + .parse(new_input("0b12389")) + .unwrap(), + (1, "2389") ); - assert_eq!( - binary.parse(new_input("0b1001_")).unwrap(), - KdlValue::Integer(0b1001) - ); - assert!(binary.parse(new_input("0b_10_01")).is_err()); - assert_eq!( - (binary, take(4usize)).parse(new_input("0b12389")).unwrap(), - (KdlValue::Integer(1), "2389") - ); - assert!(binary.parse(new_input("123")).is_err()); + assert!(binary::.parse(new_input("123")).is_err()); } -fn sign(input: &mut Input<'_>) -> PResult { +fn signum(input: &mut Input<'_>) -> PResult { let sign = opt(alt(('+', '-'))).parse_next(input)?; let mult = if let Some(sign) = sign { if sign == '+' { - 1 + true } else { - -1 + false } } else { - 1 + true }; Ok(mult) } + +trait FromStrRadix { + fn from_str_radix(s: &str, radix: u32) -> Result + where + Self: Sized; +} + +macro_rules! impl_from_str_radix { + ($($t:ty),*) => { + $( + impl FromStrRadix for $t { + fn from_str_radix(s: &str, radix: u32) -> Result { + <$t>::from_str_radix(s, radix) + } + } + )* + }; +} + +impl_from_str_radix!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); + +trait MaybeNegatable: CheckedMul { + fn negated(&self) -> Option; +} + +macro_rules! impl_negatable_signed { + ($($t:ty),*) => { + $( + impl MaybeNegatable for $t { + fn negated(&self) -> Option { + Some(self * -1) + } + } + )* + }; +} + +macro_rules! impl_negatable_unsigned { + ($($t:ty),*) => { + $( + impl MaybeNegatable for $t { + fn negated(&self) -> Option { + None + } + } + )* + }; +} + +trait ParseFloat { + fn parse_float(input: &str) -> Result + where + Self: Sized; +} + +impl ParseFloat for f32 { + fn parse_float(input: &str) -> Result { + input.parse() + } +} +impl ParseFloat for f64 { + fn parse_float(input: &str) -> Result { + input.parse() + } +} + +impl_negatable_signed!(i8, i16, i32, i64, i128, isize); +impl_negatable_unsigned!(u8, u16, u32, u64, u128, usize);