Skip to content

Commit

Permalink
refactor deserializer
Browse files Browse the repository at this point in the history
  • Loading branch information
femshima committed Feb 28, 2024
1 parent 73db0be commit 0a0b962
Showing 1 changed file with 131 additions and 35 deletions.
166 changes: 131 additions & 35 deletions src/model/parser/header/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ pub type Result<T> = std::result::Result<T, DeserializeError>;

pub struct Deserializer<'de> {
input: &'de str,
context: DeserializeContext,
}

impl<'de> Deserializer<'de> {
pub fn from_str(input: &'de str) -> Self {
Deserializer { input }
Deserializer {
input,
context: DeserializeContext::default(),
}
}
}

Expand All @@ -30,6 +34,29 @@ where
}
}

#[derive(Debug, Clone, Default)]
struct DeserializeContext {
contexts: Vec<char>,
}

impl DeserializeContext {
fn current(&self) -> Option<char> {
self.contexts.last().copied()
}
fn test(&self, c: char) -> bool {
self.contexts.contains(&c)
}
fn test_noncurrent(&self, c: char) -> bool {
self.contexts.len() > 0 && self.contexts[..self.contexts.len() - 1].contains(&c)
}
fn enter(&mut self, expect: char) {
self.contexts.push(expect)
}
fn exit(&mut self) -> Option<char> {
self.contexts.pop()
}
}

impl<'de> Deserializer<'de> {
fn peek_char(&self) -> Result<char> {
self.input.chars().next().ok_or(DeserializeError::Eof)
Expand Down Expand Up @@ -85,22 +112,54 @@ impl<'de> Deserializer<'de> {
None => Err(DeserializeError::Eof),

Check warning on line 112 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L112

Added line #L112 was not covered by tests
}
} else {
let len = self
.input
.find([':', ',', '\n'])
.unwrap_or(self.input.len());
let s = &self.input[..len];
self.input = &self.input[len..];
Ok(s)
self.str_until_expect()
}
}

fn str_until_lineend(&mut self) -> Result<&'de str> {
let len = self.input.find('\n').unwrap_or(self.input.len());
fn str_until_expect(&mut self) -> Result<&'de str> {
let len = self
.input

Check warning on line 121 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L121

Added line #L121 was not covered by tests
.find(|c| self.context.test(c))
.unwrap_or(self.input.len());

let s = &self.input[..len];
self.input = &self.input[len..];
Ok(s)
}

/// Consume string until chars other than current delimiter appears.
/// - `Some(true)`: Current delimiter found (sequence continues)
/// - `Some(false)`: Current delimiter was not found, but other delimiters were found or the string reached EOF (sequence ends)
/// - `None`: No delimiter found (syntax error)
fn consume_delimiters(&mut self) -> Option<bool> {
let Some(current) = self.context.current() else {
// Delimiter is empty
return None;

Check warning on line 137 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L137

Added line #L137 was not covered by tests
};

let Some(pos) = self.input.find(|c| c != current) else {
// Input was all delimiter (EOF)
// When EOF, clear buffer
self.input = "";
return Some(false);
};
let (prefix, rest) = self.input.split_at(pos);
if prefix.contains(|c| self.context.test_noncurrent(c)) {
// Other delimiters came earlier than current one
return Some(false);

Check warning on line 149 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L149

Added line #L149 was not covered by tests
}
if prefix.is_empty() {
match rest.chars().next() {
// The first char was delimiter (end of sequence)
Some(c) if self.context.test_noncurrent(c) => return Some(false),
// The first char was not delimiter
_ => return None,
}
}

self.input = rest;
Some(true)
}
}

impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
Expand All @@ -110,7 +169,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
where
V: Visitor<'de>,
{
visitor.visit_borrowed_str(self.str_until_lineend()?)
visitor.visit_borrowed_str(self.str_until_expect()?)
}

forward_to_deserialize_any! {
Expand Down Expand Up @@ -268,22 +327,21 @@ impl<'de, 'a> SeqAccess<'de> for List<'a, 'de> {
where
T: DeserializeSeed<'de>,
{
let mut delim_ok = false;
loop {
let char = self.de.peek_char();
match char {
Ok(c) if c == self.delim => delim_ok = true,
Ok('\n') | Err(DeserializeError::Eof) => return Ok(None),
_ => break,
self.de.context.enter(self.delim);
match self.de.consume_delimiters() {
Some(false) => {

Check warning on line 332 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L332

Added line #L332 was not covered by tests
self.de.context.exit();
return Ok(None);
}
self.de.next_char()?;
}
if !self.first && !delim_ok {
return Err(DeserializeError::ExpectedArrayComma);
// No need to clear context if deserializing stops here
None if !self.first => return Err(DeserializeError::ExpectedArrayComma),
_ => (),

Check warning on line 338 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L338

Added line #L338 was not covered by tests
}
self.first = false;

seed.deserialize(&mut *self.de).map(Some)
let res = seed.deserialize(&mut *self.de);
self.de.context.exit();
res.map(Some)
}
}

Expand All @@ -305,21 +363,19 @@ impl<'de, 'a> MapAccess<'de> for NewlineSeparated<'a, 'de> {
where
K: DeserializeSeed<'de>,
{
let mut newline = false;
loop {
let char = self.de.peek_char();
match char {
Ok('\n') => newline = true,
Err(DeserializeError::Eof) => return Ok(None),
_ => break,
self.de.context.enter('\n');
match self.de.consume_delimiters() {
Some(false) => {

Check warning on line 368 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L368

Added line #L368 was not covered by tests
self.de.context.exit();
return Ok(None);
}
self.de.next_char()?;
}
if !self.first && !newline {
return Err(DeserializeError::ExpectedMapNewline);
// No need to clear context if deserializing stops here
None if !self.first => return Err(DeserializeError::ExpectedMapNewline),
_ => (),

Check warning on line 374 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L374

Added line #L374 was not covered by tests
}
self.first = false;

self.de.context.enter(':');
seed.deserialize(&mut *self.de).map(Some)
}

Expand All @@ -328,8 +384,48 @@ impl<'de, 'a> MapAccess<'de> for NewlineSeparated<'a, 'de> {
V: DeserializeSeed<'de>,
{
if self.de.next_char()? != ':' {
// No need to clear context if deserializing stops here
return Err(DeserializeError::ExpectedMapColon);

Check warning on line 388 in src/model/parser/header/de.rs

View check run for this annotation

Codecov / codecov/patch

src/model/parser/header/de.rs#L388

Added line #L388 was not covered by tests
}
seed.deserialize(&mut *self.de)

self.de.context.exit();
let res = seed.deserialize(&mut *self.de);
self.de.context.exit();
res
}
}

#[cfg(test)]
mod tests {
use super::Deserializer;

#[test]
fn consume_delimiters() {
{
let mut de = Deserializer::from_str("\nFULL");
de.context.enter('\n');
assert_eq!(de.consume_delimiters(), Some(true));
assert_eq!(de.input, "FULL");
}
{
let mut de = Deserializer::from_str(",\nFULL");
de.context.enter('\n');
de.context.enter(',');
assert_eq!(de.consume_delimiters(), Some(true));
assert_eq!(de.input, "\nFULL");
}
{
let mut de = Deserializer::from_str("\nFULL");
de.context.enter('\n');
de.context.enter(',');
assert_eq!(de.consume_delimiters(), Some(false));
assert_eq!(de.input, "\nFULL");
}
{
let mut de = Deserializer::from_str("\nFULL");
de.context.enter(',');
assert_eq!(de.consume_delimiters(), None);
assert_eq!(de.input, "\nFULL");
}
}
}

0 comments on commit 0a0b962

Please sign in to comment.