Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "noa-parser"
version = "0.4.0"
version = "0.5.0"
edition = "2024"
homepage = "https://github.com/Akanoa/noa-parser"
repository = "https://github.com/Akanoa/noa-parser"
Expand Down
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

**0.5.0**

- Add support to separated list

**0.4.0**

- Add support to carriage return and tab recognizer
Expand Down
1 change: 1 addition & 0 deletions src/bytes/primitives/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ impl MatchSize for TokenNumber {
}

/// Define how to accept the token number.
#[derive(Debug, PartialEq)]
pub struct Number<T>(pub T);

/// Implement the `Visitor` trait for the token number.
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ pub mod peek;
pub mod peeker;
pub mod recognizer;
pub mod scanner;
pub mod separated_list;
pub mod visitor;
158 changes: 158 additions & 0 deletions src/separated_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use crate::errors::{ParseError, ParseResult};
use crate::scanner::Scanner;
use crate::visitor::Visitor;
use std::marker::PhantomData;

pub struct SeparatedList<T, V, S> {
pub(crate) data: Vec<V>,
separator: PhantomData<(S, T)>,
}

enum YieldResult<V> {
Last(V),
MaybeNext(V),
}

impl<T, V, S> SeparatedList<T, V, S> {
/// Consume the `SeparatedList` and return an iterator over the elements.
///
/// # Returns
///
/// An iterator over the elements of the `SeparatedList`.
pub fn into_iter(self) -> impl Iterator<Item = V> {
self.data.into_iter()
}
}

/// Yield the next element in the list and tell if it's the last one.
///
/// # Type Parameters
///
/// * `T` - The type of the data to scan.
/// * `V` - The type of the element to yield.
/// * `S` - The type of the separator to consume.
///
/// # Arguments
///
/// * `scanner` - The scanner to use.
///
/// # Returns
///
/// A `YieldResult` containing the element and whether it's the last one.
///
/// # Errors
///
/// Any error the visitor for the element or the separator returns.
fn yield_element<'a, T, V, S>(scanner: &mut Scanner<'a, T>) -> ParseResult<YieldResult<V>>
where
V: Visitor<'a, T>,
S: Visitor<'a, T>,
{
let cursor = scanner.current_position();
let element = match scanner.visit::<V>() {
Ok(element) => element,
Err(err) => {
scanner.jump_to(cursor);
return Err(err);
}
};

if scanner.remaining().is_empty() {
return Ok(YieldResult::Last(element));
}

// consume the separator if not the end of the slice
scanner.visit::<S>()?;

Ok(YieldResult::MaybeNext(element))
}

impl<'a, T, V, S> Visitor<'a, T> for SeparatedList<T, V, S>
where
V: Visitor<'a, T>,
S: Visitor<'a, T>,
{
/// Accept a list of elements separated by a separator.
///
/// # Arguments
///
/// * `scanner` - The scanner to use.
///
/// # Returns
///
/// A `ParseResult` containing the accepted `SeparatedList` on success, or
/// an error on failure.
///
/// # Errors
///
/// Any error the visitor for the element or the separator returns, or
/// `ParseError::UnexpectedToken` if the scanner is empty when attempting
/// to parse the separator.
fn accept(scanner: &mut Scanner<'a, T>) -> ParseResult<Self> {
let mut elements = vec![];
let cursor = scanner.current_position();

loop {
if let Ok(result) = yield_element::<T, V, S>(scanner) {
let element: YieldResult<V> = result;

match element {
YieldResult::Last(element) => {
elements.push(element);
break;
}
YieldResult::MaybeNext(element) => {
elements.push(element);
}
}
} else {
scanner.jump_to(cursor);
return Err(ParseError::UnexpectedToken);
}
}

Ok(SeparatedList {
data: elements,
separator: PhantomData,
})
}
}

#[cfg(test)]
mod tests {
use crate::bytes::primitives::number::Number;
use crate::bytes::token::Token;
use crate::errors::ParseResult;
use crate::recognizer::recognize;
use crate::scanner::Scanner;
use crate::separated_list::SeparatedList;
use crate::visitor::Visitor;

struct SeparatorComma;

impl<'a> Visitor<'a, u8> for SeparatorComma {
fn accept(scanner: &mut Scanner<'a, u8>) -> ParseResult<Self> {
recognize(Token::Comma, scanner)?;
Ok(SeparatorComma)
}
}

/// Tests parsing a list of `Number`s separated by commas.
///
/// Input: `b"12,4,78,22"`
/// Output: `vec![Number(12), Number(4), Number(78), Number(22)]`
/// Final position: 10
#[test]
fn test_parse_number_list() {
let data = b"12,4,78,22";
let mut scanner = Scanner::new(data);
let result = scanner
.visit::<SeparatedList<u8, Number<usize>, SeparatorComma>>()
.expect("failed to parse");
assert_eq!(
result.data,
vec![Number(12), Number(4), Number(78), Number(22)]
);
assert_eq!(scanner.current_position(), 10);
}
}