1mod chain;
10mod checks;
11mod edges;
12mod exact;
13mod filter;
14mod program;
15mod statements;
16
17#[cfg(test)]
18mod tests;
19
20use std::fmt::Write;
21
22use implicit_predicates_core::eft::{PRODUCT_OPERAND_MAX, PRODUCT_OPERAND_MIN};
23
24use crate::bound::{FilterConstants, MAX_DEGREE, filter_bound};
25use crate::differences::difference_graph;
26use crate::error::Error;
27use crate::parse::{Predicate, parse};
28use crate::polynomial::check_same_polynomial;
29use crate::range::{ExactRange, Leaves, exact_plan_differences, exact_plan_raw};
30use exact::{Check, Stage, write_in_range, write_product, write_stage};
31use program::{Program, Step};
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Generated {
36 pub name: String,
38 pub stages: String,
40 pub checks: String,
45}
46
47pub fn generate(source: &str, file: &str, regenerate: &str) -> Result<Vec<Generated>, Error> {
101 let notice = notice(file, regenerate);
102 parse(source, file)?
103 .iter()
104 .map(|predicate| generate_one(predicate, file, ¬ice))
105 .collect()
106}
107
108struct Forms {
110 constants: FilterConstants,
111 differences: (Program, ExactRange),
112 translated: (Program, ExactRange),
113 untranslated: Option<(Program, ExactRange)>,
114}
115
116fn generate_one(predicate: &Predicate, file: &str, notice: &str) -> Result<Generated, Error> {
118 let position = predicate.position;
123 let graph = difference_graph(&predicate.main, file, position)?;
124 if let Some(exact) = &predicate.exact {
125 let degree = exact.degree_bound();
126 if degree > u64::from(MAX_DEGREE) {
127 return Err(Error::at(
128 file,
129 predicate.exact_position.unwrap_or(position),
130 format!(
131 "the exact form of `{}` has a degree of up to {degree}, above {MAX_DEGREE}, \
132 the highest degree the filter accepts",
133 predicate.name
134 ),
135 ));
136 }
137 }
138 let constants = filter_bound(&graph, file, position)?.constants();
139 check_same_polynomial(predicate, file)?;
140 if graph.nodes.len() > edges::MAX_NODES {
141 return Err(Error::at(
142 file,
143 position,
144 format!(
145 "the main form of `{}` has {} distinct subexpressions, more than the {} \
146 for which the checks of its tests are generated: their edge cases take \
147 thousands of exact evaluations of the whole form",
148 predicate.name,
149 graph.nodes.len(),
150 edges::MAX_NODES
151 ),
152 ));
153 }
154 let (min, max) = (PRODUCT_OPERAND_MIN, PRODUCT_OPERAND_MAX);
155 let plan = |leaves| {
156 exact_plan_differences(&graph, leaves, file, position, min, max).map(|plan| {
157 (
158 Program::differences(&graph, leaves, &plan.lengths),
159 plan.range,
160 )
161 })
162 };
163 let differences = plan(Leaves::One)?;
164 let translated = plan(Leaves::Two)?;
165 let untranslated = match &predicate.exact {
166 Some(exact) => {
167 let position = predicate.exact_position.unwrap_or(position);
168 let plan = exact_plan_raw(exact, file, position, min, max)?;
169 Some((Program::raw(exact, &plan.lengths), plan.range))
170 }
171 None => None,
172 };
173 let forms = Forms {
174 constants,
175 differences,
176 translated,
177 untranslated,
178 };
179 let points = predicate.params.len();
180 let mut out = String::new();
181 write_header(&mut out, predicate, notice);
182 write_constants(&mut out, &forms);
183 let uses_max = filter::write_filter(&mut out, &graph, points, constants.degree);
184 write_exact_stages(&mut out, predicate, &forms);
185 chain::write_chain(&mut out, points, forms.untranslated.is_some());
186 if uses_max {
187 filter::write_max(&mut out);
188 }
189 write_in_range(&mut out);
190 let programs = [&forms.differences, &forms.translated]
191 .into_iter()
192 .chain(&forms.untranslated);
193 if programs
194 .flat_map(|(program, _)| &program.steps)
195 .any(|step| matches!(step, Step::Product { .. }))
196 {
197 write_product(&mut out);
198 }
199 let mut checks = String::new();
200 checks::write_checks(&mut checks, predicate, &graph, &constants, notice);
201 Ok(Generated {
202 name: predicate.name.clone(),
203 stages: out,
204 checks,
205 })
206}
207
208fn notice(file: &str, regenerate: &str) -> String {
212 let mut out = format!("// Generated by implicit-predicates-gen from {file}. Do not edit.\n");
213 for (i, line) in regenerate.lines().enumerate() {
214 let line = if i == 0 {
215 format!("To regenerate, {line}")
216 } else {
217 line.to_string()
218 };
219 writeln!(out, "// {line}").unwrap();
220 }
221 out.push('\n');
222 out
223}
224
225fn write_header(out: &mut String, predicate: &Predicate, notice: &str) {
226 out.push_str(notice);
227 comment(
228 out,
229 "//!",
230 0,
231 &format!(
232 "The stages of the predicate `{}`.\n\n\
233 Every stage takes the points {} of the definition as `p`, in that order, \
234 and returns the sign of the predicate, or `None` when it cannot decide. \
235 [`staged`] runs them in order.",
236 predicate.name,
237 list(&predicate.params)
238 ),
239 );
240 out.push_str("\nuse implicit_predicates_core::{Sign, eft, expansion};\n\n");
241}
242
243fn list(names: &[String]) -> String {
245 let quoted: Vec<String> = names.iter().map(|n| format!("`{n}`")).collect();
246 match quoted.split_last() {
247 Some((last, rest)) if !rest.is_empty() => format!("{} and {last}", rest.join(", ")),
248 _ => quoted.concat(),
249 }
250}
251
252fn write_constants(out: &mut String, forms: &Forms) {
253 let c = &forms.constants;
254 let constant = |out: &mut String, doc: &str, name: &str, ty: &str, value: String| {
255 comment(out, "///", 0, doc);
256 writeln!(out, "pub const {name}: {ty} = {value};\n").unwrap();
257 };
258 constant(
259 out,
260 "The degree `k` of the predicate in the differences of coordinates.",
261 "DEGREE",
262 "u32",
263 c.degree.to_string(),
264 );
265 constant(
266 out,
267 "The coefficient of `D^k` in the error bound of [`filter`], \
268 `(EPSILON * D + KAPPA * E) * D^(k - 1)`, rounded up. `E` is the `e` given to \
269 the filter and `D` is `E` plus the largest magnitude of a difference of \
270 coordinates.",
271 "EPSILON",
272 "f64",
273 literal(c.epsilon),
274 );
275 constant(
276 out,
277 "The coefficient of `E * D^(k - 1)` in the error bound of [`filter`], rounded up.",
278 "KAPPA",
279 "f64",
280 literal(c.kappa),
281 );
282 constant(
283 out,
284 &format!(
285 "The smallest `D` for which [`filter`] decides, `2^{}`: below it, underflow \
286 could exceed the error bound.",
287 power_of_two(c.d_min)
288 ),
289 "D_MIN",
290 "f64",
291 literal(c.d_min),
292 );
293 constant(
294 out,
295 &format!(
296 "The largest `D` for which [`filter`] decides, `2^{}`: above it, a computed \
297 value could overflow.",
298 power_of_two(c.d_max)
299 ),
300 "D_MAX",
301 "f64",
302 literal(c.d_max),
303 );
304 let range = |out: &mut String, name: &str, what: &str, range: ExactRange| {
305 constant(
306 out,
307 &format!(
308 "`(min, max)`, `(2^{}, 2^{})`: {what} has a magnitude in `min..=max`.",
309 power_of_two(range.min),
310 power_of_two(range.max)
311 ),
312 name,
313 "(f64, f64)",
314 format!("({}, {})", literal(range.min), literal(range.max)),
315 );
316 };
317 range(
318 out,
319 "DIFFERENCES_RANGE",
320 "[`exact_differences`] decides when every difference of coordinates is exact \
321 in `f64` and every nonzero one",
322 forms.differences.1,
323 );
324 range(
325 out,
326 "TRANSLATED_RANGE",
327 "[`expansions_translated`] decides when every nonzero coordinate",
328 forms.translated.1,
329 );
330 if let Some((_, r)) = &forms.untranslated {
331 range(
332 out,
333 "UNTRANSLATED_RANGE",
334 "[`expansions_untranslated`] decides when every nonzero coordinate",
335 *r,
336 );
337 }
338}
339
340fn write_exact_stages(out: &mut String, predicate: &Predicate, forms: &Forms) {
341 let points = predicate.params.len();
342 let doc = "The exact sign of the predicate: its main form evaluated with expansions on \
343 the differences of coordinates, each computed in `f64`.\n\n\
344 `None` means that this stage cannot decide: some difference is not exact in \
345 `f64`, or some nonzero difference has a magnitude outside [`DIFFERENCES_RANGE`], \
346 as when a coordinate is not finite.";
347 write_stage(
348 out,
349 &Stage {
350 name: "exact_differences",
351 doc,
352 program: &forms.differences.0,
353 check: Check::Differences("DIFFERENCES_RANGE"),
354 },
355 points,
356 );
357 let doc = "The exact sign of the predicate: its main form evaluated with expansions, \
358 each difference of coordinates as an exact expansion of two components.\n\n\
359 `None` means that this stage cannot decide: some nonzero coordinate has a \
360 magnitude outside [`TRANSLATED_RANGE`] or is not finite.";
361 write_stage(
362 out,
363 &Stage {
364 name: "expansions_translated",
365 doc,
366 program: &forms.translated.0,
367 check: Check::Coordinates("TRANSLATED_RANGE"),
368 },
369 points,
370 );
371 if let Some((program, _)) = &forms.untranslated {
372 let doc = "The exact sign of the predicate: its form for exact evaluation, marked \
373 `#[exact]` in the definition, evaluated with expansions on the coordinates.\n\n\
374 `None` means that this stage cannot decide: some nonzero coordinate has a \
375 magnitude outside [`UNTRANSLATED_RANGE`] or is not finite.";
376 write_stage(
377 out,
378 &Stage {
379 name: "expansions_untranslated",
380 doc,
381 program,
382 check: Check::Coordinates("UNTRANSLATED_RANGE"),
383 },
384 points,
385 );
386 }
387}
388
389const COMMENT_WIDTH: usize = 80;
391
392pub(crate) fn comment(out: &mut String, marker: &str, indent: usize, text: &str) {
396 let room = COMMENT_WIDTH.saturating_sub(indent + marker.len() + 1);
397 for (i, paragraph) in text.split("\n\n").enumerate() {
398 if i > 0 {
399 writeln!(out, "{marker}").unwrap();
400 }
401 let mut line = String::new();
402 for word in paragraph.split_whitespace() {
403 if !line.is_empty() && line.len() + 1 + word.len() > room {
404 writeln!(out, "{marker} {line}").unwrap();
405 line.clear();
406 }
407 if !line.is_empty() {
408 line.push(' ');
409 }
410 line.push_str(word);
411 }
412 writeln!(out, "{marker} {line}").unwrap();
413 }
414}
415
416fn power_of_two(x: f64) -> i64 {
418 debug_assert!(x.is_normal() && x.to_bits() & ((1 << 52) - 1) == 0);
419 i64::try_from(x.to_bits() >> 52).expect("11 bits") - 1023
420}
421
422pub(crate) fn literal(x: f64) -> String {
425 debug_assert!(x.is_finite());
426 format!("{x:?}")
427}