buni-rs 1.0.0

Reference Buni deserializer in Rust
Documentation
use crate::deserialize::numbers::{float, int, uint};
use crate::deserialize::string::string;
use std::fmt::Debug;
use nom::branch::alt;
use nom::bytes::streaming::tag_no_case;
use nom::character::streaming::char;
use nom::combinator::{map, not, value};
use nom::error::ParseError;
use nom::sequence::delimited;
use nom::{AsChar, Compare, IResult, Input, Needed, Parser};

mod string;
mod numbers;
mod adapter;
mod error;

pub use adapter::from_iter;
pub use adapter::from_str;
pub use adapter::Deserializer;
pub use error::DeserializeError;

#[derive(Clone, Debug, PartialEq)]
pub struct 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;
    match literal::<&str, DeserializeError<&str>>("(a(1))").finish() {
        Ok(item) => assert_eq!(
            item,
            ("", Item::List(vec![Item::AnnotatedList(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(AnnotatedList(
                    Box::new(Item::String("a".to_string())),
                    vec![
                        Item::UInt(1)
                    ],
                )), Item::AnnotatedList(AnnotatedList(
                    Box::new(Item::String("b".to_string())),
                    vec![
                        Item::Int(-2)
                    ],
                )),
                Item::AnnotatedList(AnnotatedList(
                    Box::new(Item::String("c".to_string())),
                    vec![
                        Item::Float(3.14)
                    ],
                )),
                Item::AnnotatedList(AnnotatedList(
                    Box::new(Item::Null),
                    vec![
                        Item::Bool(true)
                    ],
                )),
            ]))
        ),
        Err(err) => panic!("{}", err),
    }
}

fn strip_whitespace_or_comma<'a, I: Input, E: ParseError<I>, P: Parser<I, Error = E, Output = Item>>(mut parser: P, quit_on: 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 predicate = |(_, item): &(_, <I as Input>::Item)| {
            match item.as_char() {
                ' ' => false,
                '\r' => false,
                '\n' => false,
                '\t' => false,
                ',' => false,
                _ => true
            }
        };
        loop {
            match input.iter_indices().find(predicate) {
                None => break Err(nom::Err::Incomplete(Needed::Unknown)),
                Some(found) => {
                    if found.1.as_char() == quit_on {
                        break Ok((input.take_from(found.0), outputs));
                    }
                    println!("RUNNING: {}", input.iter_elements().map(|a| a.as_char()).collect::<String>());
                    match parser.parse(input.take_from(found.0)) {
                        Ok(out) => {
                            println!("RAN: {}", out.0.iter_elements().map(|a| a.as_char()).collect::<String>());
                            input = out.0;
                            if found.0 == 0 {
                                match out.1 {
                                    Item::List(value) => {
                                        match outputs.pop() {
                                            None => outputs.push(Item::List(value)),
                                            Some(key) => {
                                                outputs.push(Item::AnnotatedList(AnnotatedList(Box::new(key), value)));
                                            }
                                        }
                                    }
                                    _ => outputs.push(out.1)
                                }
                            } else {
                                outputs.push(out.1)
                            }
                        }
                        Err(error) => match error {
                            nom::Err::Incomplete(_) => break Err(error),
                            nom::Err::Error(_) => break Ok((input, outputs)),
                            nom::Err::Failure(_) => break Err(error)
                        }
                    }
                }
            }
        }
    }
}

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>
{
    let parse = strip_whitespace_or_comma(alt((
        literal::<I, E>,
        map(not::<I, E, _>(char::<I, E>(')')), |_| Item::Null),
    )), ')');
    map(delimited(
        char('('),
        parse,
        char(')')
    ), |a| Item::List(a)).parse(input)
}

#[test]
pub fn test_list() {
    use nom::Finish;
    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 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)
    );
}