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_backticked_valid() {
    let correct = word_subst(ParameterSubstitution::Command(vec!(cmd("foo"))));
    assert_eq!(correct, make_parser("`foo`").backticked_command_substitution().unwrap());
}

#[test]
fn test_backticked_valid_backslashes_removed_if_before_dollar_backslash_and_backtick() {
    let correct = word_subst(ParameterSubstitution::Command(vec!(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec!(),
        redirects_or_cmd_words: vec!(
            RedirectOrCmdWord::CmdWord(word("foo")),
            RedirectOrCmdWord::CmdWord(TopLevelWord(Concat(vec!(
                Word::Simple(Param(Parameter::Dollar)),
                escaped("`"),
                escaped("o"),
            )))),
        ),
    }))));
    assert_eq!(correct, make_parser("`foo \\$\\$\\\\\\`\\o`").backticked_command_substitution().unwrap());
}

#[test]
fn test_backticked_nested_backticks() {
    let correct = word_subst(ParameterSubstitution::Command(vec!(
        cmd_from_simple(SimpleCommand {
            redirects_or_env_vars: vec!(),
            redirects_or_cmd_words: vec!(
                RedirectOrCmdWord::CmdWord(word("foo")),
                RedirectOrCmdWord::CmdWord(word_subst(
                    ParameterSubstitution::Command(vec!(cmd_from_simple(SimpleCommand {
                        redirects_or_env_vars: vec!(),
                        redirects_or_cmd_words: vec!(
                            RedirectOrCmdWord::CmdWord(word("bar")),
                            RedirectOrCmdWord::CmdWord(TopLevelWord(Concat(vec!(escaped("$"), escaped("$"))))),
                        ),
                    })))
                )),
            ),
        })
    )));
    assert_eq!(correct, make_parser(r#"`foo \`bar \\\\$\\\\$\``"#).backticked_command_substitution().unwrap());
}

#[test]
fn test_backticked_nested_backticks_x2() {
    let correct = word_subst(ParameterSubstitution::Command(vec!(
        cmd_from_simple(SimpleCommand {
            redirects_or_env_vars: vec!(),
            redirects_or_cmd_words: vec!(
                RedirectOrCmdWord::CmdWord(word("foo")),
                RedirectOrCmdWord::CmdWord(word_subst(
                    ParameterSubstitution::Command(vec!(cmd_from_simple(SimpleCommand {
                        redirects_or_env_vars: vec!(),
                        redirects_or_cmd_words: vec!(
                            RedirectOrCmdWord::CmdWord(word("bar")),
                            RedirectOrCmdWord::CmdWord(word_subst(
                                ParameterSubstitution::Command(vec!(cmd_from_simple(SimpleCommand {
                                    redirects_or_env_vars: vec!(),
                                    redirects_or_cmd_words: vec!(
                                        RedirectOrCmdWord::CmdWord(word("baz")),
                                        RedirectOrCmdWord::CmdWord(TopLevelWord(Concat(
                                            vec!(escaped("$"), escaped("$"))
                                        ))),
                                    ),
                                })))
                            )),
                        ),
                    })))
                )),
            ),
        })
    )));
    assert_eq!(correct, make_parser(r#"`foo \`bar \\\`baz \\\\\\\\$\\\\\\\\$ \\\`\``"#).backticked_command_substitution().unwrap());
}

#[test]
fn test_backticked_nested_backticks_x3() {
    let correct = word_subst(ParameterSubstitution::Command(vec!(
        cmd_from_simple(SimpleCommand {
            redirects_or_env_vars: vec!(),
            redirects_or_cmd_words: vec!(
                RedirectOrCmdWord::CmdWord(word("foo")),
                RedirectOrCmdWord::CmdWord(word_subst(
                    ParameterSubstitution::Command(vec!(cmd_from_simple(SimpleCommand {
                        redirects_or_env_vars: vec!(),
                        redirects_or_cmd_words: vec!(
                            RedirectOrCmdWord::CmdWord(word("bar")),
                            RedirectOrCmdWord::CmdWord(word_subst(
                                ParameterSubstitution::Command(vec!(cmd_from_simple(SimpleCommand {
                                    redirects_or_env_vars: vec!(),
                                    redirects_or_cmd_words: vec!(
                                        RedirectOrCmdWord::CmdWord(word("baz")),
                                        RedirectOrCmdWord::CmdWord(word_subst(
                                            ParameterSubstitution::Command(vec!(cmd_from_simple(
                                                SimpleCommand {
                                                    redirects_or_env_vars: vec!(),
                                                    redirects_or_cmd_words: vec!(
                                                        RedirectOrCmdWord::CmdWord(word("qux")),
                                                        RedirectOrCmdWord::CmdWord(TopLevelWord(
                                                            Concat(vec!(
                                                                escaped("$"),
                                                                escaped("$"))
                                                            )
                                                        )),
                                                    ),
                                                }
                                            ))),
                                        )),
                                    ),
                                })))
                            )),
                        ),
                    })))
                )),
            ),
        })
    )));
    assert_eq!(correct, make_parser(
        r#"`foo \`bar \\\`baz \\\\\\\`qux \\\\\\\\\\\\\\\\$\\\\\\\\\\\\\\\\$ \\\\\\\` \\\`\``"#
    ).backticked_command_substitution().unwrap());
}

#[test]
fn test_backticked_invalid_missing_closing_backtick() {
    let src = [
        // Missing outermost backtick
        (r#"`foo"#, src(0,1,1)),
        (r#"`foo \`bar \\\\$\\\\$\`"#, src(0,1,1)),
        (r#"`foo \`bar \\\`baz \\\\\\\\$\\\\\\\\$ \\\`\`"#, src(0,1,1)),
        (r#"`foo \`bar \\\`baz \\\\\\\`qux \\\\\\\\\\\\\\\\$ \\\\\\\\\\\\\\\\$ \\\\\\\` \\\`\`"#, src(0,1,1)),

        // Missing second to last backtick
        (r#"`foo \`bar \\\\$\\\\$`"#, src(6,1,7)),
        (r#"`foo \`bar \\\`baz \\\\\\\\$\\\\\\\\$ \\\``"#, src(6,1,7)),
        (r#"`foo \`bar \\\`baz \\\\\\\`qux \\\\\\\\\\\\\\\\$ \\\\\\\\\\\\\\\\$ \\\\\\\` \\\``"#, src(6,1,7)),

        // Missing third to last backtick
        (r#"`foo \`bar \\\`baz \\\\\\\\$\\\\\\\\$ \``"#, src(14,1,15)),
        (r#"`foo \`bar \\\`baz \\\\\\\`qux \\\\\\\\\\\\\\\\$ \\\\\\\\\\\\\\\\$ \\\\\\\` \``"#, src(14,1,15)),

        // Missing fourth to last backtick
        (r#"`foo \`bar \\\`baz \\\\\\\`qux \\\\\\\\\\\\\\\\$ \\\\\\\\\\\\\\\\$ \\\`\``"#, src(26,1,27)),
    ];

    for &(s, p) in &src {
        let correct = Unmatched(Token::Backtick, p);
        match make_parser(s).backticked_command_substitution() {
            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_backticked_invalid_maintains_accurate_source_positions() {
    let src = [
        (r#"`foo ${invalid param}`"#, src(14,1,15)),
        (r#"`foo \`bar ${invalid param}\``"#, src(20,1,21)),
        (r#"`foo \`bar \\\`baz ${invalid param} \\\`\``"#, src(28,1,29)),
        (r#"`foo \`bar \\\`baz \\\\\\\`qux ${invalid param} \\\\\\\` \\\`\``"#, src(40,1,41)),
    ];

    for &(s, p) in &src {
        let correct = BadSubst(Token::Whitespace(String::from(" ")), p);
        match make_parser(s).backticked_command_substitution() {
            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_backticked_invalid_missing_opening_backtick() {
    let mut p = make_parser("foo`");
    assert_eq!(
        Err(Unexpected(Token::Name(String::from("foo")), src(0,1,1))),
        p.backticked_command_substitution()
    );
}