buni-rs 1.0.0

Reference Buni deserializer in Rust
Documentation
use crate::deserialize::Item;
use nom::branch::alt;
use nom::character::streaming::char;
use nom::combinator::map;
use nom::error::{ErrorKind, ParseError};
use nom::sequence::delimited;
use nom::{AsChar, IResult, Input, Parser};
use std::cell::Cell;

pub fn string<'a, I: Input, E: ParseError<I>>(input: I) -> IResult<I, Item, E>
where <I as Input>::Item: AsChar {
    alt((
        quoted_string::<I, E>,
        unquoted_string::<I, E>
    )).parse(input)
}

#[test]
pub fn test_string() {
    use nom::Finish;
    use crate::DeserializeError;
    match string::<&str, DeserializeError<&str>>("\"nya\\\"\\(\"").finish() {
        Ok(item) => assert_eq!(item, ("", Item::String(String::from("nya\"\\(")))),
        Err(err) => panic!("{}", err),
    }
    match string::<&str, DeserializeError<&str>>("nya\\\"\\(").finish() {
        Ok(item) => assert_eq!(item, ("", Item::String(String::from("nya\"(")))),
        Err(err) => panic!("{}", err),
    }
}

pub fn string_escaper<'a, I: Input, E: ParseError<I>, R: Fn(<I as Input>::Item, &mut Vec<char>) -> bool + 'a>(replacer: R) -> impl Fn(I) -> IResult<I, String, E>
where <I as Input>::Item: AsChar {
    move |input| {
        let buf_cell: Cell<Vec<char>> = Cell::new(Vec::with_capacity(input.input_len()));
        let split = input.split_at_position1_complete::<_, E>(|a| {
            let mut buf = buf_cell.take();
            let result = replacer(a, &mut buf);
            buf_cell.set(buf);
            result
        }, ErrorKind::Many)?;
        Ok((split.0, buf_cell
            .take()
            .iter()
            .collect::<String>()
        ))
    }
}

pub fn quoted_string<'a, I: Input, E: ParseError<I>>(input: I) -> IResult<I, Item, E>
where <I as Input>::Item: AsChar {
    map(delimited(
        char('"'),
        string_escaper(|p: <I as Input>::Item, history| {
            match p.as_char() {
                '"' => {
                    match history.last() {
                        None => true,
                        Some(last) => match last {
                            '\\' => {
                                history.pop();
                                history.push(p.as_char());
                                false
                            },
                            _ => {
                                true
                            }
                        }
                    }
                }
                'n' | 'r' | 't' | '\\' => {
                    match history.last() {
                        None => {
                            history.push(p.as_char());
                            false
                        },
                        Some(last) => match last {
                            '\\' => {
                                history.pop();
                                history.push(match p.as_char() {
                                    'n' => '\n',
                                    'r' => '\r',
                                    't' => '\t',
                                    '\\' => '\\',
                                    _ => unreachable!()
                                });
                                false
                            }
                            _ => {
                                history.push(p.as_char());
                                false
                            }
                        }
                    }
                }
                _ => {
                    history.push(p.as_char());
                    false
                }
            }
        }),
        char('"')
    ), Item::String).parse(input)
}

#[test]
pub fn test_quoted_string() {
    use nom::Finish;
    use crate::DeserializeError;
    match quoted_string::<&str, DeserializeError<&str>>("\"nya\\\"\\(\"").finish() {
        Ok(item) => assert_eq!(item, ("", Item::String(String::from("nya\"\\(")))),
        Err(err) => panic!("{}", err),
    }
}

pub fn unquoted_string<'a, I: Input, E: ParseError<I>>(input: I) -> IResult<I, Item, E>
where <I as Input>::Item: AsChar {
    map(string_escaper(|p: <I as Input>::Item, history| {
        match p.as_char() {
            '"' | '(' | ')' | ',' | ' ' | '\t' | '\n' | '\r' => {
                match history.last() {
                    None => true,
                    Some(last) => match last {
                        '\\' => {
                            history.pop();
                            history.push(p.as_char());
                            false
                        },
                        _ => true
                    }
                }
            }
            'n' | 'r' | 't' | '\\' => {
                match history.last() {
                    None => history.push(p.as_char()),
                    Some(last) => match last {
                        '\\' => {
                            history.pop();
                            history.push(match p.as_char() {
                                'n' => '\n',
                                'r' => '\r',
                                't' => '\t',
                                '\\' => '\\',
                                _ => unreachable!()
                            })
                        }
                        _ => history.push(p.as_char())
                    }
                }
                false
            }
            _ => {
                history.push(p.as_char());
                false
            }
        }
    }), Item::String).parse(input)
}

#[test]
pub fn test_unquoted_string() {
    use nom::Finish;
    use crate::DeserializeError;
    match unquoted_string::<&str, DeserializeError<&str>>("nya\\\"\\(").finish() {
        Ok(item) => assert_eq!(item, ("", Item::String(String::from("nya\"(")))),
        Err(err) => panic!("{}", err),
    }
}