mod prefix_entries; mod top_entries; use std::{ fmt::Write, fs, path::{Path, PathBuf}, }; use expect_test::expect_file; use crate::{Edition, LexedStr, TopEntryPoint}; #[rustfmt::skip] #[path = "../test_data/generated/runner.rs"] mod runner; #[test] fn lex_ok() { for case in TestCase::list("lexer/ok") { let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let actual = lex(&case.text); expect_file![case.rast].assert_eq(&actual) } } #[test] fn lex_err() { for case in TestCase::list("lexer/err") { let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let actual = lex(&case.text); expect_file![case.rast].assert_eq(&actual) } } fn lex(text: &str) -> String { let lexed = LexedStr::new(Edition::CURRENT, text); let mut res = String::new(); for i in 0..lexed.len() { let kind = lexed.kind(i); let text = lexed.text(i); let error = lexed.error(i); let error = error.map(|err| format!(" error: {err}")).unwrap_or_default(); writeln!(res, "{kind:?} {text:?}{error}").unwrap(); } res } #[test] fn parse_ok() { for case in TestCase::list("parser/ok") { let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text, Edition::CURRENT); assert!(!errors, "errors in an OK file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual); } } #[test] fn parse_err() { for case in TestCase::list("parser/err") { let _guard = stdx::panic_context::enter(format!("{:?}", case.rs)); let (actual, errors) = parse(TopEntryPoint::SourceFile, &case.text, Edition::CURRENT); assert!(errors, "no errors in an ERR file {}:\n{actual}", case.rs.display()); expect_file![case.rast].assert_eq(&actual) } } fn parse(entry: TopEntryPoint, text: &str, edition: Edition) -> (String, bool) { let lexed = LexedStr::new(edition, text); let input = lexed.to_input(edition); let output = entry.parse(&input, edition); let mut buf = String::new(); let mut errors = Vec::new(); let mut indent = String::new(); let mut depth = 0; let mut len = 0; lexed.intersperse_trivia(&output, &mut |step| match step { crate::StrStep::Token { kind, text } => { assert!(depth > 0); len += text.len(); writeln!(buf, "{indent}{kind:?} {text:?}").unwrap(); } crate::StrStep::Enter { kind } => { assert!(depth > 0 || len == 0); depth += 1; writeln!(buf, "{indent}{kind:?}").unwrap(); indent.push_str(" "); } crate::StrStep::Exit => { assert!(depth > 0); depth -= 1; indent.pop(); indent.pop(); } crate::StrStep::Error { msg, pos } => { assert!(depth > 0); errors.push(format!("error {pos}: {msg}\n")) } }); assert_eq!( len, text.len(), "didn't parse all text.\nParsed:\n{}\n\nAll:\n{}\n", &text[..len], text ); for (token, msg) in lexed.errors() { let pos = lexed.text_start(token); errors.push(format!("error {pos}: {msg}\n")); } let has_errors = !errors.is_empty(); for e in errors { buf.push_str(&e); } (buf, has_errors) } #[derive(PartialEq, Eq, PartialOrd, Ord)] struct TestCase { rs: PathBuf, rast: PathBuf, text: String, } impl TestCase { fn list(path: &'static str) -> Vec { let crate_root_dir = Path::new(env!("CARGO_MANIFEST_DIR")); let test_data_dir = crate_root_dir.join("test_data"); let dir = test_data_dir.join(path); let mut res = Vec::new(); let read_dir = fs::read_dir(&dir) .unwrap_or_else(|err| panic!("can't `read_dir` {}: {err}", dir.display())); for file in read_dir { let file = file.unwrap(); let path = file.path(); if path.extension().unwrap_or_default() == "rs" { let rs = path; let rast = rs.with_extension("rast"); let text = fs::read_to_string(&rs).unwrap(); res.push(TestCase { rs, rast, text }); } } res.sort(); res } } #[track_caller] fn run_and_expect_no_errors(path: &str) { run_and_expect_no_errors_with_edition(path, Edition::CURRENT) } #[track_caller] fn run_and_expect_errors(path: &str) { run_and_expect_errors_with_edition(path, Edition::CURRENT) } #[track_caller] fn run_and_expect_no_errors_with_edition(path: &str, edition: Edition) { let path = PathBuf::from(path); let text = std::fs::read_to_string(&path).unwrap(); let (actual, errors) = parse(TopEntryPoint::SourceFile, &text, edition); assert!(!errors, "errors in an OK file {}:\n{actual}", path.display()); let mut p = PathBuf::from(".."); p.push(path); p.set_extension("rast"); expect_file![p].assert_eq(&actual) } #[track_caller] fn run_and_expect_errors_with_edition(path: &str, edition: Edition) { let path = PathBuf::from(path); let text = std::fs::read_to_string(&path).unwrap(); let (actual, errors) = parse(TopEntryPoint::SourceFile, &text, edition); assert!(errors, "no errors in an ERR file {}:\n{actual}", path.display()); let mut p = PathBuf::from(".."); p.push(path); p.set_extension("rast"); expect_file![p].assert_eq(&actual) }