diff --git a/src/ast.rs b/src/ast.rs index e930749..9b2ae25 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -69,8 +69,8 @@ impl<'a> From> for serde_json::Value { Value::BooleanLit(b) => serde_json::Value::Bool(b.value), Value::NullKeyword(_) => serde_json::Value::Null, Value::NumberLit(num) => { - // Check if this is a hexadecimal literal (0x or 0X prefix) - let num_str = num.value.trim_start_matches('-'); + // check if this is a hexadecimal literal (0x or 0X prefix) + let num_str = num.value.trim_start_matches(['-', '+']); if num_str.len() > 2 && (num_str.starts_with("0x") || num_str.starts_with("0X")) { // Parse hexadecimal and convert to decimal let hex_part = &num_str[2..]; @@ -86,8 +86,9 @@ impl<'a> From> for serde_json::Value { Err(_) => serde_json::Value::String(num.value.to_string()), } } else { - // Standard decimal number - match serde_json::Number::from_str(num.value) { + // standard decimal number + let num_for_parsing = num.value.trim_start_matches('+'); + match serde_json::Number::from_str(num_for_parsing) { Ok(number) => serde_json::Value::Number(number), Err(_) => serde_json::Value::String(num.value.to_string()), } diff --git a/src/cst/mod.rs b/src/cst/mod.rs index dbd7f9b..e19769b 100644 --- a/src/cst/mod.rs +++ b/src/cst/mod.rs @@ -1444,7 +1444,7 @@ impl CstNumberLit { let raw = self.0.borrow().value.clone(); // check if this is a hexadecimal literal (0x or 0X prefix) - let num_str = raw.trim_start_matches('-'); + let num_str = raw.trim_start_matches(['-', '+']); if num_str.len() > 2 && (num_str.starts_with("0x") || num_str.starts_with("0X")) { // parse hexadecimal and convert to decimal let hex_part = &num_str[2..]; @@ -1460,8 +1460,9 @@ impl CstNumberLit { Err(_) => Some(serde_json::Value::String(raw)), } } else { - // standard decimal number - match serde_json::Number::from_str(&raw) { + // standard decimal number - strip leading + if present (serde_json doesn't accept it) + let num_for_parsing = raw.trim_start_matches('+'); + match serde_json::Number::from_str(num_for_parsing) { Ok(number) => Some(serde_json::Value::Number(number)), // if the number is invalid, return it as a string (same behavior as AST conversion) Err(_) => Some(serde_json::Value::String(raw)), diff --git a/src/parse_to_ast.rs b/src/parse_to_ast.rs index 1534a43..88e07bf 100644 --- a/src/parse_to_ast.rs +++ b/src/parse_to_ast.rs @@ -602,4 +602,17 @@ mod tests { ); } } + + #[test] + fn it_should_parse_unary_plus_numbers() { + let result = parse_to_ast(r#"{ "test": +42 }"#, &Default::default(), &Default::default()).unwrap(); + + let value = result.value.unwrap(); + let obj = value.as_object().unwrap(); + assert_eq!(obj.properties.len(), 1); + assert_eq!(obj.properties[0].name.as_str(), "test"); + + let number_value = obj.properties[0].value.as_number_lit().unwrap(); + assert_eq!(number_value.value, "+42"); + } } diff --git a/src/scanner.rs b/src/scanner.rs index 573f13c..9de46db 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -79,7 +79,7 @@ impl<'a> Scanner<'a> { _ => Err(self.create_error_for_current_token(ParseErrorKind::UnexpectedToken)), }, _ => { - if current_char == '-' || self.is_digit() { + if current_char == '-' || current_char == '+' || self.is_digit() { self.parse_number() } else if self.try_move_word("true") { Ok(Token::Boolean(true)) @@ -154,7 +154,8 @@ impl<'a> Scanner<'a> { fn parse_number(&mut self) -> Result, ParseError> { let start_byte_index = self.byte_index; - if self.is_negative_sign() { + // handle unary plus or minus + if self.is_negative_sign() || self.is_positive_sign() { self.move_next_char(); } @@ -423,6 +424,10 @@ impl<'a> Scanner<'a> { self.current_char() == Some('-') } + fn is_positive_sign(&self) -> bool { + self.current_char() == Some('+') + } + fn is_decimal_point(&self) -> bool { self.current_char() == Some('.') } @@ -540,6 +545,22 @@ mod tests { ); } + #[test] + fn it_tokenizes_unary_plus_numbers() { + assert_has_tokens( + "+42, +0.5, +1e10, +0xFF", + vec![ + Token::Number("+42"), + Token::Comma, + Token::Number("+0.5"), + Token::Comma, + Token::Number("+1e10"), + Token::Comma, + Token::Number("+0xFF"), + ], + ); + } + #[test] fn it_errors_invalid_exponent() { assert_has_error( diff --git a/src/serde.rs b/src/serde.rs index 8a57497..7c21c73 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -104,4 +104,30 @@ mod tests { assert_eq!(result, Some(SerdeValue::Object(expected_value))); } + + #[test] + fn it_should_parse_unary_plus_numbers() { + let result = parse_to_serde_value( + r#"{ + "pos1": +42, + "pos2": +0.5, + "pos3": +1e10 + }"#, + &Default::default(), + ) + .unwrap(); + + let mut expected_value = serde_json::map::Map::new(); + expected_value.insert("pos1".to_string(), SerdeValue::Number(serde_json::Number::from(42))); + expected_value.insert( + "pos2".to_string(), + SerdeValue::Number(serde_json::Number::from_str("0.5").unwrap()), + ); + expected_value.insert( + "pos3".to_string(), + SerdeValue::Number(serde_json::Number::from_str("1e10").unwrap()), + ); + + assert_eq!(result, Some(SerdeValue::Object(expected_value))); + } }