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 {}