From 0e2f83ed9d67303a91710e9a09ac29a219219369 Mon Sep 17 00:00:00 2001 From: Mees Delzenne Date: Tue, 5 Mar 2024 19:43:04 +0100 Subject: [PATCH] Make handling of negative numbers in record-id-ids more consistent. (#3633) --- core/src/syn/v1/thing.rs | 79 ++++++++++++++++++++++++++++- core/src/syn/v2/lexer/number.rs | 2 +- core/src/syn/v2/parser/thing.rs | 88 ++++++++++++++++++++++++++++++++- 3 files changed, 165 insertions(+), 4 deletions(-) diff --git a/core/src/syn/v1/thing.rs b/core/src/syn/v1/thing.rs index f1f9eb16..06bec250 100644 --- a/core/src/syn/v1/thing.rs +++ b/core/src/syn/v1/thing.rs @@ -9,7 +9,7 @@ use nom::{ branch::alt, bytes::complete::tag, character::complete::char, - combinator::{cut, map, value}, + combinator::{cut, map, opt, value}, sequence::delimited, Err, Parser, }; @@ -67,7 +67,24 @@ pub fn thing_raw(i: &str) -> IResult<&str, Thing> { pub fn id(i: &str) -> IResult<&str, Id> { alt(( map(integer, Id::Number), - map(ident_raw, Id::String), + map( + |i| { + let (i, _) = tag("+")(i)?; + ident_raw(i) + }, + Id::String, + ), + map( + |i| { + let (i, minus) = opt(tag("-"))(i)?; + let (i, mut res) = ident_raw(i)?; + if minus.is_some() { + res.insert(0, '-'); + } + Ok((i, res)) + }, + Id::String, + ), map(object, Id::Object), map(array, Id::Array), ))(i) @@ -243,6 +260,64 @@ mod tests { assert_eq!("⟨100⟩", format!("{}", out)); } + #[test] + fn thing_integer_min() { + let sql = format!("test:{}", i64::MIN); + let res = thing(&sql); + let (_, out) = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(i64::MIN), + } + ); + } + + #[test] + fn thing_integer_max() { + let sql = format!("test:{}", i64::MAX); + let res = thing(&sql); + let (_, out) = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(i64::MAX), + } + ); + } + + #[test] + fn thing_integer_more_then_max() { + let max_str = format!("{}", (i64::MAX as u64) + 1); + let sql = format!("test:{}", max_str); + let res = thing(&sql); + let (_, out) = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(max_str), + } + ); + } + + #[test] + fn thing_integer_more_then_min() { + let min_str = format!("-{}", (i64::MAX as u64) + 2); + let sql = format!("test:{}", min_str); + let res = thing(&sql); + let (_, out) = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(min_str), + } + ); + } + #[test] fn id_either() { let sql = "100test"; diff --git a/core/src/syn/v2/lexer/number.rs b/core/src/syn/v2/lexer/number.rs index 8b056c6b..00daf565 100644 --- a/core/src/syn/v2/lexer/number.rs +++ b/core/src/syn/v2/lexer/number.rs @@ -244,7 +244,7 @@ impl Lexer<'_> { } self.reader.next(); loop { - match dbg!(self.reader.peek()) { + match self.reader.peek() { Some(x @ b'0'..=b'9') => { self.reader.next(); self.scratch.push(x as char); diff --git a/core/src/syn/v2/parser/thing.rs b/core/src/syn/v2/parser/thing.rs index 1d2938f8..dbe7195d 100644 --- a/core/src/syn/v2/parser/thing.rs +++ b/core/src/syn/v2/parser/thing.rs @@ -9,7 +9,7 @@ use crate::{ token::{t, NumberKind, TokenKind}, }, }; -use std::ops::Bound; +use std::{cmp::Ordering, ops::Bound}; impl Parser<'_> { pub fn parse_record_string(&mut self, double: bool) -> ParseResult { @@ -208,6 +208,34 @@ impl Parser<'_> { let array = self.parse_array(token.span)?; Ok(Id::Array(array)) } + t!("+") => { + self.peek(); + self.no_whitespace()?; + expected!(self, TokenKind::Number(NumberKind::Integer)); + let text = self.lexer.string.take().unwrap(); + if let Ok(number) = text.parse() { + Ok(Id::Number(number)) + } else { + Ok(Id::String(text)) + } + } + t!("-") => { + self.peek(); + self.no_whitespace()?; + expected!(self, TokenKind::Number(NumberKind::Integer)); + let text = self.lexer.string.take().unwrap(); + if let Ok(number) = text.parse::() { + // Parse to u64 and check if the value is equal to `-i64::MIN` via u64 as + // `-i64::MIN` doesn't fit in an i64 + match number.cmp(&((i64::MAX as u64) + 1)) { + Ordering::Less => Ok(Id::Number(-(number as i64))), + Ordering::Equal => Ok(Id::Number(i64::MIN)), + Ordering::Greater => Ok(Id::String(format!("-{}", text))), + } + } else { + Ok(Id::String(text)) + } + } TokenKind::Number(NumberKind::Integer) => { // Id handle numbers more loose then other parts of the code. // If number can't fit in a i64 it will instead be parsed as a string. @@ -285,6 +313,64 @@ mod tests { ); } + #[test] + fn thing_integer_min() { + let sql = format!("test:{}", i64::MIN); + let res = thing(&sql); + let out = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(i64::MIN), + } + ); + } + + #[test] + fn thing_integer_max() { + let sql = format!("test:{}", i64::MAX); + let res = thing(&sql); + let out = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(i64::MAX), + } + ); + } + + #[test] + fn thing_integer_more_then_max() { + let max_str = format!("{}", (i64::MAX as u64) + 1); + let sql = format!("test:{}", max_str); + let res = thing(&sql); + let out = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(max_str), + } + ); + } + + #[test] + fn thing_integer_more_then_min() { + let min_str = format!("-{}", (i64::MAX as u64) + 2); + let sql = format!("test:{}", min_str); + let res = thing(&sql); + let out = res.unwrap(); + assert_eq!( + out, + Thing { + tb: String::from("test"), + id: Id::from(min_str), + } + ); + } + #[test] fn thing_string() { let sql = "r'test:001'";