implicit-predicates-gen 0.0.1

Code generator for exact geometric predicates over explicit and implicit points
Documentation
//! Tests for the generated checks of a predicate: its exact evaluator and
//! the cases at the edge of the error bound of its filter.

use implicit_predicates_gen::{Generated, generate};
use proc_macro2::TokenStream;

/// How the tests say to regenerate the code.
const REGENERATE: &str = "run the tests.";

const ORIENT3D: &str = include_str!("fixtures/orient3d.pred");
const INSPHERE: &str = include_str!("fixtures/insphere.pred");

const SOURCES: [(&str, &str); 2] = [
    (ORIENT3D, "predicates/orient3d.pred"),
    (INSPHERE, "predicates/insphere.pred"),
];

fn generated(source: &str, file: &str) -> Generated {
    let mut all = generate(source, file, REGENERATE).unwrap();
    assert_eq!(all.len(), 1);
    all.remove(0)
}

/// The hexadecimal literals of the table of bound-edge cases.
fn case_literals(checks: &str) -> Vec<&str> {
    let start = checks
        .find("const BOUND_EDGE_CASES")
        .expect("a table of cases");
    let table = &checks[start..];
    let table = &table[table.find('=').unwrap()..table.find(";\n").unwrap()];
    table
        .split(|c: char| !c.is_ascii_alphanumeric())
        .filter(|word| word.starts_with("0x"))
        .collect()
}

#[test]
fn the_checks_file_parses_as_rust() {
    // Lexed here; it is compiled with the tests of the `implicit-predicates`
    // crate, and the command line tool formats it with rustfmt.
    for (source, file) in SOURCES {
        let g = generated(source, file);
        assert!(!g.checks.is_empty(), "{file}");
        g.checks
            .parse::<TokenStream>()
            .unwrap_or_else(|e| panic!("{file}: {e}"));
        assert!(g.checks.starts_with(&format!(
            "// Generated by implicit-predicates-gen from {file}. Do not edit.\n"
        )));
        assert!(g.checks.contains("pub fn exact("), "{file}");
        assert!(g.checks.contains("pub fn bound_edge_cases("), "{file}");
    }
}

#[test]
fn edge_cases_are_emitted_as_bit_patterns() {
    for (source, file, points) in [
        (ORIENT3D, "predicates/orient3d.pred", 4),
        (INSPHERE, "predicates/insphere.pred", 5),
    ] {
        let g = generated(source, file);
        assert!(g.checks.contains("f64::from_bits"), "{file}");
        let literals = case_literals(&g.checks);
        assert_eq!(literals.len() % (3 * points), 0, "{file}");
        let cases = literals.len() / (3 * points);
        assert!(cases >= 64, "{file}: {cases} cases");
        // Below and above the bound, in pairs.
        assert_eq!(cases % 2, 0, "{file}");
        for literal in literals {
            let digits = literal.strip_prefix("0x").unwrap().replace('_', "");
            assert_eq!(digits.len(), 16, "{literal}");
            let x = f64::from_bits(u64::from_str_radix(&digits, 16).unwrap());
            assert!(x.is_finite(), "{literal}");
        }
    }
}

#[test]
fn generation_of_checks_is_deterministic() {
    for (source, file) in SOURCES {
        let first = generated(source, file);
        let second = generated(source, file);
        assert_eq!(first.checks, second.checks, "{file}");
    }
}