buni-rs 1.2.0

Reference Buni serializer / deserializer in Rust
Documentation
use crate::deserialize::numbers::{float, int, uint};
use crate::deserialize::string::string;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt::Debug;
use nom::branch::alt;
use nom::bytes::streaming::tag_no_case;
use nom::character::streaming::char;
use nom::combinator::{map, value};
use nom::error::ParseError;
use nom::sequence::delimited;
use nom::{AsChar, Compare, IResult, Input, Parser};

mod string;
mod numbers;
#[cfg(feature = "serde")]
mod adapter;
mod error;

#[cfg(feature = "iter")]
pub use adapter::from_iter;
#[cfg(feature = "serde")]
pub use adapter::from_str;
#[cfg(feature = "serde")]
pub use adapter::Deserializer;
pub use error::DeserializeError;

pub type AnnotatedList = (Box<Item>, Vec<Item>);

#[derive(Clone, PartialEq, Debug)]
pub enum Item {
    AnnotatedList(AnnotatedList),
    List(Vec<Item>),
    String(String),
    Bool(bool),
    Int(i64),
    UInt(u64),
    Float(f64),
    Null
}

pub fn literal<'a, I: Input + ToString + Compare<&'a str>, E: ParseError<I>>(input: I) -> IResult<I, Item, E>
where <I as Input>::Item: AsChar,
      String: FromIterator<I>
{
    alt((
        list::<I, E>,
        bool::<I, E>,
        float::<I, E>,
        uint::<I, E>,
        int::<I, E>,
        null::<I, E>,
        string::<I, E>
    )).parse(input)
}

#[test]
pub fn test_literal() {
    use nom::Finish;
    use alloc::vec;
    match literal::<&str, DeserializeError<&str>>("(a(1))").finish() {
        Ok(item) => assert_eq!(
            item,
            ("", Item::List(vec![Item::AnnotatedList((
                Box::new(Item::String("a".to_string())),
                vec![
                    Item::UInt(1)
                ],
            ))]))
        ),
        Err(err) => panic!("{}", err),
    }
    match literal::<&str, DeserializeError<&str>>("(a(1),b(-2),c(3.14),null(true))").finish() {
        Ok(item) => assert_eq!(
            item,
            ("", Item::List(vec![
                Item::AnnotatedList((
                    Box::new(Item::String("a".to_string())),
                    vec![
                        Item::UInt(1)
                    ],
                )), Item::AnnotatedList((
                    Box::new(Item::String("b".to_string())),
                    vec![
                        Item::Int(-2)
                    ],
                )),
                Item::AnnotatedList((
                    Box::new(Item::String("c".to_string())),
                    vec![
                        Item::Float(3.14)
                    ],
                )),
                Item::AnnotatedList((
                    Box::new(Item::Null),
                    vec![
                        Item::Bool(true)
                    ],
                )),
            ]))
        ),
        Err(err) => panic!("{}", err),
    }
}

fn process_list<'a, I: Input, E: ParseError<I>, P: Parser<I, Error = E, Output = Item>>(mut parser: P, quit_on: Option<char>) -> impl FnMut(I) -> IResult<I, Vec<Item>, E>
where <I as Input>::Item: AsChar {
    move |input| {
        let mut outputs: Vec<Item> = Vec::new();
        let mut input: I = input;
        let mut input_changed = true;
        while input_changed {
            input_changed = false;
            let mut seen_escape = false;
            for (index, item) in input.iter_indices() {
                match item.as_char() {
                    '\n' | ',' => {
                        seen_escape = true;
                        continue;
                    },
                    ' ' | '\t' | '\r' => continue,
                    item => {
                        match quit_on {
                            None => {}
                            Some(quit_on) => if item == quit_on {
                                return Ok((input.take_from(index), outputs))
                            }
                        }

                        let out = parser.parse(input.take_from(index))?;
                        input = out.0;
                        input_changed = true;
                        if seen_escape {
                            outputs.push(match out.1 {
                                Item::List(items) => {
                                    if items.len() == 1 {
                                        match items.into_iter().next() {
                                            None => unreachable!(),
                                            Some(item) => item
                                        }
                                    } else {
                                        Item::List(items)
                                    }
                                }
                                i => i
                            });
                        } else {
                            match outputs.pop() {
                                None => outputs.push(out.1),
                                Some(last) => match out.1 {
                                    Item::List(v) => outputs.push(Item::AnnotatedList((
                                        Box::new(last),
                                        v
                                    ))),
                                    i => {
                                        outputs.push(last);
                                        outputs.push(i);
                                    }
                                }
                            }
                        }
                        break
                    }
                }
            }
        }
        return Ok((input.take(0), outputs))
    }
}

pub fn deserialize<'a, I: Input + ToString + Compare<&'a str>, E: ParseError<I>>(input: I) -> IResult<I, Vec<Item>, E>
where <I as Input>::Item: AsChar,
      String: FromIterator<I>
{
    process_list(literal::<I, E>, None).parse(input)
}

#[test]
pub fn test_undelimited_list() {
    use nom::Finish;
    use alloc::vec;
    assert_eq!(
        deserialize::<&str, DeserializeError<&str>>("a\n b\n nya").finish().unwrap(),
        ("", vec![
            Item::String("a".to_string()),
            Item::String("b".to_string()),
            Item::String("nya".to_string())
        ])
    );
}

pub fn list<'a, I: Input + ToString + Compare<&'a str>, E: ParseError<I>>(input: I) -> IResult<I, Item, E>
where <I as Input>::Item: AsChar,
      String: FromIterator<I>
{
    map(delimited(
        char('('),
        process_list(literal::<I, E>, Some(')')),
        char(')')
    ), Item::List).parse(input)
}

#[test]
pub fn test_list() {
    use nom::Finish;
    use alloc::vec;
    assert_eq!(
        list::<&str, DeserializeError<&str>>("(1,-2)nya").finish().unwrap(),
        ("nya", Item::List(vec![
            Item::UInt(1),
            Item::Int(-2)
        ]))
    );
    assert_eq!(
        list::<&str, DeserializeError<&str>>("(a,b)nya").finish().unwrap(),
        ("nya", Item::List(vec![
            Item::String("a".to_string()),
            Item::String("b".to_string()),
        ]))
    );
    assert_eq!(
        list::<&str, DeserializeError<&str>>("(1\n -2 )nya").finish().unwrap(),
        ("nya", Item::List(vec![
            Item::UInt(1),
            Item::Int(-2)
        ]))
    );
    assert_eq!(
        list::<&str, DeserializeError<&str>>("(a\n b )nya").finish().unwrap(),
        ("nya", Item::List(vec![
            Item::String("a".to_string()),
            Item::String("b".to_string()),
        ]))
    );
    assert_eq!(
        list::<&str, DeserializeError<&str>>("(1\n, -2, nya)nya").finish().unwrap(),
        ("nya", Item::List(vec![
            Item::UInt(1),
            Item::Int(-2),
            Item::String("nya".to_string())
        ]))
    );
    assert_eq!(
        list::<&str, DeserializeError<&str>>("(a\n b\n nya)nya").finish().unwrap(),
        ("nya", Item::List(vec![
            Item::String("a".to_string()),
            Item::String("b".to_string()),
            Item::String("nya".to_string())
        ]))
    );
}

pub fn bool<'a, I: Input + Compare<&'a str>, E: ParseError<I> >(input: I) -> IResult<I, Item, E> {
    map(alt((
        value(true, tag_no_case("true")),
        value(false, tag_no_case("false"))
    )), Item::Bool).parse(input)
}

#[test]
pub fn test_bool() {
    use nom::Finish;
    assert_eq!(
        bool::<&str, DeserializeError<&str>>("truenya").finish().unwrap(),
        ("nya", Item::Bool(true))
    );
    assert_eq!(
        bool::<&str, DeserializeError<&str>>("falsenya").finish().unwrap(),
        ("nya", Item::Bool(false))
    );
}

pub fn null<'a, I: Input + Compare<&'a str>, E: ParseError<I>>(input: I) -> IResult<I, Item, E> {
    value(Item::Null, tag_no_case("null")).parse(input)
}

#[test]
pub fn test_null() {
    use nom::Finish;
    assert_eq!(
        null::<&str, DeserializeError<&str>>("nullnya").finish().unwrap(),
        ("nya", Item::Null)
    );
}