#![deny(rust_2018_idioms)]
use conch_parser::ast::ComplexWord::*;
use conch_parser::ast::SimpleWord::*;
use conch_parser::ast::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

mod parse_support;
use crate::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()
    );
}


#![deny(rust_2018_idioms)]
use conch_parser::ast::builder::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

mod parse_support;
use crate::parse_support::*;

#[test]
fn test_brace_group_valid() {
    let mut p = make_parser("{ foo\nbar; baz\n#comment1\n#comment2\n }");
    let correct = CommandGroup {
        commands: vec![cmd("foo"), cmd("bar"), cmd("baz")],
        trailing_comments: vec![
            Newline(Some("#comment1".into())),
            Newline(Some("#comment2".into())),
        ],
    };
    assert_eq!(correct, p.brace_group().unwrap());
}

#[test]
fn test_brace_group_invalid_missing_separator() {
    assert_eq!(
        Err(Unmatched(Token::CurlyOpen, src(0, 1, 1))),
        make_parser("{ foo\nbar; baz }").brace_group()
    );
}

#[test]
fn test_brace_group_invalid_start_must_be_whitespace_delimited() {
    let mut p = make_parser("{foo\nbar; baz; }");
    assert_eq!(
        Err(Unexpected(Token::Name(String::from("foo")), src(1, 1, 2))),
        p.brace_group()
    );
}

#[test]
fn test_brace_group_valid_end_must_be_whitespace_and_separator_delimited() {
    let mut p = make_parser("{ foo\nbar}; baz; }");
    p.brace_group().unwrap();
    assert_eq!(p.complete_command().unwrap(), None); // Ensure stream is empty
    let mut p = make_parser("{ foo\nbar; }baz; }");
    p.brace_group().unwrap();
    assert_eq!(p.complete_command().unwrap(), None); // Ensure stream is empty
}

#[test]
fn test_brace_group_valid_keyword_delimited_by_separator() {
    let mut p = make_parser("{ foo }; }");
    let correct = CommandGroup {
        commands: vec![cmd_args("foo", &["}"])],
        trailing_comments: vec![],
    };
    assert_eq!(correct, p.brace_group().unwrap());
}

#[test]
fn test_brace_group_invalid_missing_keyword() {
    let mut p = make_parser("{ foo\nbar; baz");
    assert_eq!(
        Err(Unmatched(Token::CurlyOpen, src(0, 1, 1))),
        p.brace_group()
    );
    let mut p = make_parser("foo\nbar; baz; }");
    assert_eq!(
        Err(Unexpected(Token::Name(String::from("foo")), src(0, 1, 1))),
        p.brace_group()
    );
}

#[test]
fn test_brace_group_invalid_quoted() {
    let cmds = [
        (
            "'{' foo\nbar; baz; }",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        (
            "{ foo\nbar; baz; '}'",
            Unmatched(Token::CurlyOpen, src(0, 1, 1)),
        ),
        (
            "\"{\" foo\nbar; baz; }",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
        (
            "{ foo\nbar; baz; \"}\"",
            Unmatched(Token::CurlyOpen, src(0, 1, 1)),
        ),
    ];

    for (c, e) in &cmds {
        match make_parser(c).brace_group() {
            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_brace_group_invalid_missing_body() {
    assert_eq!(
        Err(Unexpected(Token::CurlyClose, src(2, 2, 1))),
        make_parser("{\n}").brace_group()
    );
}


#![deny(rust_2018_idioms)]
use conch_parser::ast::builder::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

mod parse_support;
use crate::parse_support::*;

#[test]
fn test_case_command_valid() {
    let correct = CaseFragments {
        word: word("foo"),
        post_word_comments: vec![],
        in_comment: None,
        arms: vec![
            CaseArm {
                patterns: CasePatternFragments {
                    pre_pattern_comments: vec![],
                    pattern_alternatives: vec![word("hello"), word("goodbye")],
                    pattern_comment: None,
                },
                body: CommandGroup {
                    commands: vec![cmd_args("echo", &["greeting"])],
                    trailing_comments: vec![],
                },
                arm_comment: None,
            },
            CaseArm {
                patterns: CasePatternFragments {
                    pre_pattern_comments: vec![],
                    pattern_alternatives: vec![word("world")],
                    pattern_comment: None,
                },
                body: CommandGroup {
                    commands: vec![cmd_args("echo", &["noun"])],
                    trailing_comments: vec![],
                },
                arm_comment: None,
            },
        ],
        post_arms_comments: vec![],
    };

    let cases = vec![
        // `(` before pattern is optional
        "case foo in  hello | goodbye) echo greeting;;  world) echo noun;; esac",
        "case foo in (hello | goodbye) echo greeting;;  world) echo noun;; esac",
        "case foo in (hello | goodbye) echo greeting;; (world) echo noun;; esac",
        // Final `;;` is optional as long as last command is complete
        "case foo in hello | goodbye) echo greeting;; world) echo noun\nesac",
        "case foo in hello | goodbye) echo greeting;; world) echo noun; esac",
    ];

    for src in cases {
        assert_eq!(correct, make_parser(src).case_command().unwrap());
    }
}

#[test]
fn test_case_command_valid_with_comments() {
    let correct = CaseFragments {
        word: word("foo"),
        post_word_comments: vec![
            Newline(Some(String::from("#word_comment"))),
            Newline(Some(String::from("#post_word_a"))),
            Newline(None),
            Newline(Some(String::from("#post_word_b"))),
        ],
        in_comment: Some(Newline(Some(String::from("#in_comment")))),
        arms: vec![
            CaseArm {
                patterns: CasePatternFragments {
                    pre_pattern_comments: vec![
                        Newline(None),
                        Newline(Some(String::from("#pre_pat_a"))),
                    ],
                    pattern_alternatives: vec![word("hello"), word("goodbye")],
                    pattern_comment: Some(Newline(Some(String::from("#pat_a")))),
                },
                body: CommandGroup {
                    commands: vec![cmd_args("echo", &["greeting"])],
                    trailing_comments: vec![
                        Newline(None),
                        Newline(Some(String::from("#post_body_a"))),
                    ],
                },
                arm_comment: Some(Newline(Some(String::from("#arm_a")))),
            },
            CaseArm {
                patterns: CasePatternFragments {
                    pre_pattern_comments: vec![
                        Newline(None),
                        Newline(Some(String::from("#pre_pat_b"))),
                    ],
                    pattern_alternatives: vec![word("world")],
                    pattern_comment: Some(Newline(Some(String::from("#pat_b")))),
                },
                body: CommandGroup {
                    commands: vec![cmd_args("echo", &["noun"])],
                    trailing_comments: vec![],
                },
                arm_comment: Some(Newline(Some(String::from("#arm_b")))),
            },
        ],
        post_arms_comments: vec![Newline(None), Newline(Some(String::from("#post_arms")))],
    };

    // Various newlines and comments allowed within the command
    let cmd = "case foo #word_comment
        #post_word_a

        #post_word_b
        in #in_comment

        #pre_pat_a
        (hello | goodbye) #pat_a

        #cmd_leading
        echo greeting #within_body

        #post_body_a
        ;; #arm_a

        #pre_pat_b
        world) #pat_b

        #cmd_leading
        echo noun
        ;; #arm_b

        #post_arms
        esac";

    assert_eq!(Ok(correct), make_parser(cmd).case_command());
}

#[test]
fn test_case_command_valid_with_comments_no_body() {
    let correct = CaseFragments {
        word: word("foo"),
        post_word_comments: vec![
            Newline(Some(String::from("#word_comment"))),
            Newline(Some(String::from("#post_word_a"))),
            Newline(None),
            Newline(Some(String::from("#post_word_b"))),
        ],
        in_comment: Some(Newline(Some(String::from("#in_comment")))),
        arms: vec![],
        post_arms_comments: vec![Newline(None), Newline(Some(String::from("#post_arms")))],
    };

    // Various newlines and comments allowed within the command
    let cmd = "case foo #word_comment
        #post_word_a

        #post_word_b
        in #in_comment

        #post_arms
        esac #case_comment";

    assert_eq!(correct, make_parser(cmd).case_command().unwrap());
}

#[test]
fn test_case_command_word_need_not_be_simple_literal() {
    let mut p = make_parser("case 'foo'bar$$ in foo) echo foo;; esac");
    p.case_command().unwrap();
}

#[test]
fn test_case_command_valid_with_no_arms() {
    let mut p = make_parser("case foo in esac");
    p.case_command().unwrap();
}

#[test]
fn test_case_command_valid_branch_with_no_command() {
    let mut p = make_parser("case foo in pat)\nesac");
    p.case_command().unwrap();
    let mut p = make_parser("case foo in pat);;esac");
    p.case_command().unwrap();
}

#[test]
fn test_case_command_invalid_missing_keyword() {
    let mut p = make_parser("foo in foo) echo foo;; bar) echo bar;; esac");
    assert_eq!(
        Err(Unexpected(Token::Name(String::from("foo")), src(0, 1, 1))),
        p.case_command()
    );
    let mut p = make_parser("case foo foo) echo foo;; bar) echo bar;; esac");
    assert_eq!(
        Err(IncompleteCmd("case", src(0, 1, 1), "in", src(9, 1, 10))),
        p.case_command()
    );
    let mut p = make_parser("case foo in foo) echo foo;; bar) echo bar;;");
    assert_eq!(
        Err(IncompleteCmd("case", src(0, 1, 1), "esac", src(43, 1, 44))),
        p.case_command()
    );
}

#[test]
fn test_case_command_invalid_missing_word() {
    let mut p = make_parser("case in foo) echo foo;; bar) echo bar;; esac");
    assert_eq!(
        Err(IncompleteCmd("case", src(0, 1, 1), "in", src(8, 1, 9))),
        p.case_command()
    );
}

#[test]
fn test_case_command_invalid_quoted() {
    let cmds = [
        (
            "'case' foo in foo) echo foo;; bar) echo bar;; esac",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        (
            "case foo 'in' foo) echo foo;; bar) echo bar;; esac",
            IncompleteCmd("case", src(0, 1, 1), "in", src(9, 1, 10)),
        ),
        (
            "case foo in foo) echo foo;; bar')' echo bar;; esac",
            Unexpected(Token::Name(String::from("echo")), src(35, 1, 36)),
        ),
        (
            "case foo in foo) echo foo;; bar) echo bar;; 'esac'",
            IncompleteCmd("case", src(0, 1, 1), "esac", src(50, 1, 51)),
        ),
        (
            "\"case\" foo in foo) echo foo;; bar) echo bar;; esac",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
        (
            "case foo \"in\" foo) echo foo;; bar) echo bar;; esac",
            IncompleteCmd("case", src(0, 1, 1), "in", src(9, 1, 10)),
        ),
        (
            "case foo in foo) echo foo;; bar\")\" echo bar;; esac",
            Unexpected(Token::Name(String::from("echo")), src(35, 1, 36)),
        ),
        (
            "case foo in foo) echo foo;; bar) echo bar;; \"esac\"",
            IncompleteCmd("case", src(0, 1, 1), "esac", src(50, 1, 51)),
        ),
    ];

    for (c, e) in &cmds {
        match make_parser(c).case_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_case_command_invalid_newline_after_case() {
    let mut p = make_parser("case\nfoo in foo) echo foo;; bar) echo bar;; esac");
    assert_eq!(
        Err(Unexpected(Token::Newline, src(4, 1, 5))),
        p.case_command()
    );
}

#[test]
fn test_case_command_invalid_concat() {
    let mut p = make_parser_from_tokens(vec![
        Token::Literal(String::from("ca")),
        Token::Literal(String::from("se")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("foo")),
        Token::Literal(String::from("bar")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("in")),
        Token::Literal(String::from("foo")),
        Token::ParenClose,
        Token::Newline,
        Token::Literal(String::from("echo")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("foo")),
        Token::Newline,
        Token::Newline,
        Token::DSemi,
        Token::Literal(String::from("esac")),
    ]);
    assert_eq!(
        Err(Unexpected(Token::Literal(String::from("ca")), src(0, 1, 1))),
        p.case_command()
    );

    let mut p = make_parser_from_tokens(vec![
        Token::Literal(String::from("case")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("foo")),
        Token::Literal(String::from("bar")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("i")),
        Token::Literal(String::from("n")),
        Token::Literal(String::from("foo")),
        Token::ParenClose,
        Token::Newline,
        Token::Literal(String::from("echo")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("foo")),
        Token::Newline,
        Token::Newline,
        Token::DSemi,
        Token::Literal(String::from("esac")),
    ]);
    assert_eq!(
        Err(IncompleteCmd("case", src(0, 1, 1), "in", src(12, 1, 13))),
        p.case_command()
    );

    let mut p = make_parser_from_tokens(vec![
        Token::Literal(String::from("case")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("foo")),
        Token::Literal(String::from("bar")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("in")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("foo")),
        Token::ParenClose,
        Token::Newline,
        Token::Literal(String::from("echo")),
        Token::Whitespace(String::from(" ")),
        Token::Literal(String::from("foo")),
        Token::Newline,
        Token::Newline,
        Token::DSemi,
        Token::Literal(String::from("es")),
        Token::Literal(String::from("ac")),
    ]);
    assert_eq!(
        Err(IncompleteCmd("case", src(0, 1, 1), "esac", src(36, 4, 7))),
        p.case_command()
    );
}

#[test]
fn test_case_command_should_recognize_literals_and_names() {
    let case_str = String::from("case");
    let in_str = String::from("in");
    let esac_str = String::from("esac");
    for case_tok in vec![Token::Literal(case_str.clone()), Token::Name(case_str)] {
        for in_tok in vec![Token::Literal(in_str.clone()), Token::Name(in_str.clone())] {
            for esac_tok in vec![
                Token::Literal(esac_str.clone()),
                Token::Name(esac_str.clone()),
            ] {
                let mut p = make_parser_from_tokens(vec![
                    case_tok.clone(),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::Literal(String::from("bar")),
                    Token::Whitespace(String::from(" ")),
                    in_tok.clone(),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::ParenClose,
                    Token::Newline,
                    Token::Literal(String::from("echo")),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::Newline,
                    Token::Newline,
                    Token::DSemi,
                    esac_tok,
                ]);
                p.case_command().unwrap();
            }
        }
    }
}


#![deny(rust_2018_idioms)]
use std::rc::Rc;

use conch_parser::ast::Command::*;
use conch_parser::ast::CompoundCommandKind::*;
use conch_parser::ast::PipeableCommand::*;
use conch_parser::ast::*;
use conch_parser::token::Token;

mod parse_support;
use crate::parse_support::*;

#[test]
fn test_complete_command_job() {
    let mut p = make_parser("foo && bar & baz");
    let cmd1 = p
        .complete_command()
        .unwrap()
        .expect("failed to parse first command");
    let cmd2 = p
        .complete_command()
        .unwrap()
        .expect("failed to parse second command");

    let correct1 = TopLevelCommand(Job(CommandList {
        first: ListableCommand::Single(Simple(cmd_simple("foo"))),
        rest: vec![AndOr::And(ListableCommand::Single(Simple(cmd_simple(
            "bar",
        ))))],
    }));
    let correct2 = cmd("baz");

    assert_eq!(correct1, cmd1);
    assert_eq!(correct2, cmd2);
}

#[test]
fn test_complete_command_non_eager_parse() {
    let mut p = make_parser("foo && bar; baz\n\nqux");
    let cmd1 = p
        .complete_command()
        .unwrap()
        .expect("failed to parse first command");
    let cmd2 = p
        .complete_command()
        .unwrap()
        .expect("failed to parse second command");
    let cmd3 = p
        .complete_command()
        .unwrap()
        .expect("failed to parse third command");

    let correct1 = TopLevelCommand(List(CommandList {
        first: ListableCommand::Single(Simple(cmd_simple("foo"))),
        rest: vec![AndOr::And(ListableCommand::Single(Simple(cmd_simple(
            "bar",
        ))))],
    }));
    let correct2 = cmd("baz");
    let correct3 = cmd("qux");

    assert_eq!(correct1, cmd1);
    assert_eq!(correct2, cmd2);
    assert_eq!(correct3, cmd3);
}

#[test]
fn test_complete_command_valid_no_input() {
    let mut p = make_parser("");
    p.complete_command().expect("no input caused an error");
}

#[test]
fn test_command_delegates_valid_commands_brace() {
    let correct = Compound(Box::new(CompoundCommand {
        kind: Brace(vec![cmd("foo")]),
        io: vec![],
    }));
    assert_eq!(correct, make_parser("{ foo; }").command().unwrap());
}

#[test]
fn test_command_delegates_valid_commands_subshell() {
    let commands = ["(foo)", "( foo)"];

    let correct = Compound(Box::new(CompoundCommand {
        kind: Subshell(vec![cmd("foo")]),
        io: vec![],
    }));

    for cmd in &commands {
        match make_parser(cmd).command() {
            Ok(ref result) if result == &correct => {}
            Ok(result) => panic!(
                "Parsed \"{}\" as an unexpected command type:\n{:#?}",
                cmd, result
            ),
            Err(err) => panic!("Failed to parse \"{}\": {}", cmd, err),
        }
    }
}

#[test]
fn test_command_delegates_valid_commands_while() {
    let correct = Compound(Box::new(CompoundCommand {
        kind: While(GuardBodyPair {
            guard: vec![cmd("guard")],
            body: vec![cmd("foo")],
        }),
        io: vec![],
    }));
    assert_eq!(
        correct,
        make_parser("while guard; do foo; done").command().unwrap()
    );
}

#[test]
fn test_command_delegates_valid_commands_until() {
    let correct = Compound(Box::new(CompoundCommand {
        kind: Until(GuardBodyPair {
            guard: vec![cmd("guard")],
            body: vec![cmd("foo")],
        }),
        io: vec![],
    }));
    assert_eq!(
        correct,
        make_parser("until guard; do foo; done").command().unwrap()
    );
}

#[test]
fn test_command_delegates_valid_commands_for() {
    let correct = Compound(Box::new(CompoundCommand {
        kind: For {
            var: String::from("var"),
            words: Some(vec![]),
            body: vec![cmd("foo")],
        },
        io: vec![],
    }));
    assert_eq!(
        correct,
        make_parser("for var in; do foo; done").command().unwrap()
    );
}

#[test]
fn test_command_delegates_valid_commands_if() {
    let correct = Compound(Box::new(CompoundCommand {
        kind: If {
            conditionals: vec![GuardBodyPair {
                guard: vec![cmd("guard")],
                body: vec![cmd("body")],
            }],
            else_branch: None,
        },
        io: vec![],
    }));
    assert_eq!(
        correct,
        make_parser("if guard; then body; fi").command().unwrap()
    );
}

#[test]
fn test_command_delegates_valid_commands_case() {
    let correct = Compound(Box::new(CompoundCommand {
        kind: Case {
            word: word("foo"),
            arms: vec![],
        },
        io: vec![],
    }));
    assert_eq!(correct, make_parser("case foo in esac").command().unwrap());
}

#[test]
fn test_command_delegates_valid_simple_commands() {
    let correct = Simple(cmd_args_simple("echo", &["foo", "bar"]));
    assert_eq!(correct, make_parser("echo foo bar").command().unwrap());
}

#[test]
fn test_command_delegates_valid_commands_function() {
    let commands = [
        "function foo()      { echo body; }",
        "function foo ()     { echo body; }",
        "function foo (    ) { echo body; }",
        "function foo(    )  { echo body; }",
        "function foo        { echo body; }",
        "foo()               { echo body; }",
        "foo ()              { echo body; }",
        "foo (    )          { echo body; }",
        "foo(    )           { echo body; }",
    ];

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

    for cmd in &commands {
        match make_parser(cmd).command() {
            Ok(ref result) if result == &correct => {}
            Ok(result) => panic!(
                "Parsed \"{}\" as an unexpected command type:\n{:#?}",
                cmd, result
            ),
            Err(err) => panic!("Failed to parse \"{}\": {}", cmd, err),
        }
    }
}

#[test]
fn test_command_parses_quoted_compound_commands_as_simple_commands() {
    let cases = [
        "{foo; }", // { is a reserved word, thus concatenating it essentially "quotes" it
        "'{' foo; }",
        "'(' foo; )",
        "'while' guard do foo; done",
        "'until' guard do foo; done",
        "'if' guard; then body; fi",
        "'for' var in; do echo $var; done",
        "'function' name { echo body; }",
        "name'()' { echo body; }",
        "123fn() { echo body; }",
        "'case' foo in esac",
        "\"{\" foo; }",
        "\"(\" foo; )",
        "\"while\" guard do foo; done",
        "\"until\" guard do foo; done",
        "\"if\" guard; then body; fi",
        "\"for\" var in; do echo $var; done",
        "\"function\" name { echo body; }",
        "name\"()\" { echo body; }",
        "\"case\" foo in esac",
    ];

    for cmd in &cases {
        match make_parser(cmd).command() {
            Ok(Simple(_)) => {}
            Ok(result) => panic!(
                "Parse::command unexpectedly parsed \"{}\" as a non-simple command:\n{:#?}",
                cmd, result
            ),
            Err(err) => panic!("Parse::command failed to parse \"{}\": {}", cmd, err),
        }
    }
}

#[test]
fn test_command_should_delegate_literals_and_names_loop_while() {
    for kw in vec![
        Token::Literal(String::from("while")),
        Token::Name(String::from("while")),
    ] {
        let mut p = make_parser_from_tokens(vec![
            kw,
            Token::Newline,
            Token::Literal(String::from("guard")),
            Token::Newline,
            Token::Literal(String::from("do")),
            Token::Newline,
            Token::Literal(String::from("foo")),
            Token::Newline,
            Token::Literal(String::from("done")),
        ]);

        let cmd = p.command().unwrap();
        if let Compound(ref compound_cmd) = cmd {
            if let While(..) = compound_cmd.kind {
                continue;
            }
        }

        panic!("Parsed an unexpected command:\n{:#?}", cmd)
    }
}

#[test]
fn test_command_should_delegate_literals_and_names_loop_until() {
    for kw in vec![
        Token::Literal(String::from("until")),
        Token::Name(String::from("until")),
    ] {
        let mut p = make_parser_from_tokens(vec![
            kw,
            Token::Newline,
            Token::Literal(String::from("guard")),
            Token::Newline,
            Token::Literal(String::from("do")),
            Token::Newline,
            Token::Literal(String::from("foo")),
            Token::Newline,
            Token::Literal(String::from("done")),
        ]);

        let cmd = p.command().unwrap();
        if let Compound(ref compound_cmd) = cmd {
            if let Until(..) = compound_cmd.kind {
                continue;
            }
        }

        panic!("Parsed an unexpected command:\n{:#?}", cmd)
    }
}

#[test]
fn test_command_should_delegate_literals_and_names_if() {
    for if_tok in vec![
        Token::Literal(String::from("if")),
        Token::Name(String::from("if")),
    ] {
        for then_tok in vec![
            Token::Literal(String::from("then")),
            Token::Name(String::from("then")),
        ] {
            for elif_tok in vec![
                Token::Literal(String::from("elif")),
                Token::Name(String::from("elif")),
            ] {
                for else_tok in vec![
                    Token::Literal(String::from("else")),
                    Token::Name(String::from("else")),
                ] {
                    for fi_tok in vec![
                        Token::Literal(String::from("fi")),
                        Token::Name(String::from("fi")),
                    ] {
                        let mut p = make_parser_from_tokens(vec![
                            if_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("guard1")),
                            Token::Newline,
                            then_tok.clone(),
                            Token::Newline,
                            Token::Literal(String::from("body1")),
                            elif_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("guard2")),
                            Token::Newline,
                            then_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("body2")),
                            else_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("else part")),
                            Token::Newline,
                            fi_tok,
                        ]);

                        let cmd = p.command().unwrap();
                        if let Compound(ref compound_cmd) = cmd {
                            if let If { .. } = compound_cmd.kind {
                                continue;
                            }
                        }

                        panic!("Parsed an unexpected command:\n{:#?}", cmd)
                    }
                }
            }
        }
    }
}

#[test]
fn test_command_should_delegate_literals_and_names_for() {
    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")),
            ]);

            let cmd = p.command().unwrap();
            if let Compound(ref compound_cmd) = cmd {
                if let For { .. } = compound_cmd.kind {
                    continue;
                }
            }

            panic!("Parsed an unexpected command:\n{:#?}", cmd)
        }
    }
}

#[test]
fn test_command_should_delegate_literals_and_names_case() {
    let case_str = String::from("case");
    let in_str = String::from("in");
    let esac_str = String::from("esac");
    for case_tok in vec![Token::Literal(case_str.clone()), Token::Name(case_str)] {
        for in_tok in vec![Token::Literal(in_str.clone()), Token::Name(in_str.clone())] {
            for esac_tok in vec![
                Token::Literal(esac_str.clone()),
                Token::Name(esac_str.clone()),
            ] {
                let mut p = make_parser_from_tokens(vec![
                    case_tok.clone(),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::Literal(String::from("bar")),
                    Token::Whitespace(String::from(" ")),
                    in_tok.clone(),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::ParenClose,
                    Token::Newline,
                    Token::Literal(String::from("echo")),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::Newline,
                    Token::Newline,
                    Token::DSemi,
                    esac_tok,
                ]);

                let cmd = p.command().unwrap();
                if let Compound(ref compound_cmd) = cmd {
                    if let Case { .. } = compound_cmd.kind {
                        continue;
                    }
                }

                panic!("Parsed an unexpected command:\n{:#?}", cmd)
            }
        }
    }
}

#[test]
fn test_command_should_delegate_literals_and_names_for_function_declaration() {
    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,
        ]);
        match p.command() {
            Ok(FunctionDef(..)) => {}
            Ok(result) => panic!("Parsed an unexpected command type:\n{:#?}", result),
            Err(err) => panic!("Failed to parse command: {}", err),
        }
    }
}

#[test]
fn test_command_do_not_delegate_functions_only_if_fn_name_is_a_literal_token() {
    let mut p = make_parser_from_tokens(vec![
        Token::Literal(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,
    ]);
    match p.command() {
        Ok(Simple(..)) => {}
        Ok(result) => panic!("Parsed an unexpected command type:\n{:#?}", result),
        Err(err) => panic!("Failed to parse command: {}", err),
    }
}

#[test]
fn test_command_delegate_functions_only_if_fn_name_is_a_name_token() {
    let mut p = make_parser_from_tokens(vec![
        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,
    ]);
    match p.command() {
        Ok(FunctionDef(..)) => {}
        Ok(result) => panic!("Parsed an unexpected command type:\n{:#?}", result),
        Err(err) => panic!("Failed to parse command: {}", err),
    }
}


#![deny(rust_2018_idioms)]
use conch_parser::ast::builder::*;
use conch_parser::ast::CompoundCommandKind::*;
use conch_parser::ast::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

mod parse_support;
use crate::parse_support::*;

#[test]
fn test_do_group_valid() {
    let mut p = make_parser("do foo\nbar; baz\n#comment\n done");
    let correct = CommandGroup {
        commands: vec![cmd("foo"), cmd("bar"), cmd("baz")],
        trailing_comments: vec![Newline(Some("#comment".into()))],
    };
    assert_eq!(correct, p.do_group().unwrap());
}

#[test]
fn test_do_group_invalid_missing_separator() {
    let mut p = make_parser("do foo\nbar; baz done");
    assert_eq!(
        Err(IncompleteCmd("do", src(0, 1, 1), "done", src(20, 2, 14))),
        p.do_group()
    );
}

#[test]
fn test_do_group_valid_keyword_delimited_by_separator() {
    let mut p = make_parser("do foo done; done");
    let correct = CommandGroup {
        commands: vec![cmd_args("foo", &["done"])],
        trailing_comments: vec![],
    };
    assert_eq!(correct, p.do_group().unwrap());
}

#[test]
fn test_do_group_invalid_missing_keyword() {
    let mut p = make_parser("foo\nbar; baz; done");
    assert_eq!(
        Err(Unexpected(Token::Name(String::from("foo")), src(0, 1, 1))),
        p.do_group()
    );
    let mut p = make_parser("do foo\nbar; baz");
    assert_eq!(
        Err(IncompleteCmd("do", src(0, 1, 1), "done", src(15, 2, 9))),
        p.do_group()
    );
}

#[test]
fn test_do_group_invalid_quoted() {
    let cmds = [
        (
            "'do' foo\nbar; baz; done",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        (
            "do foo\nbar; baz; 'done'",
            IncompleteCmd("do", src(0, 1, 1), "done", src(23, 2, 17)),
        ),
        (
            "\"do\" foo\nbar; baz; done",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
        (
            "do foo\nbar; baz; \"done\"",
            IncompleteCmd("do", src(0, 1, 1), "done", src(23, 2, 17)),
        ),
    ];

    for (c, e) in &cmds {
        match make_parser(c).do_group() {
            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_do_group_invalid_concat() {
    let mut p = make_parser_from_tokens(vec![
        Token::Literal(String::from("d")),
        Token::Literal(String::from("o")),
        Token::Newline,
        Token::Literal(String::from("foo")),
        Token::Newline,
        Token::Literal(String::from("done")),
    ]);
    assert_eq!(
        Err(Unexpected(Token::Literal(String::from("d")), src(0, 1, 1))),
        p.do_group()
    );
    let mut p = make_parser_from_tokens(vec![
        Token::Literal(String::from("do")),
        Token::Newline,
        Token::Literal(String::from("foo")),
        Token::Newline,
        Token::Literal(String::from("do")),
        Token::Literal(String::from("ne")),
    ]);
    assert_eq!(
        Err(IncompleteCmd("do", src(0, 1, 1), "done", src(11, 3, 5))),
        p.do_group()
    );
}

#[test]
fn test_do_group_should_recognize_literals_and_names() {
    for do_tok in vec![
        Token::Literal(String::from("do")),
        Token::Name(String::from("do")),
    ] {
        for done_tok in vec![
            Token::Literal(String::from("done")),
            Token::Name(String::from("done")),
        ] {
            let mut p = make_parser_from_tokens(vec![
                do_tok.clone(),
                Token::Newline,
                Token::Literal(String::from("foo")),
                Token::Newline,
                done_tok,
            ]);
            p.do_group().unwrap();
        }
    }
}

#[test]
fn test_do_group_invalid_missing_body() {
    let mut p = make_parser("do\ndone");
    assert_eq!(
        Err(Unexpected(Token::Name("done".into()), src(3, 2, 1))),
        p.do_group()
    );
}

#[test]
fn test_compound_command_delegates_valid_commands_brace() {
    let correct = CompoundCommand {
        kind: Brace(vec![cmd("foo")]),
        io: vec![],
    };
    assert_eq!(correct, make_parser("{ foo; }").compound_command().unwrap());
}

#[test]
fn test_compound_command_delegates_valid_commands_subshell() {
    let commands = ["(foo)", "( foo)", " (foo)", "\t(foo)", "\\\n(foo)"];

    let correct = CompoundCommand {
        kind: Subshell(vec![cmd("foo")]),
        io: vec![],
    };

    for cmd in &commands {
        match make_parser(cmd).compound_command() {
            Ok(ref result) if result == &correct => {}
            Ok(result) => panic!(
                "Parsed \"{}\" as an unexpected command type:\n{:#?}",
                cmd, result
            ),
            Err(err) => panic!("Failed to parse \"{}\": {}", cmd, err),
        }
    }
}

#[test]
fn test_compound_command_delegates_valid_commands_while() {
    let correct = CompoundCommand {
        kind: While(GuardBodyPair {
            guard: vec![cmd("guard")],
            body: vec![cmd("foo")],
        }),
        io: vec![],
    };
    assert_eq!(
        correct,
        make_parser("while guard; do foo; done")
            .compound_command()
            .unwrap()
    );
}

#[test]
fn test_compound_command_delegates_valid_commands_until() {
    let correct = CompoundCommand {
        kind: Until(GuardBodyPair {
            guard: vec![cmd("guard")],
            body: vec![cmd("foo")],
        }),
        io: vec![],
    };
    assert_eq!(
        correct,
        make_parser("until guard; do foo; done")
            .compound_command()
            .unwrap()
    );
}

#[test]
fn test_compound_command_delegates_valid_commands_for() {
    let correct = CompoundCommand {
        kind: For {
            var: String::from("var"),
            words: Some(vec![]),
            body: vec![cmd("foo")],
        },
        io: vec![],
    };
    assert_eq!(
        correct,
        make_parser("for var in; do foo; done")
            .compound_command()
            .unwrap()
    );
}

#[test]
fn test_compound_command_delegates_valid_commands_if() {
    let correct = CompoundCommand {
        kind: If {
            conditionals: vec![GuardBodyPair {
                guard: vec![cmd("guard")],
                body: vec![cmd("body")],
            }],
            else_branch: None,
        },
        io: vec![],
    };
    assert_eq!(
        correct,
        make_parser("if guard; then body; fi")
            .compound_command()
            .unwrap()
    );
}

#[test]
fn test_compound_command_delegates_valid_commands_case() {
    let correct = CompoundCommand {
        kind: Case {
            word: word("foo"),
            arms: vec![],
        },
        io: vec![],
    };
    assert_eq!(
        correct,
        make_parser("case foo in esac").compound_command().unwrap()
    );
}

#[test]
fn test_compound_command_errors_on_quoted_commands() {
    let cases = [
        // { is a reserved word, thus concatenating it essentially "quotes" it
        // `compound_command` doesn't know or care enough to specify that "foo" is
        // the problematic token instead of {, however, callers who are smart enough
        // to expect a brace command would be aware themselves that no such valid
        // command actually exists. TL;DR: it's okay for `compound_command` to blame {
        ("{foo; }", Unexpected(Token::CurlyOpen, src(0, 1, 1))),
        ("'{' foo; }", Unexpected(Token::SingleQuote, src(0, 1, 1))),
        ("'(' foo; )", Unexpected(Token::SingleQuote, src(0, 1, 1))),
        (
            "'while' guard do foo; done",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        (
            "'until' guard do foo; done",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        (
            "'if' guard; then body; fi",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        (
            "'for' var in; do foo; done",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        (
            "'case' foo in esac",
            Unexpected(Token::SingleQuote, src(0, 1, 1)),
        ),
        ("\"{\" foo; }", Unexpected(Token::DoubleQuote, src(0, 1, 1))),
        ("\"(\" foo; )", Unexpected(Token::DoubleQuote, src(0, 1, 1))),
        (
            "\"while\" guard do foo; done",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
        (
            "\"until\" guard do foo; done",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
        (
            "\"if\" guard; then body; fi",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
        (
            "\"for\" var in; do foo; done",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
        (
            "\"case\" foo in esac",
            Unexpected(Token::DoubleQuote, src(0, 1, 1)),
        ),
    ];

    for &(src, ref e) in &cases {
        match make_parser(src).compound_command() {
            Ok(result) => panic!(
                "Parse::compound_command unexpectedly succeeded parsing \"{}\" with result:\n{:#?}",
                src, result
            ),
            Err(ref err) => {
                if err != e {
                    panic!(
                        "Expected the source \"{}\" to return the error `{:?}`, but got `{:?}`",
                        src, e, err
                    );
                }
            }
        }
    }
}

#[test]
fn test_compound_command_captures_redirections_after_command() {
    let cases = [
        "{ foo; } 1>>out <& 2 2>&-",
        "( foo; ) 1>>out <& 2 2>&-",
        "while guard; do foo; done 1>>out <& 2 2>&-",
        "until guard; do foo; done 1>>out <& 2 2>&-",
        "if guard; then body; fi 1>>out <& 2 2>&-",
        "for var in; do foo; done 1>>out <& 2 2>&-",
        "case foo in esac 1>>out <& 2 2>&-",
    ];

    for cmd in &cases {
        match make_parser(cmd).compound_command() {
            Ok(CompoundCommand { io, .. }) => assert_eq!(
                io,
                vec!(
                    Redirect::Append(Some(1), word("out")),
                    Redirect::DupRead(None, word("2")),
                    Redirect::DupWrite(Some(2), word("-")),
                )
            ),

            Err(err) => panic!("Failed to parse \"{}\": {}", cmd, err),
        }
    }
}

#[test]
fn test_compound_command_should_delegate_literals_and_names_loop() {
    for kw in vec![
        Token::Literal(String::from("while")),
        Token::Name(String::from("while")),
        Token::Literal(String::from("until")),
        Token::Name(String::from("until")),
    ] {
        let mut p = make_parser_from_tokens(vec![
            kw,
            Token::Newline,
            Token::Literal(String::from("guard")),
            Token::Newline,
            Token::Literal(String::from("do")),
            Token::Newline,
            Token::Literal(String::from("foo")),
            Token::Newline,
            Token::Literal(String::from("done")),
        ]);
        p.compound_command().unwrap();
    }
}

#[test]
fn test_compound_command_should_delegate_literals_and_names_if() {
    for if_tok in vec![
        Token::Literal(String::from("if")),
        Token::Name(String::from("if")),
    ] {
        for then_tok in vec![
            Token::Literal(String::from("then")),
            Token::Name(String::from("then")),
        ] {
            for elif_tok in vec![
                Token::Literal(String::from("elif")),
                Token::Name(String::from("elif")),
            ] {
                for else_tok in vec![
                    Token::Literal(String::from("else")),
                    Token::Name(String::from("else")),
                ] {
                    for fi_tok in vec![
                        Token::Literal(String::from("fi")),
                        Token::Name(String::from("fi")),
                    ] {
                        let mut p = make_parser_from_tokens(vec![
                            if_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("guard1")),
                            Token::Newline,
                            then_tok.clone(),
                            Token::Newline,
                            Token::Literal(String::from("body1")),
                            elif_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("guard2")),
                            Token::Newline,
                            then_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("body2")),
                            else_tok.clone(),
                            Token::Whitespace(String::from(" ")),
                            Token::Whitespace(String::from(" ")),
                            Token::Literal(String::from("else part")),
                            Token::Newline,
                            fi_tok,
                        ]);
                        p.compound_command().unwrap();
                    }
                }
            }
        }
    }
}

#[test]
fn test_compound_command_should_delegate_literals_and_names_for() {
    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.compound_command().unwrap();
        }
    }
}

#[test]
fn test_compound_command_should_delegate_literals_and_names_case() {
    let case_str = String::from("case");
    let in_str = String::from("in");
    let esac_str = String::from("esac");
    for case_tok in vec![Token::Literal(case_str.clone()), Token::Name(case_str)] {
        for in_tok in vec![Token::Literal(in_str.clone()), Token::Name(in_str.clone())] {
            for esac_tok in vec![
                Token::Literal(esac_str.clone()),
                Token::Name(esac_str.clone()),
            ] {
                let mut p = make_parser_from_tokens(vec![
                    case_tok.clone(),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::Literal(String::from("bar")),
                    Token::Whitespace(String::from(" ")),
                    in_tok.clone(),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::ParenClose,
                    Token::Newline,
                    Token::Literal(String::from("echo")),
                    Token::Whitespace(String::from(" ")),
                    Token::Literal(String::from("foo")),
                    Token::Newline,
                    Token::Newline,
                    Token::DSemi,
                    esac_tok,
                ]);
                p.compound_command().unwrap();
            }
        }
    }
}


#![deny(rust_2018_idioms)]
use conch_parser::ast::builder::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

mod parse_support;
use crate::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, e) in &cmds {
        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();
        }
    }
}


#![deny(rust_2018_idioms)]
use conch_parser::ast::CompoundCommandKind::*;
use conch_parser::ast::PipeableCommand::*;
use conch_parser::ast::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

use std::rc::Rc;

mod parse_support;
use crate::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, e) in &cmds {
        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();
    }
}


#![deny(rust_2018_idioms)]
use conch_parser::ast::ComplexWord::*;
use conch_parser::ast::Redirect::Heredoc;
use conch_parser::ast::SimpleWord::*;
use conch_parser::ast::*;
use conch_parser::parse::ParseError::*;
use conch_parser::token::Token;

mod parse_support;
use crate::parse_support::*;

fn cat_heredoc(fd: Option<u16>, body: &'static str) -> TopLevelCommand<String> {
    cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(fd, word(body))),
        ],
    })
}

#[test]
fn test_heredoc_valid() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<eof\nhello\neof\n")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_eof_after_delimiter_allowed() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<eof\nhello\neof")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_with_empty_body() {
    let correct = Some(cat_heredoc(None, ""));
    assert_eq!(
        correct,
        make_parser("cat <<eof\neof").complete_command().unwrap()
    );
    assert_eq!(
        correct,
        make_parser("cat <<eof\n").complete_command().unwrap()
    );
    assert_eq!(
        correct,
        make_parser("cat <<eof").complete_command().unwrap()
    );
}

#[test]
fn test_heredoc_valid_eof_acceptable_as_delimeter() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<eof\nhello\neof")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_does_not_lose_tokens_up_to_next_newline() {
    let mut p = make_parser("cat <<eof1; cat 3<<eof2\nhello\neof1\nworld\neof2");
    let first = Some(cat_heredoc(None, "hello\n"));
    let second = Some(cat_heredoc(Some(3), "world\n"));

    assert_eq!(first, p.complete_command().unwrap());
    assert_eq!(second, p.complete_command().unwrap());
}

#[test]
fn test_heredoc_valid_space_before_delimeter_allowed() {
    let mut p = make_parser("cat <<   eof1; cat 3<<- eof2\nhello\neof1\nworld\neof2");
    let first = Some(cat_heredoc(None, "hello\n"));
    let second = Some(cat_heredoc(Some(3), "world\n"));

    assert_eq!(first, p.complete_command().unwrap());
    assert_eq!(second, p.complete_command().unwrap());
}

#[test]
fn test_heredoc_valid_unquoted_delimeter_should_expand_body() {
    let expanded = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(
                None,
                TopLevelWord(Concat(vec![
                    Word::Simple(Param(Parameter::Dollar)),
                    lit(" "),
                    subst(ParameterSubstitution::Len(Parameter::Bang)),
                    lit(" "),
                    subst(ParameterSubstitution::Command(vec![cmd("foo")])),
                    lit("\n"),
                ])),
            )),
        ],
    }));

    let literal = Some(cat_heredoc(None, "$$ ${#!} `foo`\n"));

    assert_eq!(
        expanded,
        make_parser("cat <<eof\n$$ ${#!} `foo`\neof")
            .complete_command()
            .unwrap()
    );
    assert_eq!(
        literal,
        make_parser("cat <<'eof'\n$$ ${#!} `foo`\neof")
            .complete_command()
            .unwrap()
    );
    assert_eq!(
        literal,
        make_parser("cat <<`eof`\n$$ ${#!} `foo`\n`eof`")
            .complete_command()
            .unwrap()
    );
    assert_eq!(
        literal,
        make_parser("cat <<\"eof\"\n$$ ${#!} `foo`\neof")
            .complete_command()
            .unwrap()
    );
    assert_eq!(
        literal,
        make_parser("cat <<e\\of\n$$ ${#!} `foo`\neof")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_leading_tab_removal_works() {
    let mut p =
        make_parser("cat <<-eof1; cat 3<<-eof2\n\t\thello\n\teof1\n\t\t \t\nworld\n\t\teof2");
    let first = Some(cat_heredoc(None, "hello\n"));
    let second = Some(cat_heredoc(Some(3), " \t\nworld\n"));

    assert_eq!(first, p.complete_command().unwrap());
    assert_eq!(second, p.complete_command().unwrap());
}

#[test]
fn test_heredoc_valid_leading_tab_removal_works_if_dash_immediately_after_dless() {
    let mut p = make_parser("cat 3<< -eof\n\t\t \t\nworld\n\t\teof\n\t\t-eof\n-eof");
    let correct = Some(cat_heredoc(Some(3), "\t\t \t\nworld\n\t\teof\n\t\t-eof\n"));
    assert_eq!(correct, p.complete_command().unwrap());
}

#[test]
fn test_heredoc_valid_unquoted_backslashes_in_delimeter_disappear() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<e\\ f\\f\nhello\ne ff")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_balanced_single_quotes_in_delimeter() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<e'o'f\nhello\neof")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_balanced_double_quotes_in_delimeter() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<e\"\\o${foo}\"f\nhello\ne\\o${foo}f")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_balanced_backticks_in_delimeter() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<e`\\o\\$\\`\\\\${f}`\nhello\ne`\\o$`\\${f}`")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_balanced_parens_in_delimeter() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<eof(  )\nhello\neof(  )")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_cmd_subst_in_delimeter() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<eof$(  )\nhello\neof$(  )")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_param_subst_in_delimeter() {
    let correct = Some(cat_heredoc(None, "hello\n"));
    assert_eq!(
        correct,
        make_parser("cat <<eof${  }\nhello\neof${  }")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_skip_past_newlines_in_single_quotes() {
    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\n"))),
            RedirectOrCmdWord::CmdWord(single_quoted("\n")),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF '\n' arg\nhere\nEOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_skip_past_newlines_in_double_quotes() {
    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\n"))),
            RedirectOrCmdWord::CmdWord(double_quoted("\n")),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF \"\n\" arg\nhere\nEOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_skip_past_newlines_in_backticks() {
    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\n"))),
            RedirectOrCmdWord::CmdWord(word_subst(ParameterSubstitution::Command(vec![cmd(
                "echo",
            )]))),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF `echo \n` arg\nhere\nEOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_skip_past_newlines_in_parens() {
    let correct = Some(cat_heredoc(None, "here\n"));
    assert_eq!(
        correct,
        make_parser("cat <<EOF; (foo\n); arg\nhere\nEOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_skip_past_newlines_in_cmd_subst() {
    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\n"))),
            RedirectOrCmdWord::CmdWord(word_subst(ParameterSubstitution::Command(vec![cmd(
                "foo",
            )]))),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF $(foo\n) arg\nhere\nEOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_skip_past_newlines_in_param_subst() {
    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\n"))),
            RedirectOrCmdWord::CmdWord(word_subst(ParameterSubstitution::Assign(
                false,
                Parameter::Var(String::from("foo")),
                Some(word("\n")),
            ))),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF ${foo=\n} arg\nhere\nEOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_skip_past_escaped_newlines() {
    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\n"))),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF \\\n arg\nhere\nEOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_double_quoted_delim_keeps_backslashe_except_after_specials() {
    let correct = Some(cat_heredoc(None, "here\n"));
    assert_eq!(
        correct,
        make_parser("cat <<\"\\EOF\\$\\`\\\"\\\\\"\nhere\n\\EOF$`\"\\\n")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_unquoting_only_removes_outer_quotes_and_backslashes() {
    let correct = Some(cat_heredoc(None, "here\n"));
    assert_eq!(
        correct,
        make_parser("cat <<EOF${ 'asdf'}(\"hello'\"){\\o}\nhere\nEOF${ asdf}(hello'){o}")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_delimeter_can_be_followed_by_carriage_return_newline() {
    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\n"))),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF arg\nhere\nEOF\r\n")
            .complete_command()
            .unwrap()
    );

    let correct = Some(cmd_from_simple(SimpleCommand {
        redirects_or_env_vars: vec![],
        redirects_or_cmd_words: vec![
            RedirectOrCmdWord::CmdWord(word("cat")),
            RedirectOrCmdWord::Redirect(Heredoc(None, word("here\r\n"))),
            RedirectOrCmdWord::CmdWord(word("arg")),
        ],
    }));
    assert_eq!(
        correct,
        make_parser("cat <<EOF arg\nhere\r\nEOF\r\n")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_valid_delimiter_can_start_with() {
    let correct = Some(cat_heredoc(None, "\thello\n\t\tworld\n"));
    assert_eq!(
        correct,
        make_parser("cat << -EOF\n\thello\n\t\tworld\n-EOF")
            .complete_command()
            .unwrap()
    );

    let correct = Some(cat_heredoc(None, "hello\nworld\n"));
    assert_eq!(
        correct,
        make_parser("cat <<--EOF\n\thello\n\t\tworld\n-EOF")
            .complete_command()
            .unwrap()
    );
}

#[test]
fn test_heredoc_invalid_missing_delimeter() {
    assert_eq!(
        Err(Unexpected(Token::Semi, src(7, 1, 8))),
        make_parser("cat << ;").complete_command()
    );
}

#[test]
fn test_heredoc_invalid_unbalanced_quoting() {
    assert_eq!(
        Err(Unmatched(Token::SingleQuote, src(6, 1, 7))),
        make_parser("cat <<'eof").complete_command()
    );
    assert_eq!(
        Err(Unmatched(Token::Backtick, src(6, 1, 7))),
        make_parser("cat <<`eof").complete_command()
    );
    assert_eq!(
        Err(Unmatched(Token::DoubleQuote, src(6, 1, 7))),
        make_parser("cat <<\"eof").complete_command()
    );
    assert_eq!(
        Err(Unmatched(Token::ParenOpen, src(9, 1, 10))),
        make_parser("cat <<eof(").complete_command()
    );
    assert_eq!(
        Err(Unmatched(Token::ParenOpen, src(10, 1, 11))),
        make_parser("cat <<eof$(").complete_command()
    );
    assert_eq!(
        Err(Unmatched(Token::CurlyOpen, src(10, 1, 11))),
        make_parser("cat <<eof${").complete_command()
    );
}

#[test]
fn test_heredoc_invalid_shows_right_position_of_error() {
    let mut p = make_parser("cat <<EOF\nhello\n${invalid subst\nEOF");
    assert_eq!(
        Err(BadSubst(
            Token::Whitespace(String::from(" ")),
            src(25, 3, 10)
        )),
        p.complete_command()
    );
}

#[test]
fn test_heredoc_invalid_shows_right_position_of_error_when_tabs_stripped() {
    let mut p = make_parser("cat <<-EOF\n\t\thello\n\t\t${invalid subst\n\t\t\tEOF");
    assert_eq!(
        Err(BadSubst(
            Token::Whitespace(String::from(" ")),
            src(30, 3, 12)
        )),
        p.complete_command()
    );
}

#[test]
fn test_heredoc_keeps_track_of_correct_position_after_redirect() {
    let mut p = make_parser("cat <<EOF arg ()\nhello\nEOF");
    // Grab the heredoc command
    p.complete_command().unwrap();
    // Fail on the ()
    assert_eq!(
        Err(Unexpected(Token::ParenClose, src(15, 1, 16))),
        p.complete_command()
    );
}