implicit_predicates_gen/emit/
points.rs1use std::fmt::Write;
13
14use super::point_checks::write_checks;
15use super::point_eval::{write_error_helpers, write_exact_helpers};
16use super::point_exact::ExactPlan;
17use super::point_rules::{check_names, check_size};
18use super::point_types::{write_any_point, write_explicit, write_implicit};
19use super::{Generated, comment, literal, notice};
20use crate::error::Error;
21use crate::point::{
22 ErrorProgram, LinearSum, ParamType, PointNode, PointType, error_bound, parse_points,
23};
24
25pub(crate) const MAX_RENDERED_NODES: usize = 64;
27
28pub fn generate_points(
62 source: &str,
63 file: &str,
64 regenerate: &str,
65) -> Result<Option<Generated>, Error> {
66 let points = parse_points(source, file)?;
67 if points.is_empty() {
68 return Ok(None);
69 }
70 let mut types = Vec::new();
71 for point in &points {
72 types.push(prepare(point, file, &types)?);
73 }
74 let notice = notice(file, regenerate);
75 let mut stages = String::new();
76 write_module(&mut stages, &types, ¬ice);
77 let mut checks = String::new();
78 write_checks(&mut checks, &types, ¬ice);
79 Ok(Some(Generated {
80 name: "points".to_string(),
81 stages,
82 checks,
83 }))
84}
85
86pub(crate) struct TypeInfo<'a> {
88 pub(crate) point: &'a PointType,
89 pub(crate) slot: Vec<usize>,
91 pub(crate) vectors: usize,
93 pub(crate) scalars: usize,
94 pub(crate) check_name: String,
96 pub(crate) implicit: Option<Implicit>,
99}
100
101pub(crate) struct Implicit {
102 pub(crate) program: ErrorProgram,
103 pub(crate) rounds: u32,
104 pub(crate) slack: f64,
105 pub(crate) exact: ExactPlan,
106 pub(crate) bounds: Vec<(usize, Bound)>,
109}
110
111#[derive(Clone, Copy)]
113pub(crate) enum Bound {
114 AtLeast(f64),
115 AtMost(f64),
116}
117
118impl TypeInfo<'_> {
119 pub(crate) fn leaf(&self, node: PointNode) -> Option<String> {
122 match node {
123 PointNode::Coord { param, axis } => Some(format!("c[{}][{axis}]", self.slot[param])),
124 PointNode::Scalar(param) => Some(format!("s[{}]", self.slot[param])),
125 #[allow(clippy::cast_precision_loss)]
126 PointNode::Int(n) => Some(literal(n as f64)),
127 _ => None,
128 }
129 }
130
131 pub(crate) fn array_params(&self) -> String {
133 let mut text = format!("c: [[f64; 3]; {}]", self.vectors);
134 if self.scalars > 0 {
135 write!(text, ", s: [f64; {}]", self.scalars).unwrap();
136 }
137 text
138 }
139
140 pub(crate) fn array_args(&self, prefix: &str) -> String {
143 let of = |ty: ParamType| {
144 let names: Vec<String> = (self.point.params.iter())
145 .filter(|p| p.ty == ty)
146 .map(|p| format!("{prefix}{}", p.name))
147 .collect();
148 format!("[{}]", names.join(", "))
149 };
150 let mut text = of(ParamType::F64x3);
151 if self.scalars > 0 {
152 write!(text, ", {}", of(ParamType::F64)).unwrap();
153 }
154 text
155 }
156}
157
158fn prepare<'a>(
161 point: &'a PointType,
162 file: &str,
163 earlier: &[TypeInfo<'_>],
164) -> Result<TypeInfo<'a>, Error> {
165 check_names(point, file)?;
166 let check_name = format!("exact_{}", snake_case(&point.name));
167 if let Some(other) = earlier.iter().find(|t| t.check_name == check_name) {
168 return Err(Error::at(
169 file,
170 point.position,
171 format!(
172 "`{}` and `{}` would both be checked by `{check_name}`: rename one of them",
173 other.point.name, point.name
174 ),
175 ));
176 }
177 let mut counts = [0, 0];
178 let slot = (point.params.iter())
179 .map(|p| {
180 let kind = usize::from(p.ty == ParamType::F64);
181 counts[kind] += 1;
182 counts[kind] - 1
183 })
184 .collect();
185 let implicit = if point.is_explicit() {
186 None
187 } else {
188 let bound = error_bound(point, file)?;
189 let program = ErrorProgram::of(point);
190 Some(Implicit {
191 program,
192 rounds: bound.rounds.into_iter().max().unwrap_or(0),
193 slack: bound.slack,
194 exact: ExactPlan::of(point, file)?,
195 bounds: constraint_bounds(point, file)?,
196 })
197 };
198 check_size(point, file)?;
201 Ok(TypeInfo {
202 point,
203 slot,
204 vectors: counts[0],
205 scalars: counts[1],
206 check_name,
207 implicit,
208 })
209}
210
211fn snake_case(name: &str) -> String {
213 let mut out = String::new();
214 let mut previous_lower = false;
215 for c in name.chars() {
216 if c.is_uppercase() && previous_lower {
217 out.push('_');
218 }
219 previous_lower = c.is_lowercase() || c.is_ascii_digit();
220 out.extend(c.to_lowercase());
221 }
222 out
223}
224
225fn constraint_bounds(point: &PointType, file: &str) -> Result<Vec<(usize, Bound)>, Error> {
228 let mut bounds = Vec::new();
229 for constraint in &point.constraints {
230 let mut coefficients = vec![0i64; point.params.len()];
232 let count = |sum: &LinearSum, sign: i64, coefficients: &mut Vec<i64>| {
233 for &p in &sum.params {
234 coefficients[p] = coefficients[p].saturating_add(sign);
235 }
236 };
237 count(&constraint.lhs, 1, &mut coefficients);
238 count(&constraint.rhs, -1, &mut coefficients);
239 let k = i128::from(constraint.rhs.constant) - i128::from(constraint.lhs.constant);
240 let used: Vec<(usize, i64)> = (coefficients.iter().copied().enumerate())
241 .filter(|&(_, c)| c != 0)
242 .collect();
243 let limit = 1i128 << 53;
244 let bound = match used.as_slice() {
245 [(param, 1)] if k.abs() <= limit => (*param, Bound::AtMost(k as f64)),
246 [(param, -1)] if k.abs() <= limit => (*param, Bound::AtLeast(-k as f64)),
247 _ => {
248 return Err(Error::at(
249 file,
250 constraint.position,
251 "only a comparison of one parameter with an integer of magnitude at most \
252 2^53, once the parameters on both sides cancel, can be checked exactly \
253 for now",
254 ));
255 }
256 };
257 bounds.push(bound);
258 }
259 Ok(bounds)
260}
261
262fn write_module(out: &mut String, types: &[TypeInfo<'_>], notice: &str) {
264 out.push_str(notice);
265 let names: Vec<String> = types
266 .iter()
267 .map(|t| format!("[`{}`]", t.point.name))
268 .collect();
269 comment(
270 out,
271 "//!",
272 0,
273 &format!(
274 "The point types {}, and [`AnyPoint`], which holds a point of any of them.",
275 names.join(", ")
276 ),
277 );
278 let implicit: Vec<&Implicit> = types.iter().filter_map(|t| t.implicit.as_ref()).collect();
279 let mut core = vec!["ExactCoords", "ExactError", "Point", "Scratch"];
280 if !implicit.is_empty() {
281 core.extend(["Mark", "ScratchExhausted", "Span", "Weight"]);
282 if implicit.iter().any(|i| i.exact.uses_products()) {
283 core.push("eft");
284 }
285 core.push("expansion");
286 }
287 writeln!(
288 out,
289 "\nuse core::fmt;\n\nuse implicit_predicates_core::{{{}}};\n",
290 core.join(", ")
291 )
292 .unwrap();
293 write_error(out);
294 for info in types {
295 match &info.implicit {
296 None => write_explicit(out, info),
297 Some(implicit) => write_implicit(out, info, implicit),
298 }
299 }
300 write_any_point(out, types);
301 if !implicit.is_empty() {
302 write_error_helpers(out, &implicit);
303 write_exact_helpers(out, &implicit);
304 }
305}
306
307fn write_error(out: &mut String) {
308 out.push_str(
309 "/// Why the constructor of a point type rejected its inputs.\n\
310 #[derive(Clone, Copy, Debug, PartialEq, Eq)]\n\
311 pub enum PointError {\n\
312 /// An input is NaN or infinite.\n\
313 NonFinite,\n\
314 /// The parameters do not satisfy the conditions of the type.\n\
315 ParamOutOfRange,\n\
316 }\n\n\
317 impl fmt::Display for PointError {\n\
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n\
319 f.write_str(match self {\n\
320 PointError::NonFinite => \"an input of the point is not finite\",\n\
321 PointError::ParamOutOfRange => \"a parameter of the point is out of range\",\n\
322 })\n}\n}\n\n\
323 impl core::error::Error for PointError {}\n\n",
324 );
325}