parser.rs
raw
//! Core parser traits and utilities.
//!
//! This module contains the fundamental [`Parser`] trait that all parsers implement,
//! along with the [`Source`] wrapper for managing input streams with position tracking
//! and backtracking support.
//!
//! The [`Parser`] trait uses a Template Method Pattern where the `parse()` method
//! handles caching and backtracking automatically, while implementations provide
//! custom parsing logic in the `read()` method.
//!
//! The module also provides comprehensive blanket implementations for smart pointers
//! like `Box<T>`, `Arc<T>`, `Rc<T>`, and synchronization primitives, enabling
//! flexible parser composition and thread-safe usage patterns.
use std::cell::RefCell;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::io::{Read, Seek};
use std::rc::{Rc, Weak as RcWeak};
use std::sync::{Arc, Mutex as StdMutex, RwLock as StdRwLock, Weak as ArcWeak};
use std::any::{Any, TypeId};
use parking_lot::{Mutex, RwLock};
use crate::{
cache::{BasicCache, ParsingCache},
result::{Error, ParseResult},
};
pub trait Parsable: Read + Seek {}
impl<T> Parsable for T where T: Read + Seek {}
/// Encapsulates a [`std::io::Read`] + [`std::io::Seek`] as a source
/// of bytes to parse from.
///
/// Note that while anything that has the required traits will work,
/// using a [`std::io::BufReader`] or [`std::io::Cursor`] will usually
/// be the most efficient choice. Seek capabilities are used heavily
/// while parsing.
pub struct Source<S> {
source: S,
stack: Vec<u64>,
index: u64,
}
impl<S> Source<S>
where
S: Parsable,
{
/// Create a new Source wrapping a Read + Seek.
pub fn new(mut source: S) -> Source<S> {
let index = source.stream_position().unwrap_or(0);
Source {
source,
stack: Vec::with_capacity(20),
index,
}
}
/// Store the current location. Each push should be paired with
/// either a pop (to backtrack to the pushed location) or a commit
/// (to assert that the pushed location is no longer needed)
pub fn push(&mut self) {
self.stack.push(self.index);
}
/// Return to the previously pushed location
pub fn pop(&mut self) {
self.index = self.stack.pop().unwrap_or(0);
}
/// Discard the previously pushed location
pub fn commit(&mut self) {
self.stack.pop();
}
/// Retrieve the next byte from the source
pub fn peek1(&mut self) -> Result<u8, Error> {
let mut buf = [0; 1];
self.source.seek(std::io::SeekFrom::Start(self.index))?;
if let Err(err) = self.source.read_exact(&mut buf) {
if err.kind() == std::io::ErrorKind::UnexpectedEof {
return Err(Error::NoMatch);
}
return Err(Error::from(err));
}
Ok(buf[0])
}
/// Retrieve the next `bytes` bytes from the source
pub fn peek(&mut self, bytes: usize) -> Result<Vec<u8>, Error> {
let mut buf = vec![0; bytes];
self.source.seek(std::io::SeekFrom::Start(self.index))?;
if let Err(err) = self.source.read_exact(&mut buf) {
if err.kind() == std::io::ErrorKind::UnexpectedEof {
return Err(Error::NoMatch);
}
return Err(Error::from(err));
}
Ok(buf)
}
/// Move the current position in the source forward by `bytes` bytes.
pub fn advance(&mut self, bytes: usize) {
self.index += bytes as u64;
}
/// Retrieve the next `bytes` bytes from the source, and move the
/// current position forward by the same number of bytes.
pub fn read(&mut self, bytes: usize) -> Result<Vec<u8>, Error> {
let ret = self.peek(bytes)?;
self.advance(bytes);
Ok(ret)
}
}
pub trait Parser<Ctx = ()>
where
Self: Any,
{
type Output: Any + Clone;
/// This is the function you implement in order to make a type Parsable
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable;
/// Identifies the specific parser. For parsers which are not
/// parameterized, the default TypeId-based implementation is
/// sufficient. However, if the parser is contructed with
/// parameters, it may need to have a different id for each
/// combination of parameters in order to avoid false positives in
/// the cache. For example, the Literal parser needs to override
/// this so that parsers for different literals are not conflated
/// in the cache.
///
/// It's recommended that when overridden, the return value should
/// be generated with [DefaultHasher], and `TypeId::of<Self>()`
/// should be the first thing added to the hash, followed by
/// whatever other information is needed to differentiate
/// instances of the type.
fn id(&self) -> u64 {
let mut hasher = DefaultHasher::new();
TypeId::of::<Self>().hash(&mut hasher);
hasher.finish()
}
/// This is the function you call to try parsing the type from the
/// source. It is a wrapper which does necessary book-keeping
/// around calls to the `read` function.
fn parse<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
let parser_id = self.id();
let before = source.index;
if let Some((cached_result, after)) = cache.lookup::<Self::Output>(parser_id, before) {
source.index = after;
return Ok(cached_result);
}
source.push();
match self.read(source, cache, context) {
Ok(val) => {
source.commit();
// Store result in cache
cache.record::<Self::Output>(parser_id, before, val.clone(), source.index);
Ok(val)
}
Err(err) => match err {
Error::NoMatch => {
source.pop();
Err(err)
}
_ => Err(err),
},
}
}
}
/// Global entry point for parsing a grammar
pub fn parse<R, S>(parser: R, source: &mut Source<S>) -> Result<R::Output, Error>
where
R: Parser,
S: Parsable,
{
let mut cache = BasicCache::new();
let mut context = ();
parser.parse(source, &mut cache, &mut context)
}
/// Global entry point for parsing a grammar, if you want to specify a cache
pub fn parse_with_cache<R, S>(
parser: R,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
) -> Result<R::Output, Error>
where
R: Parser,
S: Parsable,
{
let mut context = ();
parser.parse(source, cache, &mut context)
}
/// Global entry point for parsing a grammar with context
pub fn parse_with_context<R, S, Ctx>(
parser: R,
source: &mut Source<S>,
context: &mut Ctx,
) -> Result<R::Output, Error>
where
R: Parser<Ctx>,
S: Parsable,
{
let mut cache = BasicCache::new();
parser.parse(source, &mut cache, context)
}
/// Global entry point for parsing a grammar with both cache and context
pub fn parse_with_cache_and_context<R, S, Ctx>(
parser: R,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> Result<R::Output, Error>
where
R: Parser<Ctx>,
S: Parsable,
{
parser.parse(source, cache, context)
}
/// () is a Parser which always matches after consuming zero bytes.
impl<Ctx> Parser<Ctx> for () {
type Output = ();
fn read<S>(
&self,
_source: &mut Source<S>,
_cache: &mut impl ParsingCache,
_context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
Ok(())
}
}
/// Blanket implementation for `Box<T> where T: Parser`
/// This allows boxed parsers to work correctly with the parsing system
impl<T, Ctx> Parser<Ctx> for Box<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.as_ref().id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.as_ref().read(source, cache, context)
}
}
/// Blanket implementation for `Arc<T> where T: Parser`
/// This allows shared parsers to work correctly with the parsing system
impl<T, Ctx> Parser<Ctx> for Arc<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.as_ref().id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.as_ref().read(source, cache, context)
}
}
/// Blanket implementation for `Rc<T> where T: Parser`
/// This allows reference-counted parsers to work correctly with the parsing system
impl<T, Ctx> Parser<Ctx> for Rc<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.as_ref().id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.as_ref().read(source, cache, context)
}
}
/// Blanket implementation for `Mutex<T> where T: Parser`
/// This allows mutex-protected parsers to work correctly with the parsing system
impl<T, Ctx> Parser<Ctx> for Mutex<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.lock().id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.lock().read(source, cache, context)
}
}
/// Blanket implementation for `RwLock<T> where T: Parser`
/// This allows read-write locked parsers to work correctly with the parsing system
impl<T, Ctx> Parser<Ctx> for RwLock<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.read().id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.read().read(source, cache, context)
}
}
/// Blanket implementation for `RefCell<T> where T: Parser`
/// This allows ref-cell protected parsers to work correctly with the parsing system
impl<T, Ctx> Parser<Ctx> for RefCell<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.borrow().id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.borrow().read(source, cache, context)
}
}
/// Blanket implementation for `std::sync::Mutex<T> where T: Parser`
/// This allows standard library mutex-protected parsers to work correctly with the parsing system
/// Panics if the mutex is poisoned
impl<T, Ctx> Parser<Ctx> for StdMutex<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.lock().expect("Mutex poisoned").id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.lock()
.expect("Mutex poisoned")
.read(source, cache, context)
}
}
/// Blanket implementation for `std::sync::RwLock<T> where T: Parser`
/// This allows standard library read-write locked parsers to work correctly with the parsing system
/// Panics if the RwLock is poisoned
impl<T, Ctx> Parser<Ctx> for StdRwLock<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
self.read().expect("RwLock poisoned").id()
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
self.read()
.expect("RwLock poisoned")
.read(source, cache, context)
}
}
/// Blanket implementation for `ArcWeak<T> where T: Parser`
/// This allows weak references to shared parsers
impl<T, Ctx> Parser<Ctx> for ArcWeak<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
// For weak references, we need to upgrade to get the id
// If the upgrade fails, we'll generate a default id based on the type
if let Some(strong) = self.upgrade() {
strong.as_ref().id()
} else {
// If the reference is dead, generate a consistent id based on the type
let mut hasher = DefaultHasher::new();
TypeId::of::<ArcWeak<T>>().hash(&mut hasher);
hasher.finish()
}
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
if let Some(strong) = self.upgrade() {
strong.as_ref().read(source, cache, context)
} else {
Err(Error::NoMatch)
}
}
}
/// Blanket implementation for `RcWeak<T> where T: Parser`
/// This allows weak references to reference-counted parsers
impl<T, Ctx> Parser<Ctx> for RcWeak<T>
where
T: Parser<Ctx>,
{
type Output = T::Output;
fn id(&self) -> u64 {
// For weak references, we need to upgrade to get the id
// If the upgrade fails, we'll generate a default id based on the type
if let Some(strong) = self.upgrade() {
strong.as_ref().id()
} else {
// If the reference is dead, generate a consistent id based on the type
let mut hasher = DefaultHasher::new();
TypeId::of::<RcWeak<T>>().hash(&mut hasher);
hasher.finish()
}
}
fn read<S>(
&self,
source: &mut Source<S>,
cache: &mut impl ParsingCache,
context: &mut Ctx,
) -> ParseResult<Self::Output>
where
S: Parsable,
{
if let Some(strong) = self.upgrade() {
strong.as_ref().read(source, cache, context)
} else {
Err(Error::NoMatch)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cache::{BasicCache, NoCache};
use std::io::Cursor;
#[test]
fn test_source_creation() {
let data = b"hello world";
let cursor = Cursor::new(data);
let source = Source::new(cursor);
assert_eq!(source.index, 0);
assert!(source.stack.is_empty());
}
#[test]
fn test_source_peek1() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let byte = source.peek1().unwrap();
assert_eq!(byte, b'h');
// Position should not change after peek
assert_eq!(source.index, 0);
// Peek again should give same result
let byte2 = source.peek1().unwrap();
assert_eq!(byte2, b'h');
}
#[test]
fn test_source_peek1_empty() {
let data = b"";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let result = source.peek1();
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_source_peek_multiple() {
let data = b"hello world";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let bytes = source.peek(5).unwrap();
assert_eq!(bytes, b"hello");
// Position should not change after peek
assert_eq!(source.index, 0);
// Peek more bytes
let bytes = source.peek(11).unwrap();
assert_eq!(bytes, b"hello world");
}
#[test]
fn test_source_peek_beyond_end() {
let data = b"hi";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let result = source.peek(5);
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_source_advance() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
source.advance(2);
assert_eq!(source.index, 2);
let byte = source.peek1().unwrap();
assert_eq!(byte, b'l');
source.advance(3);
assert_eq!(source.index, 5);
let result = source.peek1();
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_source_read() {
let data = b"hello world";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let bytes = source.read(5).unwrap();
assert_eq!(bytes, b"hello");
assert_eq!(source.index, 5);
let byte = source.peek1().unwrap();
assert_eq!(byte, b' ');
let bytes = source.read(6).unwrap();
assert_eq!(bytes, b" world");
assert_eq!(source.index, 11);
}
#[test]
fn test_source_read_beyond_end() {
let data = b"hi";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let result = source.read(5);
assert!(matches!(result, Err(Error::NoMatch)));
assert_eq!(source.index, 0); // Position should not change on error
}
#[test]
fn test_source_push_pop() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
source.advance(2);
assert_eq!(source.index, 2);
source.push();
assert_eq!(source.stack.len(), 1);
source.advance(2);
assert_eq!(source.index, 4);
source.pop();
assert_eq!(source.index, 2);
assert_eq!(source.stack.len(), 0);
}
#[test]
fn test_source_push_commit() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
source.advance(2);
source.push();
source.advance(2);
assert_eq!(source.index, 4);
source.commit();
assert_eq!(source.index, 4); // Position stays the same
assert_eq!(source.stack.len(), 0);
}
#[test]
fn test_source_nested_push_pop() {
let data = b"hello world";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
// First level
source.advance(2);
source.push();
// Second level
source.advance(3);
source.push();
// Third level
source.advance(2);
assert_eq!(source.index, 7);
// Pop third level
source.pop();
assert_eq!(source.index, 5);
// Pop second level
source.pop();
assert_eq!(source.index, 2);
}
#[test]
fn test_source_empty_stack_pop() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
source.advance(3);
source.pop(); // Should reset to 0
assert_eq!(source.index, 0);
}
#[test]
fn test_unit_parser() {
let data = b"anything";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let unit_parser = ();
parse(unit_parser, &mut source).unwrap();
assert_eq!(source.index, 0); // Should not advance
}
#[test]
fn test_unit_parser_empty_input() {
let data = b"";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let unit_parser = ();
parse(unit_parser, &mut source).unwrap();
}
#[test]
fn test_parse_global_function() {
let data = b"test";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let unit_parser = ();
parse(unit_parser, &mut source).unwrap();
}
#[test]
fn test_parse_with_cache_global_function() {
let data = b"test";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let mut cache = NoCache;
let unit_parser = ();
parse_with_cache(unit_parser, &mut source, &mut cache).unwrap();
}
#[test]
fn test_source_position_management() {
let data = b"0123456789";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
// Test sequential reads
for i in 0..10 {
let byte = source.peek1().unwrap();
assert_eq!(byte, b'0' + i as u8);
source.advance(1);
assert_eq!(source.index, i + 1);
}
// Should be at end
let result = source.peek1();
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_source_large_advance() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
source.advance(1000);
assert_eq!(source.index, 1000);
// Should fail to read
let result = source.peek1();
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_source_zero_read() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let bytes = source.read(0).unwrap();
assert_eq!(bytes, b"");
assert_eq!(source.index, 0);
}
#[test]
fn test_source_zero_peek() {
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let bytes = source.peek(0).unwrap();
assert_eq!(bytes, b"");
assert_eq!(source.index, 0);
}
#[test]
fn test_id_implementation_literal_parsers() {
// Test that Literal correctly implements custom id() method
// Different Literal parsers should have different IDs since they have different parameters
use crate::literal::Literal;
let literal1 = Literal::from_str("hello");
let literal2 = Literal::from_str("world");
// These should have different IDs because they have different content
assert_ne!(
<Literal as crate::parser::Parser<()>>::id(&literal1),
<Literal as crate::parser::Parser<()>>::id(&literal2),
"Different Literal instances should have different IDs to avoid cache conflicts"
);
// Same content should have same ID
let literal3 = Literal::from_str("hello");
assert_eq!(
<Literal as crate::parser::Parser<()>>::id(&literal1),
<Literal as crate::parser::Parser<()>>::id(&literal3),
"Literal instances with same content should have same ID for cache efficiency"
);
}
#[test]
fn test_id_implementation_unit_parser() {
// Unit parser () uses default id() implementation
// Since it has no parameters, using default implementation is correct
let unit1 = ();
let unit2 = ();
// These should have the same ID since they're identical
assert_eq!(
<() as crate::parser::Parser<()>>::id(&unit1),
<() as crate::parser::Parser<()>>::id(&unit2),
"Unit parsers should have same ID since they're functionally identical"
);
}
#[test]
fn test_parser_caching_behavior() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let mut cache = BasicCache::new();
let literal = Literal::from_str("hello");
// First parse should work and cache result
let result1 = parse_with_cache(literal.clone(), &mut source, &mut cache).unwrap();
assert_eq!(result1, b"hello".as_slice().into());
// Reset position
source.index = 0;
// Second parse should use cached result
let result2 = parse_with_cache(literal, &mut source, &mut cache).unwrap();
assert_eq!(result2, b"hello".as_slice().into());
}
#[test]
fn test_arc_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = Arc::new(Literal::from_str("hello"));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_rc_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = Rc::new(Literal::from_str("hello"));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_arc_mutex_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = Arc::new(Mutex::new(Literal::from_str("hello")));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_arc_rwlock_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = Arc::new(RwLock::new(Literal::from_str("hello")));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_rc_refcell_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = Rc::new(RefCell::new(Literal::from_str("hello")));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_std_mutex_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = StdMutex::new(Literal::from_str("hello"));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_std_rwlock_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = StdRwLock::new(Literal::from_str("hello"));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_arc_std_mutex_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = Arc::new(StdMutex::new(Literal::from_str("hello")));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_arc_std_rwlock_parser() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let literal = Arc::new(StdRwLock::new(Literal::from_str("hello")));
let result = parse(literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_smart_pointer_parser_id_consistency() {
use crate::literal::Literal;
let base_literal = Literal::from_str("hello");
let arc_mutex = Arc::new(Mutex::new(Literal::from_str("hello")));
let arc_rwlock = Arc::new(RwLock::new(Literal::from_str("hello")));
let rc_refcell = Rc::new(RefCell::new(Literal::from_str("hello")));
// All should have the same ID since they wrap the same parser
assert_eq!(
<Literal as crate::parser::Parser<()>>::id(&base_literal),
<Arc<Mutex<Literal>> as crate::parser::Parser<()>>::id(&arc_mutex)
);
assert_eq!(
<Literal as crate::parser::Parser<()>>::id(&base_literal),
<Arc<RwLock<Literal>> as crate::parser::Parser<()>>::id(&arc_rwlock)
);
assert_eq!(
<Literal as crate::parser::Parser<()>>::id(&base_literal),
<Rc<RefCell<Literal>> as crate::parser::Parser<()>>::id(&rc_refcell)
);
}
#[test]
fn test_arc_weak_parser_alive() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let arc_literal = Arc::new(Literal::from_str("hello"));
let weak_literal = Arc::downgrade(&arc_literal);
// ID should match the strong reference
assert_eq!(
<Arc<Literal> as crate::parser::Parser<()>>::id(&arc_literal),
<std::sync::Weak<Literal> as crate::parser::Parser<()>>::id(&weak_literal)
);
// Should work while the Arc is alive
let result = parse(weak_literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_arc_weak_parser_dropped() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let weak_literal = {
let arc_literal = Arc::new(Literal::from_str("hello"));
Arc::downgrade(&arc_literal)
}; // arc_literal is dropped here
// Should fail with NoMatch since the Arc was dropped
let result = parse(weak_literal, &mut source);
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_rc_weak_parser_alive() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let rc_literal = Rc::new(Literal::from_str("hello"));
let weak_literal = Rc::downgrade(&rc_literal);
// ID should match the strong reference
assert_eq!(
<Rc<Literal> as crate::parser::Parser<()>>::id(&rc_literal),
<std::rc::Weak<Literal> as crate::parser::Parser<()>>::id(&weak_literal)
);
// Should work while the Rc is alive
let result = parse(weak_literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_rc_weak_parser_dropped() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let weak_literal = {
let rc_literal = Rc::new(Literal::from_str("hello"));
Rc::downgrade(&rc_literal)
}; // rc_literal is dropped here
// Should fail with NoMatch since the Rc was dropped
let result = parse(weak_literal, &mut source);
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_arc_weak_mutex_parser_alive() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let arc_literal = Arc::new(Mutex::new(Literal::from_str("hello")));
let weak_literal = Arc::downgrade(&arc_literal);
// ID should match the strong reference
assert_eq!(
<Arc<Mutex<Literal>> as crate::parser::Parser<()>>::id(&arc_literal),
<std::sync::Weak<Mutex<Literal>> as crate::parser::Parser<()>>::id(&weak_literal)
);
// Should work while the Arc is alive
let result = parse(weak_literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_arc_weak_mutex_parser_dropped() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let weak_literal = {
let arc_literal = Arc::new(Mutex::new(Literal::from_str("hello")));
Arc::downgrade(&arc_literal)
}; // arc_literal is dropped here
// Should fail with NoMatch since the Arc was dropped
let result = parse(weak_literal, &mut source);
assert!(matches!(result, Err(Error::NoMatch)));
}
#[test]
fn test_arc_weak_rwlock_parser_alive() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let arc_literal = Arc::new(RwLock::new(Literal::from_str("hello")));
let weak_literal = Arc::downgrade(&arc_literal);
// ID should match the strong reference
assert_eq!(
<Arc<RwLock<Literal>> as crate::parser::Parser<()>>::id(&arc_literal),
<std::sync::Weak<RwLock<Literal>> as crate::parser::Parser<()>>::id(&weak_literal)
);
// Should work while the Arc is alive
let result = parse(weak_literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_rc_weak_refcell_parser_alive() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let rc_literal = Rc::new(RefCell::new(Literal::from_str("hello")));
let weak_literal = Rc::downgrade(&rc_literal);
// ID should match the strong reference
assert_eq!(
<Rc<RefCell<Literal>> as crate::parser::Parser<()>>::id(&rc_literal),
<std::rc::Weak<RefCell<Literal>> as crate::parser::Parser<()>>::id(&weak_literal)
);
// Should work while the Rc is alive
let result = parse(weak_literal, &mut source).unwrap();
assert_eq!(result, b"hello".as_slice().into());
}
#[test]
fn test_rc_weak_refcell_parser_dropped() {
use crate::literal::Literal;
let data = b"hello";
let cursor = Cursor::new(data);
let mut source = Source::new(cursor);
let weak_literal = {
let rc_literal = Rc::new(RefCell::new(Literal::from_str("hello")));
Rc::downgrade(&rc_literal)
}; // rc_literal is dropped here
// Should fail with NoMatch since the Rc was dropped
let result = parse(weak_literal, &mut source);
assert!(matches!(result, Err(Error::NoMatch)));
}
}