Files
awk/parser/src/tests.rs

978 lines
29 KiB
Rust

// This file is part of the uutils awk package.
//
// For the full copyright and license information, please view the LICENSE
// files that was distributed with this source code.
mod ast_gen;
mod prop_tests;
mod utils;
use bumpalo::Bump;
use crate::{
FileCache, Parser,
diagnostics::ParsingError,
test_parser,
tests::utils::{parse, parse_error_span, spanned_snippet},
};
#[test]
fn test_parser_meta_holy_macro() {
let source = "
@load \"lib_foo.1\";
@load \"lib_bar.so\";
BEGIN { print 1 + 1 }
BEGIN { 2 + 2 == 4\nprint \"foo\" }
{ if (a) print 2; }
$0 == \"lisp would be proud\";
function foo(a, b) { print a ? b : c }
";
test_parser!(source => {
loads: ["lib_foo.1", "lib_bar.so"],
begin: [
"(body (Print (Add 1 1)))",
"(body (Eq (Add 2 2) 4) (Print \"foo\"))"
],
rules: [
(None, Some("(body (if awk::a (body (Print 2))))")),
(Some("(Eq (Record 0) \"lisp would be proud\")"), None),
],
functions: [
(
"awk::foo",
&["awk::a", "awk::b"],
"(body (Print (?: awk::a awk::b awk::c)))"
)
],
});
}
#[test]
fn test_parser_valid_patterns() {
let source = "
BEGIN { print }
END { print }
BEGINFILE { print }
ENDFILE { print }
$0 == 1 && /x/ { print }
/abc/ { print }
!$0, x::a ? b : c { print }
awk;
1 + 1 \n { print }
{ print }
a in arr { print }
";
const BODY: &str = "(body (Print))";
test_parser!(source => {
begin: [BODY],
end: [BODY],
begin_file: [BODY],
end_file: [BODY],
rules: [
(Some("(And (Eq (Record 0) 1) /x/)"), Some(BODY)),
(Some("/abc/"), Some(BODY)),
(
Some("(Range (Negation (Record 0)) (?: x::a awk::b awk::c))"),
Some(BODY)
),
(Some("awk::awk"), None),
(Some("(Add 1 1)"), None),
(None, Some(BODY)),
(None, Some(BODY)),
(Some("(In awk::arr awk::a)"), Some("(body (Print))")),
],
});
}
#[test]
fn test_parser_invalid_patterns() {
test_parser!(is_err!("BEGIN", "END", "BEGINFILE", "ENDFILE", "print 1;"));
}
#[test]
fn test_parser_statement_end_after_command() {
test_parser!("{ print 1; { print 2 } } {;} { print; } { { print }; }" => {
rules: [
(None, Some("(body (Print 1) (Print 2))")),
(None, Some("(body)")),
(None, Some("(body (Print))")),
(None, Some("(body (Print))"))
],
});
test_parser!(is_err!(
"{ print 1 { 1 + 1 } }",
"{ print 1 { } }",
"{ printf \"%d\" 1 { } }",
"{ print 1 print 2 }",
"{ 1 + 1 print 1 }",
"{ delete x print 1 }",
"{ 2 + 1 delete x }"
));
}
#[test]
fn test_parser_statement_bodies() {
let source = "
{ if (1); }
{ if (1); else; }
{ if (1) print; }
{ if (1) print; else; }
{ if (1); else print; }
{ for (;;); }
{ while (1); }
";
test_parser!(source => {
rules: [
(None, Some("(body (if 1 (body)))")),
(None, Some("(body (if 1 (body) (else (body))))")),
(None, Some("(body (if 1 (body (Print))))")),
(None, Some("(body (if 1 (body (Print)) (else (body))))")),
(None, Some("(body (if 1 (body) (else (body (Print)))))")),
(None, Some("(body (for (pass) (pass) (pass) (body)))")),
(None, Some("(body (while 1 (body)))")),
],
});
test_parser!(is_err!(
"{ if (1) }",
"{ if (1); else }",
"{ if (1) }",
"{ if (1) 1 + 1 else }",
"{ if (1) print 1 else }",
"{ if (1) 1 + 1; else }",
"{ if (1) print 1; else }",
"{ while (1) }",
"{ for (;;) }"
));
}
#[test]
fn test_parser_reserved_qualified_identifiers() {
test_parser!(is_err!(
"{ if::while }",
"{ foo::while }",
"{ while::foo }",
"@namespace \"if\"; BEGIN {}",
"function foo::while() {}",
"function while::foo() {}"
));
}
#[test]
fn test_parser_non_assoc() {
test_parser!(is_err!(
"a == b == c",
"a != b != c",
"a > b > c",
"a < b < c",
"a >= b >= c",
"a <= b <= c"
));
}
#[test]
fn test_parser_exponentiation() {
let source = "
{ 2 ^ 1 }
{ 2 ** 1 }
{ 2 ^ 3 ^ 4 }
{ 2 * 3 ^ 4 }
{ 2 ^ 3 * 4 }
";
test_parser!(source => {
rules: [
(None, Some("(body (Raise 2 1))")),
(None, Some("(body (Raise 2 1))")),
(None, Some("(body (Raise 2 (Raise 3 4)))")),
(None, Some("(body (Multiply 2 (Raise 3 4)))")),
(None, Some("(body (Multiply (Raise 2 3) 4))")),
],
});
}
#[test]
fn test_parser_multiplicative_precedence() {
let source = "
{ 2 * 3 % 4 }
{ 2 % 3 * 4 }
";
test_parser!(source => {
rules: [
(None, Some("(body (Modulo (Multiply 2 3) 4))")),
(None, Some("(body (Multiply (Modulo 2 3) 4))")),
],
});
}
#[test]
fn test_parser_relaxed_assignments() {
let source = "
{ 1 + 0 && x = 1 }
{ 1 + 0 || x = 1 }
{ 1 + 0 >= x = 1 }
{ 1 + 0 < x = 1 }
{ 1 + 0 ~ x = 1 }
{ 1 + 0 !~ x = 1 }
{ y = @/a/ ? b : c }
{ 1 + 0 || z = @/a/ ? b : c }
{ 1 + 0 || z = /a/ && b || c }
";
test_parser!(
source => {
rules: [
(None, Some("(body (And (Add 1 0) (Assignment awk::x 1)))")),
(None, Some("(body (Or (Add 1 0) (Assignment awk::x 1)))")),
(None, Some("(body (GtE (Add 1 0) (Assignment awk::x 1)))")),
(None, Some("(body (Lt (Add 1 0) (Assignment awk::x 1)))")),
(None, Some("(body (Matches (Add 1 0) (Assignment awk::x 1)))")),
(None, Some("(body (MatchesNot (Add 1 0) (Assignment awk::x 1)))")),
(None, Some("(body (?: (Assignment awk::y @/a/) awk::b awk::c))")),
(None, Some("(body (?: (Or (Add 1 0) (Assignment awk::z @/a/)) awk::b awk::c))")),
(
None,
Some("(body (Or (Add 1 0) (Assignment awk::z (Or (And /a/ awk::b) awk::c))))"),
),
],
}
);
test_parser!(is_err!("{ 1 + x = 1 }", "{ x y = 1 }", "{ 1 + 2 * x = 1 }"));
}
#[test]
fn test_parser_inc_dec() {
let source = r#"
{ ++a $0-- }
{ --a[2] ++$(1 + 1) }
{ a++ a["x"]-- }
{ --a $"a"++ }
{ --a/a++ a++/--a }
{ $(a--) $a++ }
{ $--a $(++a) }
"#;
test_parser!(source => {
rules: [
(None, Some("(body (Concat (IncrementL awk::a) (DecrementR (Record 0))))")),
(
None,
Some(
"(body (Concat (DecrementL (Index awk::a 2)) (IncrementL (Record (Add 1 1)))))"
)
),
(None, Some("(body (Concat (IncrementR awk::a) (DecrementR (Index awk::a \"x\"))))")),
(None, Some("(body (Concat (DecrementL awk::a) (IncrementR (Record \"a\"))))")),
(None, Some("(body (Concat (Divide (DecrementL awk::a) (IncrementR awk::a)) \
(Divide (IncrementR awk::a) (DecrementL awk::a))))")),
(None, Some("(body (Concat (Record (DecrementR awk::a)) \
(IncrementR (Record awk::a))))")),
(None, Some("(body (Concat (Record (DecrementL awk::a)) \
(Record (IncrementL awk::a))))"))
],
});
// these should parse as (Cat (--L (++R $0)) a), or otherwise error out.
// FIXME: not treated as errors yet.
// test_parser!(is_err!("{ $0++ --a }", "{ ++$0 ++a }"));
}
#[test]
fn test_parser_arrays() {
let source = "
{ a[1]; a[1] = x = b[2] = 2 + 2 }
{ ++a[1]; print b[a]-- }
{ a[1, 2, 3, \"a\"] += 1 }
{ print a in arr, (1, 2, \"a\") in arr }
{ print $((1, 2) in a) }
";
test_parser!(source => {
rules: [
(
None,
Some("(body (Index awk::a 1) \
(Assignment (Index awk::a 1) \
(Assignment awk::x (Assignment (Index awk::b 2) (Add 2 2)))))"
)
),
(
None,
Some("(body (IncrementL (Index awk::a 1)) \
(Print (DecrementR (Index awk::b awk::a))))"
)
),
(None, Some("(body (AddAssign (Index awk::a 1 2 3 \"a\") 1))")),
(None, Some("(body (Print (In awk::arr awk::a) (In awk::arr 1 2 \"a\")))")),
(None, Some("(body (Print (Record (In awk::a 1 2))))")),
],
});
test_parser!(is_err!(
"$(1, 2) in arr",
"x in 2",
"2[1]",
"\"a\"[2]",
"2 in a = 1"
));
}
#[test]
fn test_parser_nested_arrays() {
let source = "
{ a[1][2] }
{ a[1][2][3] }
{ a[1][2] = 2 }
{ a[1][2][3] = 2 }
{ a[1][2][3][4][5] = 2 }
{ ++a[1][2] }
{ a[1][2]-- }
{ a[1][2] += 1 }
{ b = a[1][2] }
{ b = a[1][2][3] }
{ a[1, 2][3] }
{ a[1][2, 3] }
";
test_parser!(source => {
rules: [
(None, Some("(body (Index (Index awk::a 1) 2))")),
(None, Some("(body (Index (Index (Index awk::a 1) 2) 3))")),
(None, Some("(body (Assignment (Index (Index awk::a 1) 2) 2))")),
(None, Some("(body (Assignment (Index (Index (Index awk::a 1) 2) 3) 2))")),
(None, Some("(body (Assignment (Index (Index (Index (Index (Index awk::a 1) 2) 3) 4) 5) 2))")),
(None, Some("(body (IncrementL (Index (Index awk::a 1) 2)))")),
(None, Some("(body (DecrementR (Index (Index awk::a 1) 2)))")),
(None, Some("(body (AddAssign (Index (Index awk::a 1) 2) 1))")),
(None, Some("(body (Assignment awk::b (Index (Index awk::a 1) 2)))")),
(None, Some("(body (Assignment awk::b (Index (Index (Index awk::a 1) 2) 3)))")),
(None, Some("(body (Index (Index awk::a 1 2) 3))")),
(None, Some("(body (Index (Index awk::a 1) 2 3))")),
],
});
test_parser!(is_err!(
"{ 2[1][2] }",
"{ \"a\"[1][2] }",
"{ (a + b)[1][2] }"
));
}
#[test]
fn test_parser_for_loop() {
let source = "
{ for (i = 0; i < n; i++) print }
{ for (; i < n; i++) print }
{ for (i = 0; ; i++) print }
{ for (i = 0; i < n;) print }
{ for (;; i++) print }
{ for (; i < n;) print }
{ for (i = 0; ;) print }
{ for (;;) print }
{ for ((i in arr); a; b) print }
{ for (((i, 2) in arr); ;) print }
{ for (k in array) print }
";
test_parser!(
source => {
rules: [
(
None,
Some("(body (for (Assignment awk::i 0) (Lt awk::i awk::n) (IncrementR awk::i) \
(body (Print))))")
),
(
None,
Some("(body (for (pass) (Lt awk::i awk::n) (IncrementR awk::i) \
(body (Print))))")
),
(
None,
Some("(body (for (Assignment awk::i 0) (pass) (IncrementR awk::i) \
(body (Print))))")
),
(
None,
Some("(body (for (Assignment awk::i 0) (Lt awk::i awk::n) (pass) \
(body (Print))))")
),
(None, Some("(body (for (pass) (pass) (IncrementR awk::i) (body (Print))))")),
(None, Some("(body (for (pass) (Lt awk::i awk::n) (pass) (body (Print))))")),
(None, Some("(body (for (Assignment awk::i 0) (pass) (pass) (body (Print))))")),
(None, Some("(body (for (pass) (pass) (pass) (body (Print))))")),
(None, Some("(body (for (In awk::arr awk::i) awk::a awk::b (body (Print))))")),
(None, Some("(body (for (In awk::arr awk::i 2) (pass) (pass) (body (Print))))")),
(None, Some("(body (for-each awk::k awk::array (body (Print))))")),
],
}
);
test_parser!(is_err!("{ for(x in array; a; b) {} }"));
}
#[test]
fn test_parser_logical_operators() {
let source = r"
{ a && b && c == 3 }
{ a || b > 2 || c }
{ 1 ~ /a/ || b && c }
{ !a }
{ !(a && b) }
";
test_parser!(source => {
rules: [
(None, Some("(body (And (And awk::a awk::b) (Eq awk::c 3)))")),
(None, Some("(body (Or (Or awk::a (Gt awk::b 2)) awk::c))")),
(None, Some("(body (Or (Matches 1 @/a/) (And awk::b awk::c)))")),
(None, Some("(body (Negation awk::a))")),
(None, Some("(body (Negation (And awk::a awk::b)))")),
],
});
}
#[test]
fn test_parser_delete() {
let source = r"
{ delete arr[k] }
{ delete arr[i, j] }
{ delete arr }
";
test_parser!(source => {
rules: [
(None, Some("(body (delete (Index awk::arr awk::k)))")),
(None, Some("(body (delete (Index awk::arr awk::i awk::j)))")),
(None, Some("(body (delete awk::arr))")),
],
});
}
#[test]
fn test_parser_if() {
let source = r"
{ if (a) print }
{ if (k in arr) print; else if (x) print; else print; }
{ if (x == 1 && 2) print 1; else print 0 }
";
test_parser!(source => {
rules: [
(None, Some("(body (if awk::a (body (Print))))")),
(
None,
Some("(body (if (In awk::arr awk::k) (body (Print)) (else (body (if awk::x \
(body (Print)) (else (body (Print))))))))")
),
(
None,
Some("(body (if (And (Eq awk::x 1) 2) (body (Print 1)) (else (body (Print 0)))))")
),
],
});
}
#[test]
fn test_parser_dangling_else() {
let source = "{ if (a) if (b) print 1; else print 2 }";
test_parser!(source => {
rules: [
(None, Some(
"(body (if awk::a (body (if awk::b (body (Print 1)) (else (body (Print 2)))))))"
)),
],
});
}
#[test]
fn test_parser_while() {
let source = r"
{ while (a < 10) a++ }
{ while (1) { print a; if (a > 5) break } }
";
test_parser!(source => {
rules: [
(None, Some("(body (while (Lt awk::a 10) (body (IncrementR awk::a))))")),
(None, Some(concat!(
"(body (while 1 ",
"(body (Print awk::a) (if (Gt awk::a 5) (body (break))))))"
))),
],
});
}
#[test]
fn test_parser_do_while() {
let source = r"
{ do { a++ } while (a < 10) }
{ do { print; break } while (a--) }
";
test_parser!(source => {
rules: [
(None, Some("(body (do-while (body (IncrementR awk::a)) (Lt awk::a 10)))")),
(None, Some("(body (do-while (body (Print) (break)) (DecrementR awk::a)))")),
],
});
}
#[test]
fn test_parser_pratt_error_spans() {
let source = "{ 1 = x }";
let span = parse_error_span(source);
assert!(matches!(
parse(source, &Bump::new()),
Err(ParsingError::OperatorExpectsVariable(_))
));
assert_eq!(spanned_snippet(source, span), "1 =");
let source = "{ ++1 }";
let span = parse_error_span(source);
assert_eq!(spanned_snippet(source, span), "++1");
let source = "{ 1[2] }";
let span = parse_error_span(source);
assert_eq!(spanned_snippet(source, span), "1[");
let source = "{ 1 + 2 = x }";
let span = parse_error_span(source);
assert_eq!(spanned_snippet(source, span), "1 + 2 =");
let source = "{ x in 2 }";
let span = parse_error_span(source);
assert_eq!(spanned_snippet(source, span), "2");
// Non-associative operators keep the offending token's span.
let source = "{ a == b == c }";
let span = parse_error_span(source);
assert!(matches!(
parse(source, &Bump::new()),
Err(ParsingError::NonAssociativeOperator(_))
));
assert_eq!(spanned_snippet(source, span), "==");
let source = "{ (1 + 2 }";
let span = parse_error_span(source);
assert!(matches!(
parse(source, &Bump::new()),
Err(ParsingError::UnclosedParenthesisExpression(_))
));
assert_eq!(spanned_snippet(source, span), "(1 + 2 }");
let source = "{ a[1 }";
let span = parse_error_span(source);
assert!(matches!(
parse(source, &Bump::new()),
Err(ParsingError::UnclosedArrayAccess(_))
));
assert_eq!(spanned_snippet(source, span), "a[1 }");
}
#[test]
fn test_parser_switch() {
let source = r#"
{ switch (x) { case 1: print; case "a": print 2; default: print 3 } }
{ switch (1 + 1) { case /pat/: print } }
"#;
test_parser!(source => {
rules: [
(
None,
Some(concat!(
"(body (switch awk::x",
" (case 1 (body (Print)))",
" (case \"a\" (body (Print 2)))",
" (default 2 (body (Print 3)))))"
))
),
(
None,
Some("(body (switch (Add 1 1) (case /pat/ (body (Print)))))")
),
],
});
test_parser!(is_err!(
"{ switch (x) { default: print; default: print } }",
"{ switch (x) { case a: print } }"
));
}
#[test]
fn test_parser_getline() {
let source = r#"
{ getline }
{ getline x }
{ getline < "f" }
{ getline x < "f" }
{ "cmd" | getline }
{ "cmd" | getline x }
{ "cmd" |& getline }
{ "cmd" |& getline x }
"#;
test_parser!(source => {
rules: [
(None, Some("(body (getline))")),
(None, Some("(body (getline awk::x))")),
(None, Some("(body (getline< \"f\"))")),
(None, Some("(body (getline< \"f\" awk::x))")),
(None, Some("(body (getline| \"cmd\"))")),
(None, Some("(body (getline| \"cmd\" awk::x))")),
(None, Some("(body (getline|& \"cmd\"))")),
(None, Some("(body (getline|& \"cmd\" awk::x))")),
],
});
}
#[test]
fn test_parser_printf() {
let source = r#"
{ printf "%d", 1, 2 }
{ printf("%d", 1) }
"#;
test_parser!(source => {
rules: [
(None, Some("(body (Printf \"%d\" 1 2))")),
(None, Some("(body (Printf \"%d\" 1))")),
],
});
}
#[test]
fn test_parser_control_flow() {
let source = r"
{ next }
{ nextfile }
{ exit }
{ exit 1 }
{ while (0) { break; continue } }
{ for (;;) { break; continue } }
{ do { break; continue } while (0) }
{ for (k in a) { break; continue } }
{ switch (x) { case 1: break } }
function f() { return 1 }
";
test_parser!(source => {
rules: [
(None, Some("(body (next))")),
(None, Some("(body (nextfile))")),
(None, Some("(body (exit))")),
(None, Some("(body (exit 1))")),
(None, Some("(body (while 0 (body (break) (continue))))")),
(None, Some("(body (for (pass) (pass) (pass) (body (break) (continue))))")),
(None, Some("(body (do-while (body (break) (continue)) 0))")),
(None, Some("(body (for-each awk::k awk::a (body (break) (continue))))")),
(None, Some("(body (switch awk::x (case 1 (body (break)))))")),
],
functions: [
("awk::f", &[], "(body (return 1))"),
],
});
test_parser!(is_err!(
"{ break }",
"{ continue }",
"{ return }",
"{ return 1 }",
"{ if (1) break }",
"{ if (1) continue }",
"{ if (1) return }",
"{ switch (x) { case 1: continue } }",
"{ switch (x) { case 1: return } }"
));
let arena = Bump::new();
assert!(matches!(
parse("{ break }", &arena),
Err(ParsingError::BreakOutsideLoopOrSwitch(_))
));
assert!(matches!(
parse("{ continue }", &arena),
Err(ParsingError::ContinueOutsideLoop(_))
));
assert!(matches!(
parse("{ return }", &arena),
Err(ParsingError::ReturnOutsideFunction(_))
));
assert!(matches!(
parse("{ switch (x) { case 1: continue } }", &arena),
Err(ParsingError::ContinueOutsideLoop(_))
));
assert!(parse("function f() { return }", &arena).is_ok());
}
#[test]
fn test_parser_redirection() {
let source = r#"
{ print > "out" }
{ print >> "out" }
{ print | "cmd" }
{ print |& "cmd" }
{ print (1 + 1)/2 > "cmd" }
{ print (1, 2 > 3) }
{ print (1 > 3, 2) > "cmd" }
{ print 1, 2 | "cmd" }
{ print 1 |& "cmd" }
{ print 1 >> ("cmd" > "foo") }
"#;
test_parser!(source => {
rules: [
(None, Some("(body (Print (> \"out\")))")),
(None, Some("(body (Print (>> \"out\")))")),
(None, Some("(body (Print (| \"cmd\")))")),
(None, Some("(body (Print (|& \"cmd\")))")),
(None, Some("(body (Print (Divide (Add 1 1) 2) (> \"cmd\")))")),
(None, Some("(body (Print 1 (Gt 2 3)))")),
(None, Some("(body (Print (Gt 1 3) 2 (> \"cmd\")))")),
(None, Some("(body (Print 1 2 (| \"cmd\")))")),
(None, Some("(body (Print 1 (|& \"cmd\")))")),
(None, Some("(body (Print 1 (>> (Gt \"cmd\" \"foo\"))))")),
],
});
}
#[test]
fn test_parser_regex_matching() {
let source = r"
{ a ~ /b/ }
{ a !~ /b/ }
{ /x/ ~ /y/ }
";
test_parser!(source => {
rules: [
(None, Some("(body (Matches awk::a @/b/))")),
(None, Some("(body (MatchesNot awk::a @/b/))")),
(None, Some("(body (Matches /x/ @/y/))")),
],
});
}
#[test]
fn test_parser_function_calls() {
let source = r#"
{ foo(1, 2) }
{ @bar(3) }
{ foo::baz(a, b + 1) }
{ length("foo") }
{ length ("foo") }
{ foo (1) }
"#;
test_parser!(source => {
rules: [
(None, Some("(body (awk::foo 1 2))")),
(None, Some("(body (@awk::bar 3))")),
(None, Some("(body (foo::baz awk::a (Add awk::b 1)))")),
(None, Some("(body (Length \"foo\"))")),
(None, Some("(body (Length \"foo\"))")),
(None, Some("(body (Concat awk::foo 1))"))
],
});
test_parser!(is_err!("foo ()", "foo (1, 2)"));
}
#[test]
fn test_parser_compound_assignments() {
let source = r"
{ a -= 1 }
{ a *= 2 }
{ a /= 3 }
{ a ^= 4 }
{ a %= 5 }
{ a /= /pat/ }
";
test_parser!(source => {
rules: [
(None, Some("(body (SubAssign awk::a 1))")),
(None, Some("(body (MulAssign awk::a 2))")),
(None, Some("(body (DivAssign awk::a 3))")),
(None, Some("(body (PowAssign awk::a 4))")),
(None, Some("(body (ModAssign awk::a 5))")),
(None, Some("(body (DivAssign awk::a /pat/))")),
],
});
}
#[test]
fn test_parser_builtin_variables() {
let source = r"
{ NR; NF; FS; RS; OFS; ORS; FILENAME; ARGC; ARGV; SUBSEP; FNR; OFMT; RSTART; RLENGTH; ENVIRON }
";
test_parser!(source => {
rules: [(
None,
Some(concat!(
"(body NR NF FS RS OFS ORS FILENAME ARGC ARGV SUBSEP FNR OFMT ",
"RSTART RLENGTH ENVIRON)"
))
)],
});
}
#[test]
fn test_parser_concurrent() {
let source = r"
@concurrent { print 1 }
@concurrent $1 { print 2 }
";
test_parser!(source => {
concurrent: [
(None, Some("(body (Print 1))")),
(Some("(Record 1)"), Some("(body (Print 2))")),
],
});
}
#[test]
fn test_parser_namespace_directive() {
let source = r#"
@namespace "my_ns";
function bar() { print }
"#;
test_parser!(source => {
functions: [("my_ns::bar", &[], "(body (Print))")],
});
}
#[test]
fn test_parser_unary_and_divide() {
let source = r"
{ -a; +a; !a; a / b; a + b - c }
{ int(a) }
";
test_parser!(source => {
rules: [
(
None,
Some(concat!(
"(body (Negative awk::a) (ToInt awk::a) (Negation awk::a) ",
"(Divide awk::a awk::b) (Subtract (Add awk::a awk::b) awk::c))"
))
),
(None, Some("(body (Int awk::a))")),
],
});
}
#[test]
fn test_parser_builtin_in_expression_context() {
let source = r#"
BEGIN { print int(3.7); x = length("ab") }
"#;
test_parser!(source => {
begin: [
r#"(body (Print (Int 3.7)) (Assignment awk::x (Length "ab")))"#
],
});
}
#[test]
fn test_parser_proper_assignments() {
let source = r"
{ a = 1 }
{ $a = 1 }
{ a[2] = 1 }
{ a[2, 1] = 1 }
{ a[2][1] = 1 }
";
test_parser!(source => {
rules: [
(None, Some("(body (Assignment awk::a 1))")),
(None, Some("(body (Assignment (Record awk::a) 1))")),
(None, Some("(body (Assignment (Index awk::a 2) 1))")),
(None, Some("(body (Assignment (Index awk::a 2 1) 1))")),
(None, Some("(body (Assignment (Index (Index awk::a 2) 1) 1))"))
],
});
test_parser!(is_err!(
"{ (a) = 1 }",
"{ ($a) = 1 }",
"{ (a[2]) = 1 }",
"{ (a)[2] = 1 }",
"{ (a[2, 1]) = 1 }",
"{ (a[2][1]) = 1 }"
));
}
#[test]
fn test_rejections_next() {
test_parser!("{ next }" => {
rules: [(None, Some("(body (next))"))],
});
test_parser!("BEGIN {} END {} { next }" => {
begin: ["(body)"],
end: ["(body)"],
rules: [(None, Some("(body (next))"))],
});
for e in ["BEGIN", "END", "BEGINFILE", "ENDFILE"] {
test_parser!(is_err!(&format!("{{}} {e} {{ next }}")));
}
}
#[test]
fn test_rejections_nextfile() {
test_parser!("{ nextfile }" => {
rules: [(None, Some("(body (nextfile))"))],
});
test_parser!("BEGIN {} END {} { nextfile } BEGINFILE { nextfile }" => {
begin: ["(body)"],
end: ["(body)"],
begin_file: ["(body (nextfile))"],
rules: [(None, Some("(body (nextfile))"))],
});
for e in ["BEGIN", "END", "ENDFILE"] {
test_parser!(is_err!(&format!("{{}} {e} {{ nextfile }}")));
}
}
#[test]
fn test_pretty_print_omits_default_namespace() {
use std::fmt::Write;
let arena = Bump::new();
let parser = arena.alloc(Parser::new(&arena, true));
let source = "{ print a + b }";
let ast = parser.parse(FileCache(None), source.as_bytes()).unwrap();
let mut printed = String::new();
write!(printed, "{ast}").unwrap();
assert!(
!printed.contains("awk::"),
"pretty-print should omit the default awk namespace: {printed}"
);
assert!(printed.contains("print a"));
}
#[test]
fn test_pretty_print_namespace_directive() {
use std::fmt::Write;
let arena = Bump::new();
let parser = arena.alloc(Parser::new(&arena, true));
let source = r#"
@namespace "my_ns";
function bar(x) { print x }
"#;
let ast = parser.parse(FileCache(None), source.as_bytes()).unwrap();
let mut printed = String::new();
write!(printed, "{ast}").unwrap();
assert!(
printed.contains("@namespace \"my_ns\""),
"pretty-print should restore @namespace directive: {printed}"
);
assert!(
!printed.contains("my_ns::"),
"pretty-print should use unqualified names under @namespace: {printed}"
);
}
#[test]
fn test_pretty_print_namespace_roundtrip() {
use std::fmt::Write;
let arena = Bump::new();
let parser = arena.alloc(Parser::new(&arena, true));
let source = r#"
@namespace "my_ns";
function bar() { x = 1 }
{ print y }
"#;
let ast = parser.parse(FileCache(None), source.as_bytes()).unwrap();
let mut printed = String::new();
write!(printed, "{ast}").unwrap();
let arena2 = Bump::new();
let parser2 = arena2.alloc(Parser::new(&arena2, true));
let reparsed = parser2.parse(FileCache(None), printed.as_bytes()).unwrap();
assert_eq!(ast.functions.len(), reparsed.functions.len());
assert_eq!(ast.rules.len(), reparsed.rules.len());
}