1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! A Wadler/Prettier-style document IR. Rules build a tree of these primitives;
//! the [`printer`](crate::formatter::printer) makes all line-break decisions by
//! choosing flat or broken layout per [`Group`](Ir::Group).
use std::rc::Rc;
#[derive(Debug, Clone)]
pub enum Ir {
/// Literal text. Must not contain newlines (use [`Ir::HardLine`]).
Text(Rc<str>),
/// A sequence of documents laid out one after another.
Concat(Rc<[Ir]>),
/// A space when its group is flat, a newline (+ indent) when broken.
Line,
/// Nothing when its group is flat, a newline (+ indent) when broken.
SoftLine,
/// Always a newline (+ indent); forces every enclosing group to break.
HardLine,
/// A bare newline at column zero (no indent); forces every enclosing group
/// to break. Used to emit a *blank* line between elements of an already-broken
/// layout, where a [`HardLine`](Ir::HardLine) would leave the indent as trailing
/// whitespace on the otherwise-empty line.
BlankLine,
/// Increase the indent of the contained document by one step.
Indent(Rc<Ir>),
/// A group laid out flat if it fits the line width, otherwise broken.
Group(Rc<Ir>),
/// Conditional text: the first string when the enclosing group is broken,
/// the second when it is flat. The canonical use is a trailing separator that
/// only appears in the broken layout, e.g. `IfBreak(",", "")`. Measured as its
/// *flat* string when deciding whether a group fits.
IfBreak(Rc<str>, Rc<str>),
/// A trailing-argument hug with an explode fallback. Renders as
/// `prefix body close` — the hug layout: `prefix` (the open bracket plus the
/// leading arguments, flat) rides the current line and `body` (the hugged
/// last argument) breaks in place via its own group — when the hug layout's
/// *first line* fits: `prefix` measured strictly flat, then `body` up to its
/// first break opportunity. Otherwise renders `explode`, the standard
/// width-driven one-item-per-line group over all items, which is guaranteed
/// to break whenever the hug is rejected (the hug measure never exceeds the
/// flat measure).
HugGroup {
prefix: Rc<Ir>,
body: Rc<Ir>,
close: Rc<Ir>,
explode: Rc<Ir>,
},
/// A two-layout choice decided not by the current column but by whether a
/// later, re-indented line fits. `primary` is laid out when `probe` — measured
/// flat from the group's *base indent* — fits the width; otherwise `fallback`.
///
/// The canonical use is a `where` clause `f(args) where {T, S}` whose deciding
/// line is the *closing* line `) where {T, S}` (after the argument list breaks),
/// not the opening one. `primary` keeps the bound flat so the argument list
/// breaks around it; `fallback` keeps the arguments flat and explodes the bound.
/// `probe` is the closing line's fixed prefix plus the flat bound, so it fits
/// exactly when breaking the arguments would let the bound sit flat.
CondGroup {
primary: Rc<Ir>,
fallback: Rc<Ir>,
probe: Rc<Ir>,
},
}
impl Ir {
pub fn text(s: impl Into<Rc<str>>) -> Ir {
Ir::Text(s.into())
}
pub fn concat(items: impl IntoIterator<Item = Ir>) -> Ir {
Ir::Concat(items.into_iter().collect())
}
pub fn group(inner: Ir) -> Ir {
Ir::Group(Rc::new(inner))
}
pub fn indent(inner: Ir) -> Ir {
Ir::Indent(Rc::new(inner))
}
pub fn if_break(broken: impl Into<Rc<str>>, flat: impl Into<Rc<str>>) -> Ir {
Ir::IfBreak(broken.into(), flat.into())
}
pub fn hug_group(prefix: Ir, body: Ir, close: Ir, explode: Ir) -> Ir {
Ir::HugGroup {
prefix: Rc::new(prefix),
body: Rc::new(body),
close: Rc::new(close),
explode: Rc::new(explode),
}
}
pub fn cond_group(primary: Ir, fallback: Ir, probe: Ir) -> Ir {
Ir::CondGroup {
primary: Rc::new(primary),
fallback: Rc::new(fallback),
probe: Rc::new(probe),
}
}
}