1105 lines
40 KiB
Rust
1105 lines
40 KiB
Rust
use expect_test::{expect, Expect};
|
|
use oyster_parser::ParseTree;
|
|
|
|
fn check(s: &str, expect: Expect) {
|
|
let actual = ParseTree::from(s);
|
|
expect.assert_debug_eq(&actual);
|
|
}
|
|
|
|
#[test]
|
|
fn empty() {
|
|
check(
|
|
"",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn word() {
|
|
check(
|
|
"word",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 4,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn word_with_escape() {
|
|
check(
|
|
r"hello\#world",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 5,
|
|
},
|
|
Leaf {
|
|
kind: EscapedChar,
|
|
len: 2,
|
|
},
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 5,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unicode() {
|
|
check(
|
|
"谚语",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn simple_command() {
|
|
check(
|
|
"echo hi",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 4,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 2,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn semicolon() {
|
|
check(
|
|
"hello; hi",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 5,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Semicolon,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 2,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn comment() {
|
|
let source = r"# a
|
|
# b";
|
|
|
|
check(
|
|
source,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Leaf {
|
|
kind: Comment,
|
|
len: 3,
|
|
},
|
|
Leaf {
|
|
kind: Newlines,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Comment,
|
|
len: 3,
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn inline_comment() {
|
|
check(
|
|
"whoami # hello",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Comment,
|
|
len: 7,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pipeline() {
|
|
check(
|
|
"whoami | cat",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Pipe,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 3,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn multiline_pipeline() {
|
|
let source = r"whoami |
|
|
wc -l";
|
|
|
|
check(
|
|
source,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Pipe,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Newlines,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 2,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 2,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn comment_in_pipeline() {
|
|
let source = r"whoami | # comment
|
|
wc -l";
|
|
|
|
check(
|
|
source,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Pipe,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Comment,
|
|
len: 9,
|
|
},
|
|
Leaf {
|
|
kind: Newlines,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 2,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 2,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reject_leading_pipe() {
|
|
let source = r"whoami
|
|
| cat";
|
|
|
|
check(
|
|
source,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Newlines,
|
|
len: 1,
|
|
},
|
|
Error {
|
|
kind: UnexpectedPipe,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 3,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reject_trailing_pipe() {
|
|
check(
|
|
"whoami |",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Pipe,
|
|
len: 1,
|
|
},
|
|
Error {
|
|
kind: UnexpectedEof,
|
|
len: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reject_pipe_semicolon() {
|
|
let source = "whoami | ; cat";
|
|
|
|
check(
|
|
source,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Pipe,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Error {
|
|
kind: UnexpectedSemicolon,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 3,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reject_double_pipe() {
|
|
check(
|
|
"whoami | | cat",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Pipe,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Error {
|
|
kind: UnexpectedPipe,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 3,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn double_quote_string() {
|
|
check(
|
|
r#""hello world""#,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Tree {
|
|
kind: DQuotedString,
|
|
children: [
|
|
Leaf {
|
|
kind: DoubleQuote,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 11,
|
|
},
|
|
Leaf {
|
|
kind: DoubleQuote,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn escaped_char_in_double_quotes() {
|
|
check(
|
|
r#""hello \" world""#,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Tree {
|
|
kind: DQuotedString,
|
|
children: [
|
|
Leaf {
|
|
kind: DoubleQuote,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
Leaf {
|
|
kind: EscapedChar,
|
|
len: 2,
|
|
},
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
Leaf {
|
|
kind: DoubleQuote,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unterminated_double_quotes() {
|
|
check(
|
|
r#""hello world"#,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Tree {
|
|
kind: DQuotedString,
|
|
children: [
|
|
Leaf {
|
|
kind: DoubleQuote,
|
|
len: 1,
|
|
},
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 11,
|
|
},
|
|
Error {
|
|
kind: UnexpectedEof,
|
|
len: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn command_substitution() {
|
|
check(
|
|
r#"echo (whoami)"#,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 4,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Tree {
|
|
kind: CommandSubstitution,
|
|
children: [
|
|
Leaf {
|
|
kind: OpeningParenthesis,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: ClosingParenthesis,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn quoted_command_substitution() {
|
|
let source = r#"echo "(whoami)
|
|
""#;
|
|
|
|
check(
|
|
source,
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 4,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: Whitespace,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Tree {
|
|
kind: DQuotedString,
|
|
children: [
|
|
Leaf {
|
|
kind: DoubleQuote,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: CommandSubstitution,
|
|
children: [
|
|
Leaf {
|
|
kind: OpeningParenthesis,
|
|
len: 1,
|
|
},
|
|
Tree {
|
|
kind: Pipeline,
|
|
children: [
|
|
Tree {
|
|
kind: Command,
|
|
children: [
|
|
Tree {
|
|
kind: Word,
|
|
children: [
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 6,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: ClosingParenthesis,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
Leaf {
|
|
kind: PlainText,
|
|
len: 9,
|
|
},
|
|
Leaf {
|
|
kind: DoubleQuote,
|
|
len: 1,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_pipeline() {
|
|
check(
|
|
";",
|
|
expect![[r#"
|
|
Tree {
|
|
kind: Program,
|
|
children: [
|
|
Leaf {
|
|
kind: Semicolon,
|
|
len: 1,
|
|
},
|
|
],
|
|
}
|
|
"#]],
|
|
);
|
|
}
|