use std::fmt::Write;
use crate::golden_test_template::golden_test_template;
use crate::slice_vec_ext::SliceExt;
use crate::syntax::ast::Stmt;
use crate::syntax::AstModule;
use crate::syntax::Dialect;
use crate::syntax::DialectTypes;
fn parse_fails_with_dialect(name: &str, dialect: &Dialect, programs: &[&str]) {
let mut out = String::new();
for (i, program) in programs.iter().enumerate() {
if i != 0 {
writeln!(out).unwrap();
}
let program = program.trim();
writeln!(out, "Program:").unwrap();
writeln!(out, "{}", program).unwrap();
writeln!(out).unwrap();
let err = AstModule::parse(name, program.to_owned(), dialect).unwrap_err();
writeln!(out, "Error:").unwrap();
writeln!(out, "{}", err).unwrap();
}
golden_test_template(&format!("src/syntax/grammar_tests/{}.golden", name), &out);
}
fn parse_fail_with_dialect(name: &str, dialect: &Dialect, program: &str) {
parse_fails_with_dialect(name, dialect, &[program]);
}
fn parse_fail(name: &str, program: &str) {
parse_fail_with_dialect(name, &Dialect::AllOptionsInternal, program);
}
fn parse_fails(name: &str, programs: &[&str]) {
parse_fails_with_dialect(name, &Dialect::AllOptionsInternal, programs);
}
#[test]
fn test_empty() {
assert_eq!(parse("\n"), "");
assert_eq!(parse(""), "");
assert_eq!(parse(" "), "");
assert_eq!(parse(" \n"), "");
assert_eq!(parse("x = 1\n "), "x = 1\n");
assert_eq!(parse("x = 1\n \n"), "x = 1\n");
}
#[test]
fn test_assign_op() {
assert_eq!(parse("x = 1\n"), "x = 1\n");
assert_eq!(parse("x += 1\n"), "x += 1\n");
assert_eq!(parse("x -= 1\n"), "x -= 1\n");
assert_eq!(parse("x *= 1\n"), "x *= 1\n");
assert_eq!(parse("x /= 1\n"), "x /= 1\n");
assert_eq!(parse("x //= 1\n"), "x //= 1\n");
assert_eq!(parse("x %= 1\n"), "x %= 1\n");
assert_eq!(parse("x &= 1\n"), "x &= 1\n");
assert_eq!(parse("x |= 1\n"), "x |= 1\n");
assert_eq!(parse("x ^= 1\n"), "x ^= 1\n");
assert_eq!(parse("x <<= 1\n"), "x <<= 1\n");
assert_eq!(parse("x >>= 1\n"), "x >>= 1\n");
}
#[test]
fn test_top_level_comment() {
assert_eq!(parse("# Test"), "");
}
#[test]
fn test_top_level_load() {
let want = "load(\"//top/level/load.bzl\", top-level = \"top-level\")\n";
assert_eq!(
parse("\nload(\"//top/level/load.bzl\", \"top-level\")\n"),
want
);
assert_eq!(
parse("\nload(\"//top/level/load.bzl\", \"top-level\")"),
want
);
assert_eq!(
parse("\nload(\n \"//top/level/load.bzl\",\n \"top-level\",\n)\n"),
want
);
}
#[test]
fn test_top_level_assignation() {
assert_eq!(
parse("\n_ASSIGNATION = 'top-level'\n"),
"_ASSIGNATION = \"top-level\"\n"
);
}
#[test]
fn test_top_level_docstring() {
assert_eq!(
parse("\n\"\"\"Top-level docstring\"\"\"\n"),
"\"Top-level docstring\"\n"
);
}
#[test]
fn test_top_level_def() {
assert_eq!(parse("def toto():\n pass\n"), "def toto():\n pass\n");
parse_fail_with_dialect(
"top_level_def",
&Dialect {
enable_def: false,
..Dialect::AllOptionsInternal
},
"def toto():\n pass",
);
assert_eq!(parse("def toto():\n pass"), "def toto():\n pass\n");
assert_eq!(
parse("def toto():\n pass\ndef titi(): return 1"),
"def toto():\n pass\ndef titi():\n return 1\n"
);
assert_eq!(
parse("def toto():\n pass\n\ndef titi(): return 1"),
"def toto():\n pass\ndef titi():\n return 1\n"
);
assert_eq!(parse("def t():\n\n pass"), "def t():\n pass\n");
}
#[test]
fn test_top_level_statements() {
let no_top_leve_stmt = Dialect {
enable_top_level_stmt: false,
..Dialect::AllOptionsInternal
};
parse_fails_with_dialect(
"top_level_statements",
&no_top_leve_stmt,
&[
"x = 1\nif x == 1:\n x = 2\nx = 3",
"x = 1\nfor x in []:\n pass\n",
],
);
let mut dialect = Dialect::Standard;
dialect.enable_top_level_stmt = false;
assert_eq!(parse_with_dialect("pass", &dialect), "pass\n");
assert_eq!(parse("if x == 1:\n x = 2"), "if (x == 1):\n x = 2\n");
assert_eq!(parse("for x in []:\n pass\n"), "for x in []:\n pass\n");
assert_eq!(parse("pass"), "pass\n");
}
#[test]
fn test_top_level_def_with_docstring() {
assert_eq!(
parse(
"\"\"\"Top-level docstring\"\"\"
def toto():
pass
"
),
"\"Top-level docstring\"\ndef toto():\n pass\n"
);
}
#[test]
fn test_ifelse() {
assert_eq!(
parse("def d():\n if True:\n a\n else:\n b"),
"def d():\n if True:\n a\n else:\n b\n"
);
}
#[test]
fn test_kwargs_passing() {
assert_eq!(
parse("f(x, *a, **b); f(x, *a, **{a:b}); f(x, *[a], **b)"),
"f(x, *a, **b)\nf(x, *a, **{a: b})\nf(x, *[a], **b)\n"
);
}
#[test]
fn test_unary_op() {
assert_eq!(parse("a = -1"), "a = -1\n");
assert_eq!(parse("a = +1"), "a = +1\n");
assert_eq!(parse("a = -a"), "a = -a\n");
assert_eq!(parse("a = +a"), "a = +a\n");
}
#[test]
fn test_tuples() {
assert_eq!(parse("a = (-1)"), "a = -1\n"); assert_eq!(parse("a = (+1,)"), "a = (+1,)\n"); assert_eq!(parse("a = ()"), "a = ()\n");
}
#[test]
fn test_return() {
assert_eq!(parse("def fn(): return 1"), "def fn():\n return 1\n");
assert_eq!(parse("def fn(): return a()"), "def fn():\n return a()\n");
assert_eq!(parse("def fn(): return"), "def fn():\n return\n");
}
#[test]
fn test_optional_whitespace() {
assert_eq!(parse("6 or()"), "(6 or ())\n");
assert_eq!(parse("6or()"), "(6 or ())\n");
}
#[test]
fn test_optional_whitespace_after_0() {
assert_eq!(parse("0in[1,2,3]"), "(0 in [1, 2, 3])\n");
}
#[test]
fn test_blank_line() {
assert_eq!(
parse(
r#"
def foo():
bar
bar
"#
),
"def foo():\n bar\n bar\n"
)
}
#[test]
fn test_fncall_span() {
let content = r#"def fn(a):
fail(a)
fn(1)
fail(2)
"#;
let ast = parse_ast(content);
match &ast.statement.node {
Stmt::Statements(xs) => {
let lines = xs.map(|x| ast.codemap.resolve_span(x.span).begin.line);
assert_eq!(lines, vec![0, 3, 5])
}
_ => panic!("Expected to parse as statements"),
}
}
#[test]
fn test_comprehension() {
assert_eq!(
parse("[x for x in range(12) if x % 2 == 0 if x % 3 == 0]"),
"[x for x in range(12) if ((x % 2) == 0) if ((x % 3) == 0)]\n"
);
assert_eq!(
parse("[x + 7 for x in range(12) for y in range(10)]"),
"[(x + 7) for x in range(12) for y in range(10)]\n"
);
}
#[test]
fn test_lambda() {
assert_eq!(parse("x = lambda y: y + 1"), "x = (lambda y: (y + 1))\n");
parse_fail_with_dialect(
"lambda",
&Dialect {
enable_lambda: false,
..Dialect::AllOptionsInternal
},
"x = lambda y: y + 1",
);
assert_eq!(parse("(lambda y: x == 1)(1)"), "(lambda y: (x == 1))(1)\n");
assert_eq!(parse("(lambda x: x or 1)(1)"), "(lambda x: (x or 1))(1)\n");
assert_eq!(
parse("f = lambda x, y: x * y"),
"f = (lambda x, y: (x * y))\n"
);
assert_eq!(parse("lambda x: True"), "(lambda x: True)\n");
assert_eq!(parse("lambda: True"), "(lambda : True)\n");
assert_eq!(
parse("f(lambda x, y=1, *args, **kwargs: x + y + z)"),
"f((lambda x, y = 1, *args, **kwargs: ((x + y) + z)))\n"
);
assert_eq!(
parse("[x for x in [1, 2] if (lambda : 3 if True else 4)]"),
"[x for x in [1, 2] if (lambda : (3 if True else 4))]\n"
);
}
#[test]
fn test_ellipsis() {
parse_fails_with_dialect(
"ellipsis",
&Dialect {
enable_types: DialectTypes::Disable,
..Dialect::AllOptionsInternal
},
&["x = ..."],
);
assert_eq!(parse("x = ..."), "x = ...\n");
}
#[test]
fn test_nested_def() {
assert_eq!(
parse("def foo(x):\n def bar(y): return y\n return bar(x)"),
"def foo(x):\n def bar(y):\n return y\n return bar(x)\n"
);
}
#[test]
fn test_op_associativity() {
assert_eq!(parse("1 or 2 or 3 or 4"), "(((1 or 2) or 3) or 4)\n");
assert_eq!(parse("1 and 2 and 3 and 4"), "(((1 and 2) and 3) and 4)\n");
assert_eq!(parse("1 | 2 | 3"), "((1 | 2) | 3)\n");
assert_eq!(parse("1 + 2 + 3"), "((1 + 2) + 3)\n");
assert_eq!(parse("1 * 2 * 3"), "((1 * 2) * 3)\n");
let err =
AstModule::parse("x", "0 <= 1 < 2".to_owned(), &Dialect::AllOptionsInternal).unwrap_err();
assert!(err.to_string().contains("Parse error"), "{}", err);
}
#[test]
fn test_bad_assignment() {
parse_fails("bad_assignment", &["[x or y] = 1", "[x] += 1"]);
}
#[test]
fn test_assignment_type_annotation() {
parse("x: int = 1");
parse("x.y: int = 1");
parse("x[1]: int = 1");
parse_fail("assignment_type_annotation", "(x, y): int = foo");
}
#[test]
fn test_test_list_in_index_expr() {
assert_eq!(parse("x[1, 2]"), "x[1, 2]\n");
parse_fail("list_in_index_expr", "x[1, 2] = 3");
}
pub fn parse(program: &str) -> String {
parse_ast(program).statement.to_string()
}
pub fn parse_ast(program: &str) -> AstModule {
parse_ast_with_dialect(program, &Dialect::AllOptionsInternal)
}
fn parse_with_dialect(program: &str, dialect: &Dialect) -> String {
parse_ast_with_dialect(program, dialect)
.statement
.to_string()
}
fn parse_ast_with_dialect(program: &str, dialect: &Dialect) -> AstModule {
match AstModule::parse("assert.bzl", program.to_owned(), dialect) {
Ok(x) => x,
Err(e) => {
panic!(
"starlark::assert::parse_ast, expected parse success but failed\nCode: {}\nError: {}",
program, e
);
}
}
}