pretty_expressive/cost.rs
1use std::{fmt, ops::Add};
2
3/// A measurement of the badness of a particular printing layout.
4///
5/// [`Doc`](crate::Doc) is generic on the `Cost` type it can be used with, since
6/// a document can contain [`cost`](crate::cost) nodes that increase the cost of
7/// certain paths.
8///
9/// When a document is being printed, costs are assigned
10/// by a [`CostFactory`]. This determines the badness of
11/// [`text`](crate::text)/[`newline`](crate::newline) nodes based on their
12/// location in the layout. The printer will then try to minimize this cost
13/// overall.
14///
15/// A [`DefaultCost`] and [`DefaultCostFactory`] is provided, and
16/// [`Doc`](crate::Doc) will default to using the `DefaultCost` if no generic
17/// parameter is given to it.
18///
19/// ## Requirements for costs
20///
21/// A `Cost` must satisfy several properties for the printer to be able to use
22/// it correctly:
23///
24/// * `a <= b` must be a total order. This requirement is imposed by the [`Ord`]
25/// trait.
26/// * `a + b` (via the [`Add`] trait) must be commutative and associative.
27/// * If `a <= b` and `c <= d`, then `a + c <= b + d`.
28///
29/// There are [additional
30/// requirements](CostFactory#requirements-for-cost-factories) for the functions
31/// on the [`CostFactory`].
32pub trait Cost: Ord + Add + Clone + fmt::Debug {}
33
34/// Trait for types that can assign costs to particular layouts of a document.
35///
36/// A custom cost factory can be used with
37/// [`validate_with_cost`](crate::Doc::validate_with_cost) to customize how the
38/// costs of different layouts are determined.
39///
40/// In most cases, the [`DefaultCostFactory`] that is used by `to_string()` and
41/// [`validate`](crate::Doc::validate) will be sufficient.
42///
43/// ## Requirements for cost factories
44///
45/// In addition to the [requirements](Cost#requirements-for-costs) imposed on
46/// the [`Cost`] type itself, a cost factory's operation must satisfy certain
47/// properties for the printer to behave correctly:
48///
49/// * If `c1 <= c2`, then `text(c1, l) <= text(c2, l)`: the cost of adding the
50/// same length of text must not decrease as the column moves to the right.
51/// * If `i1 <= i2`, then `newline(i1) <= newline(i2)`: the cost of adding a
52/// newline must not decrease as the indentation level increases.
53/// * `text(c, l1) + text(c + l1, l2) == text(c, l1 + l2)`: splitting or
54/// combining text at the same position must not affect its cost.
55pub trait CostFactory: fmt::Debug {
56 /// The type of cost values that the factory will assign.
57 type CostType: Cost<Output = Self::CostType>;
58
59 /// Determine the cost of adding a slice of text to the document.
60 ///
61 /// The text being added has length `l` and begins at column `c`.
62 fn text(&self, c: usize, l: usize) -> Self::CostType;
63
64 /// Determine the cost of adding a newline to the document.
65 ///
66 /// There will be `i` spaces of indentation included after the newline.
67 fn newline(&self, i: usize) -> Self::CostType;
68
69 /// The computation width limit.
70 ///
71 /// If a layout the printer is attempting will exceed this width for a line,
72 /// the printer will consider that layout "tainted". A tainted layout will
73 /// not be rejected outright. Instead, it will be set aside in case there
74 /// are no valid untainted layouts. In that case, a tainted layout might be
75 /// chosen, but it is not guaranteed to be the optimal one.
76 fn limit(&self) -> usize;
77}
78
79/// The default strategy for assigning costs to layouts.
80///
81/// This cost factory uses [`DefaultCost`] as its cost type. Its documentation
82/// has more information about the components that make up the cost.
83///
84/// The default cost factory has two configurable parameters:
85///
86/// * `page_width`: Required. The desired width of the document. Layouts that
87/// exceed this width will have a cost imposed based on how much they exceed
88/// it. The printer will choose a layout that fits within this width unless it
89/// is not possible to do so.
90/// * `computation_width`: Optional. A more extreme width constraint. Documents
91/// that extend beyond this width will be "tainted", and will not be explored
92/// further by the printer until it exhausts all untainted options. This
93/// constraint improves the performance of the printer. If not provided, it
94/// will default to `1.2 * page_width`.
95///
96/// This cost factory is used in the following situations:
97///
98/// * Calling `to_string()` on a [Doc](crate::Doc) will use this cost factory
99/// with a page width of 80.
100/// * Formatting a [Doc](crate::Doc) using one of [Rust's formatting
101/// macros](std::fmt#related-macros) will use this cost factory. If a width
102/// parameter is given to the macro, it will be used as the page width.
103/// Otherwise, the default 80 characters will be used
104/// * Calling [`Doc::validate`](crate::Doc::validate) will use this cost factory
105/// with the given page width.
106///
107/// None of these allow overriding the computation width,
108/// as it isn't usually necessary. If you want to use a
109/// custom computation width, you can use [`Self::new`] and
110/// [`Doc::validate_with_cost`](crate::Doc::validate_with_cost).
111#[derive(Debug)]
112pub struct DefaultCostFactory {
113 page_width: usize,
114 computation_width: Option<usize>,
115}
116
117/// The default cost type used for documents.
118///
119/// It is composed of two values: the badness and the number of newlines. The
120/// badness quantifies how much the layout overflows the desired page width.
121/// When this cost is used, the badness will be minimized first, then the number
122/// of newlines will be minimized from there.
123///
124/// This cost is generally used with the [`DefaultCostFactory`], but it is valid
125/// to use it with a custom cost factory if it serves your needs.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
127pub struct DefaultCost(
128 /// The badness of the layout.
129 pub i32,
130 /// The number of newlines the layout produced.
131 pub i32,
132);
133
134impl DefaultCostFactory {
135 /// Create a new cost factory with the given parameters.
136 pub fn new(page_width: usize, computation_width: Option<usize>) -> Self {
137 Self {
138 page_width,
139 computation_width,
140 }
141 }
142}
143
144impl CostFactory for DefaultCostFactory {
145 type CostType = DefaultCost;
146
147 /// Adds 1 to the [second component](DefaultCost::1) of the cost.
148 ///
149 /// The indentation level is ignored in this cost factory.
150 fn newline(&self, _i: usize) -> Self::CostType {
151 DefaultCost(0, 1)
152 }
153
154 /// Adds to the [first component](DefaultCost::0) of the cost if the page
155 /// width is exceeded.
156 ///
157 /// If the text will not exceed the page width, no cost is imposed.
158 fn text(&self, c: usize, l: usize) -> Self::CostType {
159 let stop = c + l;
160 if stop > self.page_width {
161 let maxwc = self.page_width.max(c) as i32;
162 let a = maxwc - (self.page_width as i32);
163 let b = (stop as i32) - maxwc;
164 DefaultCost(b * (2 * a + b), 0)
165 } else {
166 DefaultCost(0, 0)
167 }
168 }
169
170 fn limit(&self) -> usize {
171 match self.computation_width {
172 None => (1.2 * (self.page_width as f64)) as usize,
173 Some(cw) => cw,
174 }
175 }
176}
177
178impl Add for DefaultCost {
179 type Output = Self;
180
181 /// Pair-wise adds the individual components of the cost.
182 fn add(self, rhs: Self) -> Self::Output {
183 Self(self.0 + rhs.0, self.1 + rhs.1)
184 }
185}
186
187impl Cost for DefaultCost {}