repl_framework 0.3.0

A crate to easily create simple repls
Documentation
use std::{mem, ops};

#[derive(Clone, Copy)]
pub(crate) struct FnPtr<T>(pub fn(&mut T, Vec<String>));

impl<T> std::fmt::Debug for FnPtr<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Pointer::fmt(&(self.0 as usize as *const ()), f)
    }
}

impl<T> ops::Deref for FnPtr<T> {
    type Target = fn(&mut T, Vec<String>);

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

pub fn parse(input: String) -> Vec<String> {
    #[derive(Debug, Clone, Default)]
    struct ParserState {
        in_literal: bool,
        last_char: Option<char>,
        last_quote: Option<char>,
        parsed_args: Vec<String>,
        current_arg: String,
    }
    let mut result = input
        .chars()
        .fold(ParserState::default(), |mut state, chr| {
            match state.in_literal {
                true => {
                    if state.last_char != Some('\\') && state.last_quote == Some(chr) {
                        state.in_literal = false;
                        state.last_quote = None;
                        if !state.current_arg.is_empty() {
                            state.parsed_args.push(mem::take(&mut state.current_arg));
                        }
                    } else if state.last_char == Some('\\') {
                        state.current_arg.push(match chr {
                            't' => '\t',
                            'n' => '\n',
                            '"' => '"',
                            '\'' => '\'',
                            '\\' => '\\',
                            _ => panic!("unknown escape character {:?} in input", chr),
                        })
                    } else if chr != '\\' {
                        state.current_arg.push(chr)
                    }
                }
                false => {
                    if chr == ' ' {
                        if !state.current_arg.is_empty() {
                            state.parsed_args.push(mem::take(&mut state.current_arg));
                        }
                    } else if state.last_char == Some('\\') {
                        state.current_arg.push(if chr == 't' {
                            '\t'
                        } else if chr == 'n' {
                            '\n'
                        } else if chr == '"' {
                            '"'
                        } else if chr == '\'' {
                            '\''
                        } else if chr == '\\' {
                            '\\'
                        } else {
                            panic!("unknown escape character {:?} in input", chr);
                        })
                    } else if (chr == '"' || chr == '\'') && state.last_char != Some('\\') {
                        state.in_literal = true;
                        state.last_quote = Some(chr);
                        if !state.current_arg.is_empty() {
                            state.parsed_args.push(mem::take(&mut state.current_arg));
                        }
                    } else if chr != '\\' {
                        state.current_arg.push(chr)
                    } else {
                        state.current_arg.push(chr);
                    }
                }
            }
            state.last_char = Some(chr);
            state
        });
    result.parsed_args.push(mem::take(&mut result.current_arg));
    if result.in_literal {
        panic!("non matching quote characters found!");
    }
    result.parsed_args
}

#[cfg(test)]
mod tests {
    use super::parse;
    use std::panic::catch_unwind;
    #[test]
    fn test_parse() {
        assert_eq!(
            parse(String::from(r#""hello world" world"#)),
            vec!["hello world", "world"]
        );
        assert_eq!(
            parse(String::from(r#"hello world"#)),
            vec!["hello", "world"]
        );
        assert_eq!(
            parse(String::from(r#""hello\"" world"#)),
            vec!["hello\"", "world"]
        );
        assert!(catch_unwind(|| dbg!(parse(String::from(r#"\hello"#)))).is_err());
    }
}