use std::path::{Path, PathBuf};
use fml_model::compile;
use serde_json::Value;
fn reference_dir() -> PathBuf {
match std::env::var_os("FML_REFERENCE") {
Some(dir) => PathBuf::from(dir),
None => Path::new(env!("CARGO_MANIFEST_DIR")).join("../../reference/maps"),
}
}
fn align_with_reference(value: &mut Value) {
match value {
Value::String(text) => *text = quote_calendar_units(text),
Value::Object(map) => {
map.values_mut().for_each(align_with_reference);
}
Value::Array(items) => items.iter_mut().for_each(align_with_reference),
_ => {}
}
}
const CALENDAR_UNITS: &[&str] = &[
"years",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"year",
"month",
"week",
"day",
"hour",
"minute",
"second",
"millisecond",
];
fn quote_calendar_units(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
'scan: while !rest.is_empty() {
if out.ends_with(|c: char| c.is_ascii_digit()) && rest.starts_with(' ') {
for unit in CALENDAR_UNITS {
if let Some(after) = rest[1..].strip_prefix(unit)
&& !after.starts_with(|c: char| c.is_alphanumeric() || c == '_')
{
out.push_str(&format!(" '{unit}'"));
rest = after;
continue 'scan;
}
}
}
let c = rest.chars().next().unwrap();
out.push(c);
rest = &rest[c.len_utf8()..];
}
out
}
#[test]
fn matches_the_reference_compiler() {
let dir = reference_dir();
let Ok(index) = std::fs::read_to_string(dir.join("index.tsv")) else {
eprintln!("no reference output; run scripts/reference.sh");
return;
};
let mut compared = 0;
let mut different = Vec::new();
for line in index.lines() {
let Some((number, file)) = line.split_once('\t') else {
continue;
};
let Ok(expected) = std::fs::read_to_string(dir.join(format!("{number}.json"))) else {
continue;
};
let Ok(text) = std::fs::read_to_string(file) else {
continue;
};
let expected: Value = serde_json::from_str(&expected).unwrap();
let mut actual = serde_json::to_value(compile(&text).map).unwrap();
align_with_reference(&mut actual);
compared += 1;
if actual != expected {
different.push(file.to_owned());
}
}
eprintln!(
"reference: {compared} maps compared, {} different",
different.len()
);
assert!(different.is_empty(), "{}", different.join("\n"));
}