Skip to content

Commit

Permalink
Fix parsing of collections, short values.
Browse files Browse the repository at this point in the history
This fixes a regression introduced in c730bdf in which an unterminated
collection string (e.g. "[") would cause the parser to loop infinitely.
It also fixes a regression in which the parser would discard the latter
part of a string if the earlier part parsed as a valid value while the
latter did not, e.g. " [discarded" in "okay [discarded".

Fixes #120.
  • Loading branch information
SergioBenitez committed Aug 31, 2024
1 parent 3f9f333 commit ebf6d86
Showing 1 changed file with 43 additions and 35 deletions.
78 changes: 43 additions & 35 deletions src/value/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ pub enum Error {
Eof,
}

type Result<'a, T> = std::result::Result<T, Error>;
type Result<T> = std::result::Result<T, Error>;

impl<'a> Parser<'a> {
fn new(cursor: &'a str) -> Self {
Self { cursor }
}

fn peek(&self, c: char) -> bool {
self.cursor.chars().next() == Some(c)
self.peek_next() == Some(c)
}

fn eof(&self) -> bool {
self.cursor.is_empty()
}

fn peek_next(&self) -> Option<char> {
Expand Down Expand Up @@ -104,46 +108,39 @@ impl<'a> Parser<'a> {
}
}

fn dict(&mut self) -> Result<Dict> {
self.eat('{')?;
fn delimited<T, V, F>(&mut self, start: char, end: char, value: F) -> Result<T>
where T: Extend<V> + Default, F: Fn(&mut Self) -> Result<V>,
{
let mut collection = T::default();
self.eat(start)?;
self.skip_whitespace();

while !self.peek(end) {
collection.extend(Some(value(self)?));

let mut dict = Dict::new();
loop {
self.skip_whitespace();
if self.eat('}').is_ok() {
if self.eat(',').is_err() {
break;
}

let key = self.key()?;
self.skip_whitespace();
self.eat('=')?;
self.skip_whitespace();
let value = self.value()?;
dict.insert(key.to_string(), value);

self.skip_whitespace();
let _ = self.eat(',');
}

Ok(dict)
self.eat(end)?;
Ok(collection)
}

fn array(&mut self) -> Result<Vec<Value>> {
self.eat('[')?;
let mut values = Vec::new();

loop {
self.skip_whitespace();
if self.eat(']').is_ok() {
break;
}

values.push(self.value()?);
self.skip_whitespace();
let _ = self.eat(',');
}
fn dict(&mut self) -> Result<Dict> {
self.delimited('{', '}', |parser| {
let key = parser.key()?;
(parser.skip_whitespace(), parser.eat('=')?, parser.skip_whitespace());
let value = parser.value()?;
Ok((key.to_string(), value))
})
}

Ok(values)
fn array(&mut self) -> Result<Vec<Value>> {
self.delimited('[', ']', |parser| parser.value())
}

fn value(&mut self) -> Result<Value> {
Expand All @@ -152,6 +149,7 @@ impl<'a> Parser<'a> {
!matches!(byte, ',' | '{' | '}' | '[' | ']')
}

self.skip_whitespace();
let value = match self.peek_next() {
Some('"') => Value::from(self.quoted_str()?.to_string()),
Some('\'') => Value::from(self.quoted_char()?),
Expand Down Expand Up @@ -187,11 +185,12 @@ impl<'a> Parser<'a> {
impl std::str::FromStr for Value {
type Err = std::convert::Infallible;

fn from_str(s: &str) -> std::result::Result<Self, std::convert::Infallible> {
let mut parser = Parser::new(s.trim());
fn from_str(string: &str) -> std::result::Result<Self, std::convert::Infallible> {
let string = string.trim();
let mut parser = Parser::new(string);
match parser.value() {
Ok(value) => Ok(value),
Err(_) => Ok(Value::from(s)),
Ok(value) if parser.eof() => Ok(value),
_ => Ok(Value::from(string)),
}
}
}
Expand Down Expand Up @@ -231,13 +230,19 @@ mod tests {
" -0" => 0i8,
" -2" => -2,
" 123 " => 123u8,
"a,b" => "a,b",
" a,b" => "a,b",
"\"a\"" => "a",
"a " => "a",
" a " => "a",
"\" a\"" => " a",
"\"a \"" => "a ",
"\" a \"" => " a ",
"1.2" => 1.2,
"[" => "[",
"[a" => "[a",
"[a b" => "[a b",
"]" => "]",
" 1.2" => 1.2,
"3.14159" => 3.14159,
"\"\\t\"" => "\t",
Expand All @@ -263,6 +268,9 @@ mod tests {
"[1,2,3]" => vec![1u8, 2u8, 3u8],
"[ 1 , 2 ,3]" => vec![1u8, 2u8, 3u8],
" [ 1 , 2 , 3 ] " => vec![1u8, 2u8, 3u8],
" [ a , b ,, d ] " => vec!["a", "b", "", "d"],
" [ a , b c,] " => vec!["a", "b c"],
" [ a , b c,,] " => vec!["a", "b c", ""],
"{a=b}" => map!["a" => "b"],
" { a = b } " => map!["a" => "b"],
"{\"a\"=b}" => map!["a" => "b"],
Expand Down

0 comments on commit ebf6d86

Please sign in to comment.