conch-parser 0.1.1

A library for parsing programs written in the shell programming language.
Documentation
extern crate conch_parser;

use conch_parser::ast::builder::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

mod parse_support;
use parse_support::*;

#[test]
fn test_for_command_valid_with_words() {
    let mut p = make_parser("\
    for var #var comment
    #prew1
    #prew2
    in one two three #word comment
    #precmd1
    #precmd2
    do echo;
    #body_comment
    done
    ");
    assert_eq!(p.for_command(), Ok(ForFragments {
        var: "var".into(),
        var_comment: Some(Newline(Some("#var comment".into()))),
        words: Some((
            vec!(
                Newline(Some("#prew1".into())),
                Newline(Some("#prew2".into())),
            ),
            vec!(
                word("one"),
                word("two"),
                word("three"),
            ),
            Some(Newline(Some("#word comment".into())))
        )),
        pre_body_comments: vec!(
            Newline(Some("#precmd1".into())),
            Newline(Some("#precmd2".into())),
        ),
        body: CommandGroup {
            commands: vec!(cmd("echo")),
            trailing_comments: vec!(Newline(Some("#body_comment".into()))),
        },
    }));
}

#[test]
fn test_for_command_valid_without_words() {
    let mut p = make_parser("\
    for var #var comment
    #w1
    #w2
    do echo;
    #body_comment
    done
    ");
    assert_eq!(p.for_command(), Ok(ForFragments {
        var: "var".into(),
        var_comment: Some(Newline(Some("#var comment".into()))),
        words: None,
        pre_body_comments: vec!(
            Newline(Some("#w1".into())),
            Newline(Some("#w2".into())),
        ),
        body: CommandGroup {
            commands: vec!(cmd("echo")),
            trailing_comments: vec!(Newline(Some("#body_comment".into()))),
        },
    }));
}

#[test]
fn test_for_command_valid_separators() {
    let cases = vec!(
        "for var                 do body; done",
        "for var             ;   do body; done",
        "for var             ;\n do body; done",
        "for var\n               do body; done",
        "for var\n in        ;   do body; done",
        "for var\n in        ;\n do body; done",
        "for var\n in         \n do body; done",
        "for var   in        ;   do body; done",
        "for var   in        ;\n do body; done",
        "for var   in         \n do body; done",
        "for var\n in one two;   do body; done",
        "for var\n in one two;\n do body; done",
        "for var\n in one two \n do body; done",
        "for var   in one two;   do body; done",
        "for var   in one two;\n do body; done",
        "for var   in one two \n do body; done",
    );

    for src in cases {
        match make_parser(src).for_command() {
            Ok(_) => {},
            e@Err(_) => panic!("expected `{}` to parse successfully, but got: {:?}", src, e),
        }
    }
}

#[test]
fn test_for_command_valid_with_separator() {
    let mut p = make_parser("for var in one two three\ndo echo $var; done");
    p.for_command().unwrap();
    let mut p = make_parser("for var in one two three;do echo $var; done");
    p.for_command().unwrap();
}

#[test]
fn test_for_command_invalid_with_in_no_words_no_with_separator() {
    let mut p = make_parser("for var in do echo $var; done");
    assert_eq!(Err(IncompleteCmd("for", src(0,1,1), "do", src(25,1,26))), p.for_command());
}

#[test]
fn test_for_command_invalid_missing_separator() {
    let mut p = make_parser("for var in one two three do echo $var; done");
    assert_eq!(Err(IncompleteCmd("for", src(0,1,1), "do", src(39,1,40))), p.for_command());
}

#[test]
fn test_for_command_invalid_amp_not_valid_separator() {
    let mut p = make_parser("for var in one two three& do echo $var; done");
    assert_eq!(Err(Unexpected(Token::Amp, src(24, 1, 25))), p.for_command());
}

#[test]
fn test_for_command_invalid_missing_keyword() {
    let mut p = make_parser("var in one two three\ndo echo $var; done");
    assert_eq!(Err(Unexpected(Token::Name(String::from("var")), src(0,1,1))), p.for_command());
}

#[test]
fn test_for_command_invalid_missing_var() {
    let mut p = make_parser("for in one two three\ndo echo $var; done");
    assert_eq!(Err(IncompleteCmd("for", src(0,1,1), "in", src(7,1,8))), p.for_command());
}

#[test]
fn test_for_command_invalid_missing_body() {
    let mut p = make_parser("for var in one two three\n");
    assert_eq!(Err(IncompleteCmd("for", src(0,1,1), "do", src(25,2,1))), p.for_command());
}

#[test]
fn test_for_command_invalid_quoted() {
    let cmds = [
        ("'for' var in one two three\ndo echo $var; done", Unexpected(Token::SingleQuote, src(0,1,1))),
        ("for var 'in' one two three\ndo echo $var; done", IncompleteCmd("for", src(0,1,1), "in", src(8,1,9))),
        ("\"for\" var in one two three\ndo echo $var; done", Unexpected(Token::DoubleQuote, src(0,1,1))),
        ("for var \"in\" one two three\ndo echo $var; done", IncompleteCmd("for", src(0,1,1), "in", src(8,1,9))),
    ];

    for &(c, ref e) in cmds.into_iter() {
        match make_parser(c).for_command() {
            Ok(result) => panic!("Unexpectedly parsed \"{}\" as\n{:#?}", c, result),
            Err(ref err) => if err != e {
                panic!("Expected the source \"{}\" to return the error `{:?}`, but got `{:?}`",
                       c, e, err);
            },
        }
    }
}

#[test]
fn test_for_command_invalid_var_must_be_name() {
    let mut p = make_parser("for 123var in one two three\ndo echo $var; done");
    assert_eq!(Err(BadIdent(String::from("123var"), src(4, 1, 5))), p.for_command());
    let mut p = make_parser("for 'var' in one two three\ndo echo $var; done");
    assert_eq!(Err(Unexpected(Token::SingleQuote, src(4, 1, 5))), p.for_command());
    let mut p = make_parser("for \"var\" in one two three\ndo echo $var; done");
    assert_eq!(Err(Unexpected(Token::DoubleQuote, src(4, 1, 5))), p.for_command());
    let mut p = make_parser("for var*% in one two three\ndo echo $var; done");
    assert_eq!(Err(IncompleteCmd("for", src(0, 1, 1), "in", src(7, 1, 8))), p.for_command());
}

#[test]
fn test_for_command_invalid_concat() {
    let mut p = make_parser_from_tokens(vec!(
        Token::Literal(String::from("fo")), Token::Literal(String::from("r")),
        Token::Whitespace(String::from(" ")), Token::Name(String::from("var")),
        Token::Whitespace(String::from(" ")),

        Token::Literal(String::from("in")),
        Token::Literal(String::from("one")), Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("two")), Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("three")), Token::Whitespace(String::from(" ")),
        Token::Newline,

        Token::Literal(String::from("do")), Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("echo")), Token::Whitespace(String::from(" ")),
        Token::Dollar, Token::Literal(String::from("var")),
        Token::Newline,
        Token::Literal(String::from("done")),
    ));
    assert_eq!(Err(Unexpected(Token::Literal(String::from("fo")), src(0, 1, 1))), p.for_command());

    let mut p = make_parser_from_tokens(vec!(
        Token::Literal(String::from("for")),
        Token::Whitespace(String::from(" ")), Token::Name(String::from("var")),
        Token::Whitespace(String::from(" ")),

        Token::Literal(String::from("i")), Token::Literal(String::from("n")),
        Token::Literal(String::from("one")), Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("two")), Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("three")), Token::Whitespace(String::from(" ")),
        Token::Newline,

        Token::Literal(String::from("do")), Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("echo")), Token::Whitespace(String::from(" ")),
        Token::Dollar, Token::Literal(String::from("var")),
        Token::Newline,
        Token::Literal(String::from("done")),
    ));
    assert_eq!(Err(IncompleteCmd("for", src(0,1,1), "in", src(8,1,9))), p.for_command());
}

#[test]
fn test_for_command_should_recognize_literals_and_names() {
    for for_tok in vec!(Token::Literal(String::from("for")), Token::Name(String::from("for"))) {
        for in_tok in vec!(Token::Literal(String::from("in")), Token::Name(String::from("in"))) {
            let mut p = make_parser_from_tokens(vec!(
                for_tok.clone(),
                Token::Whitespace(String::from(" ")),

                Token::Name(String::from("var")),
                Token::Whitespace(String::from(" ")),

                in_tok.clone(),
                Token::Whitespace(String::from(" ")),
                Token::Literal(String::from("one")),
                Token::Whitespace(String::from(" ")),
                Token::Literal(String::from("two")),
                Token::Whitespace(String::from(" ")),
                Token::Literal(String::from("three")),
                Token::Whitespace(String::from(" ")),
                Token::Newline,

                Token::Literal(String::from("do")),
                Token::Whitespace(String::from(" ")),

                Token::Literal(String::from("echo")),
                Token::Whitespace(String::from(" ")),
                Token::Dollar,
                Token::Name(String::from("var")),
                Token::Newline,

                Token::Literal(String::from("done")),
            ));
            p.for_command().unwrap();
        }
    }
}