use std::fmt::Write;
use super::point_eval::write_evaluate;
use super::point_exact::write_exact;
use super::points::{Bound, Implicit, MAX_RENDERED_NODES, TypeInfo};
use super::{comment, list, literal};
use crate::point::{LinearSum, ParamType, PointNode, PointType};
fn signature(point: &PointType) -> String {
let params: Vec<String> = (point.params.iter())
.map(|p| match p.ty {
ParamType::F64x3 => format!("{}: [f64; 3]", p.name),
ParamType::F64 => format!("{}: f64", p.name),
})
.collect();
params.join(", ")
}
fn all_finite(point: &PointType) -> String {
let parts: Vec<String> = (point.params.iter())
.map(|p| match p.ty {
ParamType::F64x3 => format!("{}.iter().all(|x| x.is_finite())", p.name),
ParamType::F64 => format!("{}.is_finite()", p.name),
})
.collect();
parts.join(" && ")
}
pub(crate) fn write_explicit(out: &mut String, info: &TypeInfo<'_>) {
let point = info.point;
let (name, param) = (&point.name, &point.params[0].name);
comment(
out,
"///",
0,
&format!(
"A point of type `{name}`: the point with the coordinates `{param}`, given \
exactly. Its approximation is its coordinates and its error is zero."
),
);
writeln!(
out,
"#[derive(Clone, Copy, Debug, PartialEq)]\n#[repr(transparent)]\npub struct {name}([f64; 3]);\n\n\
impl {name} {{"
)
.unwrap();
comment(
out,
"///",
0,
&format!(
"The point with the coordinates `{param}`.\n\n# Errors\n\n\
[`PointError::NonFinite`] if a coordinate is NaN or infinite."
),
);
writeln!(
out,
"#[inline]\npub fn new({param}: [f64; 3]) -> Result<Self, PointError> {{\n\
if !({}) {{\nreturn Err(PointError::NonFinite);\n}}\n\
Ok(Self::new_unchecked({param}))\n}}\n",
all_finite(point)
)
.unwrap();
comment(
out,
"///",
0,
&format!(
"The point with the coordinates `{param}`, without checking them. A \
coordinate that is not finite makes [`Point::exact`] fail with \
[`ExactError::OutOfRange`]."
),
);
writeln!(
out,
"#[inline]\npub fn new_unchecked({param}: [f64; 3]) -> Self {{\nSelf({param})\n}}\n}}\n\n\
impl Point for {name} {{\n\
#[inline]\nfn approx(&self) -> &[f64; 3] {{\n&self.0\n}}\n\n\
#[inline]\nfn err(&self) -> &[f64; 3] {{\n&[0.0; 3]\n}}\n\n\
#[inline]\nfn is_explicit(&self) -> bool {{\ntrue\n}}\n\n\
#[inline]\nfn exact(&self, scratch: &mut Scratch<'_>) -> Result<ExactCoords, ExactError> {{\n\
self.0.exact(scratch)\n}}\n}}\n"
)
.unwrap();
}
fn write_description(out: &mut String, point: &PointType) {
let names: Vec<String> = point.params.iter().map(|p| p.name.clone()).collect();
let small = point.graph.nodes.len() <= MAX_RENDERED_NODES;
let ending = if small { ":" } else { " by its declaration." };
comment(
out,
"///",
0,
&format!(
"A point of type `{}`, computed from {}{ending}",
point.name,
list(&names)
),
);
let mut lines = Vec::new();
if small {
for (k, axis) in ["x", "y", "z"].iter().enumerate() {
lines.push(format!("{axis} = {}", render(point, k)));
}
}
if !point.constraints.is_empty() {
let shown: Vec<String> = (point.constraints.iter())
.map(|c| format!("{} <= {}", sum_text(point, &c.lhs), sum_text(point, &c.rhs)))
.collect();
lines.push(format!("where {}", shown.join(", ")));
}
if !lines.is_empty() {
out.push_str("///\n/// ```text\n");
for line in lines {
writeln!(out, "/// {line}").unwrap();
}
out.push_str("/// ```\n");
}
}
fn sum_text(point: &PointType, sum: &LinearSum) -> String {
let mut parts: Vec<String> = sum
.params
.iter()
.map(|&p| point.params[p].name.clone())
.collect();
if sum.constant != 0 || parts.is_empty() {
parts.push(sum.constant.to_string());
}
parts.join(" + ")
}
fn render(point: &PointType, axis: usize) -> String {
fn go(point: &PointType, id: usize) -> (String, u8) {
let name = |param: usize| point.params[param].name.as_str();
let wrap = |(text, precedence): (String, u8), min: u8| {
if precedence >= min {
text
} else {
format!("({text})")
}
};
match point.graph.nodes[id] {
PointNode::Coord { param, axis } => (
format!("{}.{}", name(param), ["x", "y", "z"][usize::from(axis)]),
4,
),
PointNode::Scalar(param) => (name(param).to_string(), 4),
PointNode::Int(n) => (n.to_string(), if n < 0 { 3 } else { 4 }),
PointNode::Neg(x) => (format!("-{}", wrap(go(point, x), 4)), 3),
PointNode::Add(x, y) => (
format!("{} + {}", wrap(go(point, x), 1), wrap(go(point, y), 2)),
1,
),
PointNode::Sub(x, y) => (
format!("{} - {}", wrap(go(point, x), 1), wrap(go(point, y), 2)),
1,
),
PointNode::Mul(x, y) => (
format!("{} * {}", wrap(go(point, x), 2), wrap(go(point, y), 3)),
2,
),
}
}
go(point, point.graph.roots[axis]).0
}
pub(crate) fn write_implicit(out: &mut String, info: &TypeInfo<'_>, implicit: &Implicit) {
let point = info.point;
let name = &point.name;
write_description(out, point);
out.push_str("///\n");
comment(
out,
"///",
0,
"The constructors compute the coordinates in `f64` with a bound on the error of \
each one, [`Point::approx`] and [`Point::err`]; [`Point::exact`] computes them \
exactly.",
);
out.push_str("#[derive(Clone, Copy, Debug, PartialEq)]\n");
writeln!(out, "pub struct {name} {{").unwrap();
for p in &point.params {
let ty = if p.ty == ParamType::F64x3 {
"[f64; 3]"
} else {
"f64"
};
writeln!(out, "{}: {ty},", p.name).unwrap();
}
out.push_str("approx: [f64; 3],\nerr: [f64; 3],\n}\n\n");
writeln!(out, "impl {name} {{").unwrap();
comment(
out,
"///",
0,
&format!(
"The slack factor of the error bound: the smallest `f64` `S` with \
`S * (1 - 2^-53)^{0} >= 1`, where {0} is the number of roundings of the \
computation of the error of a coordinate on its longest chain of operations.",
implicit.rounds
),
);
writeln!(out, "const ERR_SLACK: f64 = {};\n", literal(implicit.slack)).unwrap();
let names: Vec<String> = point.params.iter().map(|p| p.name.clone()).collect();
let mut errors = String::from("[`PointError::NonFinite`] if an input is NaN or infinite");
if !point.constraints.is_empty() {
errors.push_str(
", and [`PointError::ParamOutOfRange`] if the parameters do not satisfy the \
conditions of the type",
);
}
comment(
out,
"///",
0,
&format!(
"The point of type `{name}` with the parameters {}.\n\n# Errors\n\n{errors}.",
list(&names)
),
);
let args: Vec<&str> = point.params.iter().map(|p| p.name.as_str()).collect();
writeln!(
out,
"pub fn new({}) -> Result<Self, PointError> {{\nif !({}) {{\nreturn Err(PointError::NonFinite);\n}}",
signature(point),
all_finite(point)
)
.unwrap();
for &(param, bound) in &implicit.bounds {
let p = &point.params[param].name;
let (op, value) = match bound {
Bound::AtLeast(v) => ("<", v),
Bound::AtMost(v) => (">", v),
};
writeln!(
out,
"if {p} {op} {} {{\nreturn Err(PointError::ParamOutOfRange);\n}}",
literal(value)
)
.unwrap();
}
writeln!(out, "Ok(Self::new_unchecked({}))\n}}\n", args.join(", ")).unwrap();
comment(
out,
"///",
0,
&format!(
"The point of type `{name}` with the parameters {}, without checking them: any \
`f64` is accepted. A coordinate whose approximation or error bound is not finite \
gets an error of `+∞`, and [`Point::exact`] fails with \
[`ExactError::OutOfRange`] when an input is not finite.",
list(&names)
),
);
writeln!(
out,
"pub fn new_unchecked({}) -> Self {{\nlet (approx, err) = Self::evaluate({});\n\
Self {{\n{},\napprox,\nerr,\n}}\n}}\n",
signature(point),
info.array_args(""),
args.join(",\n")
)
.unwrap();
for p in &point.params {
comment(out, "///", 0, &format!("The parameter `{}`.", p.name));
let (ty, value) = match p.ty {
ParamType::F64x3 => ("&[f64; 3]", "&self."),
ParamType::F64 => ("f64", "self."),
};
writeln!(
out,
"#[inline]\npub fn {0}(&self) -> {ty} {{\n{value}{0}\n}}\n",
p.name
)
.unwrap();
}
write_evaluate(out, info, implicit);
write_exact(out, info, implicit);
writeln!(
out,
"}}\n\nimpl Point for {name} {{\n\
#[inline]\nfn approx(&self) -> &[f64; 3] {{\n&self.approx\n}}\n\n\
#[inline]\nfn err(&self) -> &[f64; 3] {{\n&self.err\n}}\n\n\
#[inline]\nfn is_explicit(&self) -> bool {{\nfalse\n}}\n\n\
fn exact(&self, scratch: &mut Scratch<'_>) -> Result<ExactCoords, ExactError> {{\n\
Self::write_exact(scratch, {})\n}}\n}}\n",
info.array_args("self.")
)
.unwrap();
}
pub(crate) fn write_any_point(out: &mut String, types: &[TypeInfo<'_>]) {
out.push_str(
"/// A point of any of the types of this module.\n\
#[derive(Clone, Copy, Debug, PartialEq)]\npub enum AnyPoint {\n",
);
for t in types {
let name = &t.point.name;
writeln!(out, "/// A point of type [`{name}`].\n{name}({name}),").unwrap();
}
out.push_str("}\n\nimpl Point for AnyPoint {\n");
let methods = [
("approx", "&self", "&[f64; 3]", "p.approx()"),
("err", "&self", "&[f64; 3]", "p.err()"),
("is_explicit", "&self", "bool", "p.is_explicit()"),
(
"exact",
"&self, scratch: &mut Scratch<'_>",
"Result<ExactCoords, ExactError>",
"p.exact(scratch)",
),
];
for (i, (method, params, ty, call)) in methods.iter().enumerate() {
if i > 0 {
out.push('\n');
}
writeln!(
out,
"#[inline]\nfn {method}({params}) -> {ty} {{\nmatch self {{"
)
.unwrap();
for t in types {
writeln!(out, "AnyPoint::{0}(p) => {call},", t.point.name).unwrap();
}
out.push_str("}\n}\n");
}
out.push_str("}\n");
for t in types {
writeln!(
out,
"\nimpl From<{0}> for AnyPoint {{\n#[inline]\nfn from(p: {0}) -> Self {{\nAnyPoint::{0}(p)\n}}\n}}",
t.point.name
)
.unwrap();
}
out.push('\n');
}