192 lines
5.5 KiB
Rust
192 lines
5.5 KiB
Rust
use fine::parser::SyntaxTree;
|
|
use fine::semantics::{Semantics, Type};
|
|
use fine::tokens::Lines;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
fn rebase_concrete(source_path: &str, dump: &str) {
|
|
let contents = std::fs::read_to_string(source_path)
|
|
.expect(&format!("unable to read input file {}", source_path));
|
|
|
|
let mut result = String::new();
|
|
let mut lines = contents.lines();
|
|
|
|
// Search for the "concrete:" section.
|
|
let mut found_concrete_section = false;
|
|
while let Some(line) = lines.next() {
|
|
result.push_str(line);
|
|
result.push_str("\n");
|
|
|
|
if line == "// concrete:" {
|
|
found_concrete_section = true;
|
|
break;
|
|
}
|
|
}
|
|
if !found_concrete_section {
|
|
panic!(
|
|
"unable to locate the concrete section in {}. Is there a line that starts with '// concrete:'?",
|
|
source_path
|
|
);
|
|
}
|
|
|
|
// We've found the section we care about, replace all the lines we care
|
|
// about with the actual lines.
|
|
let mut replaced_output = false;
|
|
while let Some(line) = lines.next() {
|
|
if line.starts_with("// | ") {
|
|
// Skip copying lines here, because we're skipping
|
|
// the existing concrete syntax tree.
|
|
} else {
|
|
// OK we're out of concrete syntax tree; copy in the
|
|
// new CST. (We do this inline so we don't lose
|
|
// `line`.)
|
|
for expected_line in dump.lines() {
|
|
result.push_str("// | ");
|
|
result.push_str(expected_line);
|
|
result.push_str("\n");
|
|
}
|
|
|
|
// (Make sure not to drop this line.)
|
|
result.push_str(line);
|
|
result.push_str("\n");
|
|
|
|
replaced_output = true;
|
|
break;
|
|
}
|
|
}
|
|
if !replaced_output {
|
|
panic!(
|
|
"didn't actually replace the output section in {}",
|
|
source_path
|
|
);
|
|
}
|
|
|
|
// Now just copy the rest of the lines.
|
|
while let Some(line) = lines.next() {
|
|
result.push_str(line);
|
|
result.push_str("\n");
|
|
}
|
|
|
|
// ... and re-write the file.
|
|
std::fs::write(source_path, result).expect("unable to write the new file!");
|
|
}
|
|
|
|
fn assert_concrete(tree: &SyntaxTree, expected: &str, source_path: &str) {
|
|
let dump = tree.dump(false);
|
|
let rebase = std::env::var("FINE_TEST_REBASE")
|
|
.unwrap_or(String::new())
|
|
.to_lowercase();
|
|
match rebase.as_str() {
|
|
"1" | "true" | "yes" | "y" => {
|
|
if dump != expected {
|
|
rebase_concrete(source_path, &dump)
|
|
}
|
|
}
|
|
_ => assert_eq!(expected, dump, "concrete syntax trees did not match (set FINE_TEST_REBASE=1 to auto-rebase if the diff is expected)"),
|
|
}
|
|
}
|
|
|
|
fn report_semantic_error(semantics: &Semantics, message: &str) {
|
|
let tree = semantics.tree();
|
|
|
|
println!("{message}! Parsed the tree as:");
|
|
println!("\n{}", tree.dump(true));
|
|
|
|
let errors = semantics.snapshot_errors();
|
|
if errors.len() == 0 {
|
|
println!("There were no errors reported during checking.\n");
|
|
} else {
|
|
println!(
|
|
"{} error{} reported during checking:",
|
|
errors.len(),
|
|
if errors.len() == 1 { "" } else { "s" }
|
|
);
|
|
for error in &errors {
|
|
println!(" Error: {error}");
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
|
|
macro_rules! semantic_panic {
|
|
($semantics:expr, $($t:tt)*) => {{
|
|
let message = format!($($t)*);
|
|
report_semantic_error($semantics, &message);
|
|
panic!("{message}");
|
|
}};
|
|
}
|
|
|
|
macro_rules! semantic_assert {
|
|
($semantics:expr, $pred:expr, $($t:tt)*) => {{
|
|
if !$pred {
|
|
let message = format!($($t)*);
|
|
report_semantic_error($semantics, &message);
|
|
panic!("{message}");
|
|
}
|
|
}};
|
|
}
|
|
|
|
macro_rules! semantic_assert_eq {
|
|
($semantics:expr, $left:expr, $right:expr, $($t:tt)*) => {{
|
|
let ll = $left;
|
|
let rr = $right;
|
|
if ll != rr {
|
|
let message = format!($($t)*);
|
|
report_semantic_error($semantics, &message);
|
|
assert_eq!(ll, rr, "{}", message);
|
|
}
|
|
}};
|
|
}
|
|
|
|
fn assert_type_at(
|
|
tree: &SyntaxTree,
|
|
lines: &Lines,
|
|
pos: usize,
|
|
expected: &str,
|
|
_source_path: &str,
|
|
) {
|
|
let semantics = Semantics::new(tree, lines);
|
|
let tree_ref = match tree.find_tree_at(pos) {
|
|
Some(t) => t,
|
|
None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"),
|
|
};
|
|
|
|
let tree_type = semantics.type_of(tree_ref);
|
|
let actual = format!("{}", tree_type.unwrap_or(Type::Error));
|
|
semantic_assert_eq!(
|
|
&semantics,
|
|
expected,
|
|
actual,
|
|
"The type of the tree at position {pos} was incorrect"
|
|
);
|
|
}
|
|
|
|
fn assert_type_error_at(
|
|
tree: &SyntaxTree,
|
|
lines: &Lines,
|
|
pos: usize,
|
|
expected: &str,
|
|
_source_path: &str,
|
|
) {
|
|
let semantics = Semantics::new(tree, lines);
|
|
let tree_ref = match tree.find_tree_at(pos) {
|
|
Some(t) => t,
|
|
None => semantic_panic!(&semantics, "Unable to find the subtee at position {pos}"),
|
|
};
|
|
|
|
let tree_type = semantics.type_of(tree_ref);
|
|
semantic_assert!(
|
|
&semantics,
|
|
matches!(tree_type, Some(Type::Error)),
|
|
"The type of the {:?} tree at position {pos} was '{tree_type:?}', not an error",
|
|
tree[tree_ref].kind
|
|
);
|
|
|
|
let errors = semantics.snapshot_errors();
|
|
semantic_assert!(
|
|
&semantics,
|
|
errors.iter().any(|e| e.message == expected),
|
|
"Unable to find the expected error message '{expected}'"
|
|
);
|
|
}
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/generated_tests.rs"));
|