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::*;
use conch_parser::ast::CompoundCommandKind::*;
use conch_parser::ast::PipeableCommand::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

use std::rc::Rc;

mod parse_support;
use parse_support::*;

#[test]
fn test_function_declaration_valid() {
    let correct = FunctionDef(
        String::from("foo"),
        Rc::new(CompoundCommand {
            kind: Brace(vec!(cmd_args("echo", &["body"]))),
            io: vec!(),
        })
    );

    assert_eq!(correct, make_parser("function foo()      { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo ()     { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo (    ) { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo(    )  { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo        { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo()               { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo ()              { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo (    )          { echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo(    )           { echo body; }").function_declaration().unwrap());

    assert_eq!(correct, make_parser("function foo()     \n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo ()    \n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo (    )\n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo(    ) \n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo       \n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo()              \n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo ()             \n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo (    )         \n{ echo body; }").function_declaration().unwrap());
    assert_eq!(correct, make_parser("foo(    )          \n{ echo body; }").function_declaration().unwrap());
}

#[test]
fn test_function_declaration_valid_body_need_not_be_a_compound_command() {
    let src = vec!(
        ("function foo()      echo body;", src(20, 1, 21)),
        ("function foo ()     echo body;", src(20, 1, 21)),
        ("function foo (    ) echo body;", src(20, 1, 21)),
        ("function foo(    )  echo body;", src(20, 1, 21)),
        ("function foo        echo body;", src(20, 1, 21)),
        ("foo()               echo body;", src(20, 1, 21)),
        ("foo ()              echo body;", src(20, 1, 21)),
        ("foo (    )          echo body;", src(20, 1, 21)),
        ("foo(    )           echo body;", src(20, 1, 21)),

        ("function foo()     \necho body;", src(20, 2, 1)),
        ("function foo ()    \necho body;", src(20, 2, 1)),
        ("function foo (    )\necho body;", src(20, 2, 1)),
        ("function foo(    ) \necho body;", src(20, 2, 1)),
        ("function foo       \necho body;", src(20, 2, 1)),
        ("foo()              \necho body;", src(20, 2, 1)),
        ("foo ()             \necho body;", src(20, 2, 1)),
        ("foo (    )         \necho body;", src(20, 2, 1)),
        ("foo(    )          \necho body;", src(20, 2, 1)),
    );

    for (s, p) in src {
        let correct = Unexpected(Token::Name(String::from("echo")), p);
        match make_parser(s).function_declaration() {
            Ok(w) => panic!("Unexpectedly parsed the source \"{}\" as\n{:?}", s, w),
            Err(ref err) => if err != &correct {
                panic!("Expected the source \"{}\" to return the error `{:?}`, but got `{:?}`",
                       s, correct, err);
            },
        }
    }
}

#[test]
fn test_function_declaration_parens_can_be_subshell_if_function_keyword_present() {
    let correct = FunctionDef(
        String::from("foo"),
        Rc::new(CompoundCommand {
            kind: Subshell(vec!(cmd_args("echo", &["subshell"]))),
            io: vec!(),
        })
    );

    assert_eq!(correct, make_parser("function foo (echo subshell)").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo() (echo subshell)").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo () (echo subshell)").function_declaration().unwrap());
    assert_eq!(correct, make_parser("function foo\n(echo subshell)").function_declaration().unwrap());
}

#[test]
fn test_function_declaration_invalid_newline_in_declaration() {
    let mut p = make_parser("function\nname() { echo body; }");
    assert_eq!(Err(Unexpected(Token::Newline, src(8,1,9))), p.function_declaration());
    // If the function keyword is present the () are optional, and at this particular point
    // they become an empty subshell (which is invalid)
    let mut p = make_parser("function name\n() { echo body; }");
    assert_eq!(Err(Unexpected(Token::ParenClose, src(15,2,2))), p.function_declaration());
}

#[test]
fn test_function_declaration_invalid_missing_space_after_fn_keyword_and_no_parens() {
    let mut p = make_parser("functionname { echo body; }");
    assert_eq!(Err(Unexpected(Token::CurlyOpen, src(13,1,14))), p.function_declaration());
}

#[test]
fn test_function_declaration_invalid_missing_fn_keyword_and_parens() {
    let mut p = make_parser("name { echo body; }");
    assert_eq!(Err(Unexpected(Token::CurlyOpen, src(5,1,6))), p.function_declaration());
}

#[test]
fn test_function_declaration_invalid_missing_space_after_name_no_parens() {
    let mut p = make_parser("function name{ echo body; }");
    assert_eq!(Err(Unexpected(Token::CurlyOpen, src(13,1,14))), p.function_declaration());
    let mut p = make_parser("function name( echo body; )");
    assert_eq!(Err(Unexpected(Token::Name(String::from("echo")), src(15,1,16))), p.function_declaration());
}

#[test]
fn test_function_declaration_invalid_missing_name() {
    let mut p = make_parser("function { echo body; }");
    assert_eq!(Err(Unexpected(Token::CurlyOpen, src(9,1,10))), p.function_declaration());
    let mut p = make_parser("function () { echo body; }");
    assert_eq!(Err(Unexpected(Token::ParenOpen, src(9,1,10))), p.function_declaration());
    let mut p = make_parser("() { echo body; }");
    assert_eq!(Err(Unexpected(Token::ParenOpen, src(0,1,1))), p.function_declaration());
}

#[test]
fn test_function_declaration_invalid_missing_body() {
    let mut p = make_parser("function name");
    assert_eq!(Err(UnexpectedEOF), p.function_declaration());
    let mut p = make_parser("function name()");
    assert_eq!(Err(UnexpectedEOF), p.function_declaration());
    let mut p = make_parser("name()");
    assert_eq!(Err(UnexpectedEOF), p.function_declaration());
}

#[test]
fn test_function_declaration_invalid_quoted() {
    let cmds = [
        ("'function' name { echo body; }", Unexpected(Token::SingleQuote, src(0,1,1))),
        ("function 'name'() { echo body; }", Unexpected(Token::SingleQuote, src(9,1,10))),
        ("name'()' { echo body; }", Unexpected(Token::SingleQuote, src(4,1,5))),
        ("\"function\" name { echo body; }", Unexpected(Token::DoubleQuote, src(0,1,1))),
        ("function \"name\"() { echo body; }", Unexpected(Token::DoubleQuote, src(9,1,10))),
        ("name\"()\" { echo body; }", Unexpected(Token::DoubleQuote, src(4,1,5))),
    ];

    for &(c, ref e) in cmds.into_iter() {
        match make_parser(c).function_declaration() {
            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_function_declaration_invalid_fn_must_be_name() {
    let mut p = make_parser("function 123fn { echo body; }");
    assert_eq!(Err(BadIdent(String::from("123fn"), src(9, 1, 10))), p.function_declaration());
    let mut p = make_parser("function 123fn() { echo body; }");
    assert_eq!(Err(BadIdent(String::from("123fn"), src(9, 1, 10))), p.function_declaration());
    let mut p = make_parser("123fn() { echo body; }");
    assert_eq!(Err(BadIdent(String::from("123fn"), src(0, 1, 1))), p.function_declaration());
}

#[test]
fn test_function_declaration_invalid_fn_name_must_be_name_token() {
    let mut p = make_parser_from_tokens(vec!(
        Token::Literal(String::from("function")),
        Token::Whitespace(String::from(" ")),

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

        Token::ParenOpen, Token::ParenClose,
        Token::Whitespace(String::from(" ")),
        Token::CurlyOpen,
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("echo")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("fn body")),
        Token::Semi,
        Token::CurlyClose,
    ));
    assert_eq!(Err(BadIdent(String::from("fn_name"), src(9, 1, 10))), p.function_declaration());

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

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

        Token::ParenOpen, Token::ParenClose,
        Token::Whitespace(String::from(" ")),
        Token::CurlyOpen,
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("echo")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("fn body")),
        Token::Semi,
        Token::CurlyClose,
    ));
    p.function_declaration().unwrap();
}

#[test]
fn test_function_declaration_invalid_concat() {
    let mut p = make_parser_from_tokens(vec!(
        Token::Literal(String::from("func")), Token::Literal(String::from("tion")),
        Token::Whitespace(String::from(" ")),

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

        Token::ParenOpen, Token::ParenClose,
        Token::Whitespace(String::from(" ")),
        Token::CurlyOpen,
        Token::Literal(String::from("echo")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("fn body")),
        Token::Semi,
        Token::CurlyClose,
    ));
    assert_eq!(Err(BadIdent(String::from("func"), src(0, 1, 1))), p.function_declaration());
}

#[test]
fn test_function_declaration_should_recognize_literals_and_names_for_fn_keyword() {
    for fn_tok in vec!(Token::Literal(String::from("function")), Token::Name(String::from("function"))) {
        let mut p = make_parser_from_tokens(vec!(
            fn_tok,
            Token::Whitespace(String::from(" ")),

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

            Token::ParenOpen, Token::ParenClose,
            Token::Whitespace(String::from(" ")),
            Token::CurlyOpen,
            Token::Whitespace(String::from(" ")),
            Token::Literal(String::from("echo")),
            Token::Whitespace(String::from(" ")),
            Token::Literal(String::from("fn body")),
            Token::Semi,
            Token::CurlyClose,
        ));
        p.function_declaration().unwrap();
    }
}