use std::fmt::Write;
use super::point_checks::write_checks;
use super::point_eval::{write_error_helpers, write_exact_helpers};
use super::point_exact::ExactPlan;
use super::point_rules::{check_names, check_size};
use super::point_types::{write_any_point, write_explicit, write_implicit};
use super::{Generated, comment, literal, notice};
use crate::error::Error;
use crate::point::{
ErrorProgram, LinearSum, ParamType, PointNode, PointType, error_bound, parse_points,
};
pub(crate) const MAX_RENDERED_NODES: usize = 64;
pub fn generate_points(
source: &str,
file: &str,
regenerate: &str,
) -> Result<Option<Generated>, Error> {
let points = parse_points(source, file)?;
if points.is_empty() {
return Ok(None);
}
let mut types = Vec::new();
for point in &points {
types.push(prepare(point, file, &types)?);
}
let notice = notice(file, regenerate);
let mut stages = String::new();
write_module(&mut stages, &types, ¬ice);
let mut checks = String::new();
write_checks(&mut checks, &types, ¬ice);
Ok(Some(Generated {
name: "points".to_string(),
stages,
checks,
}))
}
pub(crate) struct TypeInfo<'a> {
pub(crate) point: &'a PointType,
pub(crate) slot: Vec<usize>,
pub(crate) vectors: usize,
pub(crate) scalars: usize,
pub(crate) check_name: String,
pub(crate) implicit: Option<Implicit>,
}
pub(crate) struct Implicit {
pub(crate) program: ErrorProgram,
pub(crate) rounds: u32,
pub(crate) slack: f64,
pub(crate) exact: ExactPlan,
pub(crate) bounds: Vec<(usize, Bound)>,
}
#[derive(Clone, Copy)]
pub(crate) enum Bound {
AtLeast(f64),
AtMost(f64),
}
impl TypeInfo<'_> {
pub(crate) fn leaf(&self, node: PointNode) -> Option<String> {
match node {
PointNode::Coord { param, axis } => Some(format!("c[{}][{axis}]", self.slot[param])),
PointNode::Scalar(param) => Some(format!("s[{}]", self.slot[param])),
#[allow(clippy::cast_precision_loss)]
PointNode::Int(n) => Some(literal(n as f64)),
_ => None,
}
}
pub(crate) fn array_params(&self) -> String {
let mut text = format!("c: [[f64; 3]; {}]", self.vectors);
if self.scalars > 0 {
write!(text, ", s: [f64; {}]", self.scalars).unwrap();
}
text
}
pub(crate) fn array_args(&self, prefix: &str) -> String {
let of = |ty: ParamType| {
let names: Vec<String> = (self.point.params.iter())
.filter(|p| p.ty == ty)
.map(|p| format!("{prefix}{}", p.name))
.collect();
format!("[{}]", names.join(", "))
};
let mut text = of(ParamType::F64x3);
if self.scalars > 0 {
write!(text, ", {}", of(ParamType::F64)).unwrap();
}
text
}
}
fn prepare<'a>(
point: &'a PointType,
file: &str,
earlier: &[TypeInfo<'_>],
) -> Result<TypeInfo<'a>, Error> {
check_names(point, file)?;
let check_name = format!("exact_{}", snake_case(&point.name));
if let Some(other) = earlier.iter().find(|t| t.check_name == check_name) {
return Err(Error::at(
file,
point.position,
format!(
"`{}` and `{}` would both be checked by `{check_name}`: rename one of them",
other.point.name, point.name
),
));
}
let mut counts = [0, 0];
let slot = (point.params.iter())
.map(|p| {
let kind = usize::from(p.ty == ParamType::F64);
counts[kind] += 1;
counts[kind] - 1
})
.collect();
let implicit = if point.is_explicit() {
None
} else {
let bound = error_bound(point, file)?;
let program = ErrorProgram::of(point);
Some(Implicit {
program,
rounds: bound.rounds.into_iter().max().unwrap_or(0),
slack: bound.slack,
exact: ExactPlan::of(point, file)?,
bounds: constraint_bounds(point, file)?,
})
};
check_size(point, file)?;
Ok(TypeInfo {
point,
slot,
vectors: counts[0],
scalars: counts[1],
check_name,
implicit,
})
}
fn snake_case(name: &str) -> String {
let mut out = String::new();
let mut previous_lower = false;
for c in name.chars() {
if c.is_uppercase() && previous_lower {
out.push('_');
}
previous_lower = c.is_lowercase() || c.is_ascii_digit();
out.extend(c.to_lowercase());
}
out
}
fn constraint_bounds(point: &PointType, file: &str) -> Result<Vec<(usize, Bound)>, Error> {
let mut bounds = Vec::new();
for constraint in &point.constraints {
let mut coefficients = vec![0i64; point.params.len()];
let count = |sum: &LinearSum, sign: i64, coefficients: &mut Vec<i64>| {
for &p in &sum.params {
coefficients[p] = coefficients[p].saturating_add(sign);
}
};
count(&constraint.lhs, 1, &mut coefficients);
count(&constraint.rhs, -1, &mut coefficients);
let k = i128::from(constraint.rhs.constant) - i128::from(constraint.lhs.constant);
let used: Vec<(usize, i64)> = (coefficients.iter().copied().enumerate())
.filter(|&(_, c)| c != 0)
.collect();
let limit = 1i128 << 53;
let bound = match used.as_slice() {
[(param, 1)] if k.abs() <= limit => (*param, Bound::AtMost(k as f64)),
[(param, -1)] if k.abs() <= limit => (*param, Bound::AtLeast(-k as f64)),
_ => {
return Err(Error::at(
file,
constraint.position,
"only a comparison of one parameter with an integer of magnitude at most \
2^53, once the parameters on both sides cancel, can be checked exactly \
for now",
));
}
};
bounds.push(bound);
}
Ok(bounds)
}
fn write_module(out: &mut String, types: &[TypeInfo<'_>], notice: &str) {
out.push_str(notice);
let names: Vec<String> = types
.iter()
.map(|t| format!("[`{}`]", t.point.name))
.collect();
comment(
out,
"//!",
0,
&format!(
"The point types {}, and [`AnyPoint`], which holds a point of any of them.",
names.join(", ")
),
);
let implicit: Vec<&Implicit> = types.iter().filter_map(|t| t.implicit.as_ref()).collect();
let mut core = vec!["ExactCoords", "ExactError", "Point", "Scratch"];
if !implicit.is_empty() {
core.extend(["Mark", "ScratchExhausted", "Span", "Weight"]);
if implicit.iter().any(|i| i.exact.uses_products()) {
core.push("eft");
}
core.push("expansion");
}
writeln!(
out,
"\nuse core::fmt;\n\nuse implicit_predicates_core::{{{}}};\n",
core.join(", ")
)
.unwrap();
write_error(out);
for info in types {
match &info.implicit {
None => write_explicit(out, info),
Some(implicit) => write_implicit(out, info, implicit),
}
}
write_any_point(out, types);
if !implicit.is_empty() {
write_error_helpers(out, &implicit);
write_exact_helpers(out, &implicit);
}
}
fn write_error(out: &mut String) {
out.push_str(
"/// Why the constructor of a point type rejected its inputs.\n\
#[derive(Clone, Copy, Debug, PartialEq, Eq)]\n\
pub enum PointError {\n\
/// An input is NaN or infinite.\n\
NonFinite,\n\
/// The parameters do not satisfy the conditions of the type.\n\
ParamOutOfRange,\n\
}\n\n\
impl fmt::Display for PointError {\n\
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\
f.write_str(match self {\n\
PointError::NonFinite => \"an input of the point is not finite\",\n\
PointError::ParamOutOfRange => \"a parameter of the point is out of range\",\n\
})\n}\n}\n\n\
impl core::error::Error for PointError {}\n\n",
);
}