Make handling of negative numbers in record-id-ids more consistent. (#3633)

This commit is contained in:
Mees Delzenne 2024-03-05 19:43:04 +01:00 committed by GitHub
parent 08fa85b3ab
commit 0e2f83ed9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 165 additions and 4 deletions

View file

@ -9,7 +9,7 @@ use nom::{
branch::alt, branch::alt,
bytes::complete::tag, bytes::complete::tag,
character::complete::char, character::complete::char,
combinator::{cut, map, value}, combinator::{cut, map, opt, value},
sequence::delimited, sequence::delimited,
Err, Parser, Err, Parser,
}; };
@ -67,7 +67,24 @@ pub fn thing_raw(i: &str) -> IResult<&str, Thing> {
pub fn id(i: &str) -> IResult<&str, Id> { pub fn id(i: &str) -> IResult<&str, Id> {
alt(( alt((
map(integer, Id::Number), 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(object, Id::Object),
map(array, Id::Array), map(array, Id::Array),
))(i) ))(i)
@ -243,6 +260,64 @@ mod tests {
assert_eq!("⟨100⟩", format!("{}", out)); 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] #[test]
fn id_either() { fn id_either() {
let sql = "100test"; let sql = "100test";

View file

@ -244,7 +244,7 @@ impl Lexer<'_> {
} }
self.reader.next(); self.reader.next();
loop { loop {
match dbg!(self.reader.peek()) { match self.reader.peek() {
Some(x @ b'0'..=b'9') => { Some(x @ b'0'..=b'9') => {
self.reader.next(); self.reader.next();
self.scratch.push(x as char); self.scratch.push(x as char);

View file

@ -9,7 +9,7 @@ use crate::{
token::{t, NumberKind, TokenKind}, token::{t, NumberKind, TokenKind},
}, },
}; };
use std::ops::Bound; use std::{cmp::Ordering, ops::Bound};
impl Parser<'_> { impl Parser<'_> {
pub fn parse_record_string(&mut self, double: bool) -> ParseResult<Thing> { pub fn parse_record_string(&mut self, double: bool) -> ParseResult<Thing> {
@ -208,6 +208,34 @@ impl Parser<'_> {
let array = self.parse_array(token.span)?; let array = self.parse_array(token.span)?;
Ok(Id::Array(array)) 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::<u64>() {
// 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) => { TokenKind::Number(NumberKind::Integer) => {
// Id handle numbers more loose then other parts of the code. // 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. // 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] #[test]
fn thing_string() { fn thing_string() {
let sql = "r'test:001'"; let sql = "r'test:001'";