use implicit_predicates_core::eft::{PRODUCT_OPERAND_MAX, PRODUCT_OPERAND_MIN};
use implicit_predicates_gen::{
ExactRange, Generated, Leaves, difference_graph, exact_plan_differences, exact_plan_raw,
filter_bound, generate, parse,
};
use proc_macro2::TokenStream;
const ORIENT3D: &str = include_str!("fixtures/orient3d.pred");
const INSPHERE: &str = include_str!("fixtures/insphere.pred");
const REGENERATE: &str = "run the tests.";
fn generated(source: &str, file: &str) -> Generated {
let mut all = generate(source, file, REGENERATE).unwrap();
assert_eq!(all.len(), 1);
all.remove(0)
}
fn constant<'a>(code: &'a str, name: &str, ty: &str) -> &'a str {
let prefix = format!("pub const {name}: {ty} = ");
let start = code
.find(&prefix)
.unwrap_or_else(|| panic!("no constant {name}"))
+ prefix.len();
let end = start + code[start..].find(';').unwrap();
code[start..end].trim()
}
fn assert_literal(text: &str, expected: f64) {
let value: f64 = text.parse().unwrap_or_else(|_| panic!("{text}"));
assert_eq!(
value.to_bits(),
expected.to_bits(),
"{text} is not {expected:e}"
);
}
fn assert_range(code: &str, name: &str, range: ExactRange) {
let text = constant(code, name, "(f64, f64)");
let inner = text.strip_prefix('(').unwrap().strip_suffix(')').unwrap();
let (min, max) = inner.split_once(',').unwrap();
assert_literal(min.trim(), range.min);
assert_literal(max.trim(), range.max);
}
#[test]
fn generation_is_deterministic() {
for (source, file) in [
(ORIENT3D, "predicates/orient3d.pred"),
(INSPHERE, "predicates/insphere.pred"),
] {
let first = generate(source, file, REGENERATE).unwrap();
let second = generate(source, file, REGENERATE).unwrap();
assert_eq!(first.len(), second.len());
for (a, b) in first.iter().zip(&second) {
assert_eq!(a.name, b.name);
assert_eq!(a.stages, b.stages);
assert_eq!(a.checks, b.checks);
}
}
}
#[test]
fn the_output_parses_as_rust() {
for (source, file) in [
(ORIENT3D, "predicates/orient3d.pred"),
(INSPHERE, "predicates/insphere.pred"),
] {
let g = generated(source, file);
g.stages
.parse::<TokenStream>()
.unwrap_or_else(|e| panic!("{file}: {e}"));
}
}
#[test]
fn the_constants_in_the_output_are_the_derived_ones() {
let g = generated(ORIENT3D, "predicates/orient3d.pred");
let code = &g.stages;
let p = parse(ORIENT3D, "t.pred").unwrap().remove(0);
let diff = difference_graph(&p.main, "t.pred", p.position).unwrap();
let constants = filter_bound(&diff, "t.pred", p.position)
.unwrap()
.constants();
assert_eq!(
constant(code, "DEGREE", "u32"),
constants.degree.to_string()
);
assert_literal(constant(code, "EPSILON", "f64"), constants.epsilon);
assert_literal(constant(code, "KAPPA", "f64"), constants.kappa);
assert_literal(constant(code, "D_MIN", "f64"), constants.d_min);
assert_literal(constant(code, "D_MAX", "f64"), constants.d_max);
let (min, max) = (PRODUCT_OPERAND_MIN, PRODUCT_OPERAND_MAX);
let one = exact_plan_differences(&diff, Leaves::One, "t.pred", p.position, min, max).unwrap();
let two = exact_plan_differences(&diff, Leaves::Two, "t.pred", p.position, min, max).unwrap();
let raw = exact_plan_raw(
p.exact.as_ref().unwrap(),
"t.pred",
p.exact_position.unwrap(),
min,
max,
)
.unwrap();
assert_range(code, "DIFFERENCES_RANGE", one.range);
assert_range(code, "TRANSLATED_RANGE", two.range);
assert_range(code, "UNTRANSLATED_RANGE", raw.range);
assert_eq!(constants.degree, 3);
assert_eq!(one.range.min, 2f64.powi(-188));
assert_eq!(one.range.max, 2f64.powi(239));
assert_eq!(two.range.max, 2f64.powi(238));
assert_eq!(raw.range.max, 2f64.powi(239));
}
#[test]
fn the_header_says_do_not_edit() {
let g = generated(ORIENT3D, "predicates/orient3d.pred");
let mut lines = g.stages.lines();
assert_eq!(
lines.next(),
Some("// Generated by implicit-predicates-gen from predicates/orient3d.pred. Do not edit.")
);
assert_eq!(lines.next(), Some("// To regenerate, run the tests."));
}
#[test]
fn a_predicate_without_an_exact_form_has_no_untranslated_stage_and_chains_the_main_form() {
let g = generated(INSPHERE, "predicates/insphere.pred");
assert_eq!(g.name, "insphere");
assert!(!g.stages.contains("expansions_untranslated"));
assert!(!g.stages.contains("UNTRANSLATED_RANGE"));
assert!(g.stages.contains("pub fn expansions_translated"));
let staged = &g.stages[g.stages.find("pub fn staged").unwrap()..];
let body = &staged[..staged.find("\n}").unwrap()];
let filter = body.find("filter(p, 0.0)").unwrap();
let differences = body.find("exact_differences(p)").unwrap();
let translated = body.find("expansions_translated(p)").unwrap();
assert!(filter < differences && differences < translated, "{body}");
assert!(!body.contains("expansions_untranslated(p)"), "{body}");
}
#[test]
fn the_chain_tries_the_translated_form_before_the_exact_form() {
let o = generated(ORIENT3D, "predicates/orient3d.pred");
let staged = &o.stages[o.stages.find("pub fn staged").unwrap()..];
let body = &staged[..staged.find("\n}").unwrap()];
let order = [
"filter(p, 0.0)",
"exact_differences(p)",
"expansions_translated(p)",
"expansions_untranslated(p)",
]
.map(|stage| {
body.find(stage)
.unwrap_or_else(|| panic!("{stage}: {body}"))
});
assert!(order.is_sorted(), "{body}");
}
#[test]
fn shared_subexpressions_are_emitted_once() {
let g = generated(ORIENT3D, "predicates/orient3d.pred");
assert_eq!(
g.stages
.matches("expansion::from_two_product(p[2][1], p[3][2],")
.count(),
1
);
let s = generated(INSPHERE, "predicates/insphere.pred");
let filter = &s.stages[s.stages.find("pub fn filter").unwrap()..];
let filter = &filter[..filter.find("\n}").unwrap()];
assert_eq!(filter.matches("p[2][1] - p[4][1]").count(), 1, "{filter}");
}
#[test]
fn a_definition_error_is_reported_with_its_position() {
let e = generate(
"fn p(a: Point3, b: Point3) -> Sign {\n a.x * b.y\n}",
"predicates/p.pred",
REGENERATE,
)
.unwrap_err();
assert_eq!(e.file, "predicates/p.pred");
assert_eq!((e.line, e.column), (1, 4), "{e}");
}
#[test]
fn the_header_names_the_file_and_tells_how_to_regenerate() {
let all = generate(
ORIENT3D,
"defs/o.pred",
"run, from the root:\nmake predicates",
)
.unwrap();
let expected = "// Generated by implicit-predicates-gen from defs/o.pred. Do not edit.\n\
// To regenerate, run, from the root:\n\
// make predicates\n\n";
for code in [&all[0].stages, &all[0].checks] {
assert!(code.starts_with(expected), "{}", &code[..200]);
}
}