mod chain;
mod check_fns;
mod checks;
mod edges;
mod exact;
mod filter;
mod filter_axis;
mod generic;
mod grid;
mod point_checks;
mod point_eval;
mod point_exact;
mod point_rules;
mod point_types;
mod points;
mod program;
mod spans;
mod stages;
mod statements;
mod text;
#[cfg(test)]
mod tests;
use std::fmt::Write;
use implicit_predicates_core::eft::{PRODUCT_OPERAND_MAX, PRODUCT_OPERAND_MIN};
use crate::bound::{FilterForm, MAX_DEGREE, filter_form};
use crate::differences::difference_graph;
use crate::error::Error;
use crate::parse::{Predicate, parse};
use crate::polynomial::check_same_polynomial;
use crate::range::{
Leaves, MAX_COORD_COMPONENTS, exact_plan_differences, exact_plan_raw,
exact_plan_spans_translated, exact_plan_spans_untranslated,
};
use chain::ChainStage;
use exact::{write_in_range, write_product};
use filter::FilterKind;
pub use points::generate_points;
use program::{Program, Step};
use spans::write_span_in_range;
use stages::{Forms, fits_on_stack, write_constants, write_exact_stages};
pub(crate) use text::{comment, literal, power_of_two};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Generated {
pub name: String,
pub stages: String,
pub checks: String,
}
pub fn generate(source: &str, file: &str, regenerate: &str) -> Result<Vec<Generated>, Error> {
let notice = notice(file, regenerate);
parse(source, file)?
.iter()
.map(|predicate| generate_one(predicate, file, ¬ice))
.collect()
}
fn generate_one(predicate: &Predicate, file: &str, notice: &str) -> Result<Generated, Error> {
let position = predicate.position;
let graph = difference_graph(&predicate.main, file, position)?;
if let Some(exact) = &predicate.exact {
let degree = exact.degree_bound();
if degree > u64::from(MAX_DEGREE) {
return Err(Error::at(
file,
predicate.exact_position.unwrap_or(position),
format!(
"the exact form of `{}` has a degree of up to {degree}, above {MAX_DEGREE}, \
the highest degree the filter accepts",
predicate.name
),
));
}
}
let filter = match filter_form(&graph, file, position)? {
FilterForm::PerAxis(bound) => FilterKind::PerAxis(bound.constants(), bound.certain_zero()),
FilterForm::Global(bound) => FilterKind::Global(bound.constants()),
};
check_same_polynomial(predicate, file)?;
if graph.nodes.len() > edges::MAX_NODES {
return Err(Error::at(
file,
position,
format!(
"the main form of `{}` has {} distinct subexpressions, more than the {} \
for which the checks of its tests are generated: their edge cases take \
thousands of exact evaluations of the whole form",
predicate.name,
graph.nodes.len(),
edges::MAX_NODES
),
));
}
let (min, max) = (PRODUCT_OPERAND_MIN, PRODUCT_OPERAND_MAX);
let stack = |leaves| {
exact_plan_differences(&graph, leaves, file, position, min, max).map(|plan| {
fits_on_stack(plan.total).then(|| {
(
Program::differences(&graph, leaves, &plan.lengths),
plan.range,
)
})
})
};
let differences = stack(Leaves::One)?;
let translated = stack(Leaves::Two)?;
let spans =
exact_plan_spans_translated(&graph, MAX_COORD_COMPONENTS, file, position, min, max)?;
let (untranslated, spans_untranslated) = match &predicate.exact {
Some(exact) => {
let position = predicate.exact_position.unwrap_or(position);
let plan = exact_plan_raw(exact, file, position, min, max)?;
let program = |lengths: &[usize]| Program::raw(exact, lengths);
let stack = fits_on_stack(plan.total).then(|| (program(&plan.lengths), plan.range));
let spans = exact_plan_spans_untranslated(
exact,
MAX_COORD_COMPONENTS,
file,
position,
min,
max,
)?;
(stack, Some((exact, spans.range)))
}
None => (None, None),
};
let forms = Forms {
filter,
differences,
translated,
untranslated,
spans_translated: (&graph, spans.range),
spans_untranslated,
};
let points = predicate.params.len();
let mut out = String::new();
write_header(&mut out, predicate, notice, &forms);
write_constants(&mut out, &forms);
let (uses_max, explicit) = match &forms.filter {
FilterKind::Global(c) => (
filter::write_filter(&mut out, &graph, points, c.degree),
"0.0",
),
FilterKind::PerAxis(c, zero) => (
filter_axis::write_filter(&mut out, &graph, points, c, *zero),
"[0.0; 3]",
),
};
write_exact_stages(&mut out, points, &forms);
let explicit_stages = chain_stages(&forms);
chain::write_chain(&mut out, points, &explicit_stages, explicit);
let spans: &[&str] = if forms.spans_untranslated.is_some() {
&["expansions_translated_in", "expansions_untranslated_in"]
} else {
&["expansions_translated_in"]
};
generic::write_generic_chain(
&mut out,
&generic::GenericChain {
points,
per_axis: matches!(forms.filter, FilterKind::PerAxis(..)),
explicit: &explicit_stages,
spans,
},
);
if uses_max {
filter::write_max(&mut out);
}
write_in_range(&mut out);
write_span_in_range(&mut out);
if forms
.stack_stages()
.flat_map(|(_, program)| &program.steps)
.any(|step| matches!(step, Step::Product { .. }))
{
write_product(&mut out);
}
let mut checks = String::new();
checks::write_checks(&mut checks, predicate, &graph, &forms.filter, notice);
Ok(Generated {
name: predicate.name.clone(),
stages: out,
checks,
})
}
pub(crate) fn notice(file: &str, regenerate: &str) -> String {
let mut out = format!("// Generated by implicit-predicates-gen from {file}. Do not edit.\n");
for (i, line) in regenerate.lines().enumerate() {
let line = if i == 0 {
format!("To regenerate, {line}")
} else {
line.to_string()
};
writeln!(out, "// {line}").unwrap();
}
out.push('\n');
out
}
fn write_header(out: &mut String, predicate: &Predicate, notice: &str, forms: &Forms) {
out.push_str(notice);
comment(
out,
"//!",
0,
&format!(
"The stages of the predicate `{}`.\n\n\
Every stage takes the points {} of the definition as `p`, in that order, \
and returns the sign of the predicate, or `None` when it cannot decide. \
[`staged`] runs them in order on explicit points, and [`staged_in`] on points \
of any type.",
predicate.name,
list(&predicate.params)
),
);
let mut names = vec![
"ExactCoords",
"ExactError",
"Point",
"Scratch",
"ScratchExhausted",
"Sign",
"Span",
"Weight",
];
if forms.differences.is_some() {
names.push("eft");
}
if forms.stack_stages().next().is_some() {
names.push("expansion");
}
writeln!(
out,
"\nuse implicit_predicates_core::{{{}}};\n",
names.join(", ")
)
.unwrap();
}
fn chain_stages(forms: &Forms) -> Vec<ChainStage> {
let mut stages = Vec::new();
if forms.differences.is_some() {
stages.push(ChainStage::Stack("exact_differences"));
}
stages.push(match forms.translated {
Some(_) => ChainStage::Stack("expansions_translated"),
None => ChainStage::Spans("expansions_translated_in"),
});
if forms.spans_untranslated.is_some() {
stages.push(match forms.untranslated {
Some(_) => ChainStage::Stack("expansions_untranslated"),
None => ChainStage::Spans("expansions_untranslated_in"),
});
}
stages
}
pub(crate) fn list(names: &[String]) -> String {
let quoted: Vec<String> = names.iter().map(|n| format!("`{n}`")).collect();
match quoted.split_last() {
Some((last, rest)) if !rest.is_empty() => format!("{} and {last}", rest.join(", ")),
_ => quoted.concat(),
}
}