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

mod parse_support;
use parse_support::*;

#[test]
fn test_word_single_quote_valid() {
    let correct = single_quoted("abc&&||\n\n#comment\nabc");
    assert_eq!(Some(correct), make_parser("'abc&&||\n\n#comment\nabc'").word().unwrap());
}

#[test]
fn test_word_single_quote_valid_slash_remains_literal() {
    let correct = single_quoted("\\\n");
    assert_eq!(Some(correct), make_parser("'\\\n'").word().unwrap());
}

#[test]
fn test_word_single_quote_valid_does_not_quote_single_quotes() {
    let correct = single_quoted("hello \\");
    assert_eq!(Some(correct), make_parser("'hello \\'").word().unwrap());
}

#[test]
fn test_word_single_quote_invalid_missing_close_quote() {
    assert_eq!(Err(Unmatched(Token::SingleQuote, src(0, 1, 1))), make_parser("'hello").word());
}

#[test]
fn test_word_double_quote_valid() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(Literal(String::from("abc&&||\n\n#comment\nabc"))))));
    assert_eq!(Some(correct), make_parser("\"abc&&||\n\n#comment\nabc\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_recognizes_parameters() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("test asdf")),
        Param(Parameter::Var(String::from("foo"))),
        Literal(String::from(" $")),
    ))));

    assert_eq!(Some(correct), make_parser("\"test asdf$foo $\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_recognizes_backticks() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("test asdf ")),
        Subst(Box::new(ParameterSubstitution::Command(vec!(cmd("foo"))))),
    ))));

    assert_eq!(Some(correct), make_parser("\"test asdf `foo`\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_slash_escapes_dollar() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("test")),
        Escaped(String::from("$")),
        Literal(String::from("foo ")),
        Param(Parameter::At),
    ))));

    assert_eq!(Some(correct), make_parser("\"test\\$foo $@\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_slash_escapes_backtick() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("test")),
        Escaped(String::from("`")),
        Literal(String::from(" ")),
        Param(Parameter::Star),
    ))));

    assert_eq!(Some(correct), make_parser("\"test\\` $*\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_slash_escapes_double_quote() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("test")),
        Escaped(String::from("\"")),
        Literal(String::from(" ")),
        Param(Parameter::Pound),
    ))));

    assert_eq!(Some(correct), make_parser("\"test\\\" $#\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_slash_escapes_newline() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("test")),
        Escaped(String::from("\n")),
        Literal(String::from(" ")),
        Param(Parameter::Question),
        Literal(String::from("\n")),
    ))));

    assert_eq!(Some(correct), make_parser("\"test\\\n $?\n\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_slash_escapes_slash() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("test")),
        Escaped(String::from("\\")),
        Literal(String::from(" ")),
        Param(Parameter::Positional(0)),
    ))));

    assert_eq!(Some(correct), make_parser("\"test\\\\ $0\"").word().unwrap());
}

#[test]
fn test_word_double_quote_valid_slash_remains_literal_in_general_case() {
    let correct = TopLevelWord(Single(Word::DoubleQuoted(vec!(
        Literal(String::from("t\\est ")),
        Param(Parameter::Dollar),
    ))));

    assert_eq!(Some(correct), make_parser("\"t\\est $$\"").word().unwrap());
}

#[test]
fn test_word_double_quote_slash_invalid_missing_close_quote() {
    assert_eq!(Err(Unmatched(Token::DoubleQuote, src(0, 1, 1))), make_parser("\"hello").word());
    assert_eq!(Err(Unmatched(Token::DoubleQuote, src(0, 1, 1))), make_parser("\"hello\\\"").word());
}

#[test]
fn test_word_delegate_parameters() {
    let params = [
        "$@",
        "$*",
        "$#",
        "$?",
        "$-",
        "$$",
        "$!",
        "$3",
        "${@}",
        "${*}",
        "${#}",
        "${?}",
        "${-}",
        "${$}",
        "${!}",
        "${foo}",
        "${3}",
        "${1000}",
    ];

    for p in params.into_iter() {
        match make_parser(p).word() {
            Ok(Some(TopLevelWord(Single(Word::Simple(w))))) => if let Param(_) = w {} else {
                panic!("Unexpectedly parsed \"{}\" as a non-parameter word:\n{:#?}", p, w);
            },
            Ok(Some(w)) => panic!("Unexpectedly parsed \"{}\" as a non-parameter word:\n{:#?}", p, w),
            Ok(None) => panic!("Did not parse \"{}\" as a parameter", p),
            Err(e) => panic!("Did not parse \"{}\" as a parameter: {}", p, e),
        }
    }
}

#[test]
fn test_word_literal_dollar_if_not_param() {
    let correct = word("$%asdf");
    assert_eq!(correct, make_parser("$%asdf").word().unwrap().unwrap());
}

#[test]
fn test_word_does_not_capture_comments() {
    assert_eq!(Ok(None), make_parser("#comment\n").word());
    assert_eq!(Ok(None), make_parser("  #comment\n").word());
    let mut p = make_parser("word   #comment\n");
    p.word().unwrap().unwrap();
    assert_eq!(Ok(None), p.word());
}

#[test]
fn test_word_pound_in_middle_is_not_comment() {
    let correct = word("abc#def");
    assert_eq!(Ok(Some(correct)), make_parser("abc#def\n").word());
}

#[test]
fn test_word_tokens_which_become_literal_words() {
    let words = [
        "{",
        "}",
        "!",
        "name",
        "1notname",
    ];

    for w in words.into_iter() {
        match make_parser(w).word() {
            Ok(Some(res)) => {
                let correct = word(*w);
                if correct != res {
                    panic!("Unexpectedly parsed \"{}\": expected:\n{:#?}\ngot:\n{:#?}", w, correct, res);
                }
            },
            Ok(None) => panic!("Did not parse \"{}\" as a word", w),
            Err(e) => panic!("Did not parse \"{}\" as a word: {}", w, e),
        }
    }
}

#[test]
fn test_word_concatenation_works() {
    let correct = TopLevelWord(Concat(vec!(
        lit("foo=bar"),
        Word::DoubleQuoted(vec!(Literal(String::from("double")))),
        Word::SingleQuoted(String::from("single")),
    )));

    assert_eq!(Ok(Some(correct)), make_parser("foo=bar\"double\"'single'").word());
}

#[test]
fn test_word_special_words_recognized_as_such() {
    assert_eq!(Ok(Some(TopLevelWord(Single(Word::Simple(Star))))),        make_parser("*").word());
    assert_eq!(Ok(Some(TopLevelWord(Single(Word::Simple(Question))))),    make_parser("?").word());
    assert_eq!(Ok(Some(TopLevelWord(Single(Word::Simple(Tilde))))),       make_parser("~").word());
    assert_eq!(Ok(Some(TopLevelWord(Single(Word::Simple(SquareOpen))))),  make_parser("[").word());
    assert_eq!(Ok(Some(TopLevelWord(Single(Word::Simple(SquareClose))))), make_parser("]").word());
    assert_eq!(Ok(Some(TopLevelWord(Single(Word::Simple(Colon))))),       make_parser(":").word());
}

#[test]
fn test_word_backslash_makes_things_literal() {
    let lit = [
        "a",
        "&",
        ";",
        "(",
        "*",
        "?",
        "$",
    ];

    for l in lit.into_iter() {
        let src = format!("\\{}", l);
        match make_parser(&src).word() {
            Ok(Some(res)) => {
                let correct = word_escaped(l);
                if correct != res {
                    panic!("Unexpectedly parsed \"{}\": expected:\n{:#?}\ngot:\n{:#?}", src, correct, res);
                }
            },
            Ok(None) => panic!("Did not parse \"{}\" as a word", src),
            Err(e) => panic!("Did not parse \"{}\" as a word: {}", src, e),
        }
    }
}

#[test]
fn test_word_escaped_newline_becomes_whitespace() {
    let mut p = make_parser("foo\\\nbar");
    assert_eq!(Ok(Some(word("foo"))), p.word());
    assert_eq!(Ok(Some(word("bar"))), p.word());
}