#![allow(dead_code)]
use std::rc::Rc;
#[derive(Debug, Clone)]
pub(crate) enum Ir {
Text(Rc<str>),
Concat(Rc<[Ir]>),
Line,
SoftLine,
HardLine,
EmptyLine,
Indent(Rc<Ir>),
Group {
inner: Rc<Ir>,
expand: bool,
hug: bool,
hug_excuse_overflow: bool,
},
IfBreak { flat: Rc<Ir>, broken: Rc<Ir> },
Verbatim { text: Rc<str>, force_break: bool },
ConditionalGroup(Rc<[Ir]>),
ConditionalGroupAllLines(Rc<[Ir]>),
Nil,
}
impl Ir {
pub(crate) fn text(s: impl Into<Rc<str>>) -> Ir {
Ir::Text(s.into())
}
pub(crate) fn concat(items: impl IntoIterator<Item = Ir>) -> Ir {
let items: Vec<Ir> = items
.into_iter()
.filter(|i| !matches!(i, Ir::Nil))
.collect();
match items.len() {
0 => Ir::Nil,
1 => items.into_iter().next().unwrap(),
_ => Ir::Concat(items.into()),
}
}
pub(crate) fn join(sep: Ir, items: impl IntoIterator<Item = Ir>) -> Ir {
let mut out = Vec::new();
for (i, item) in items.into_iter().enumerate() {
if i > 0 {
out.push(sep.clone());
}
out.push(item);
}
Ir::concat(out)
}
pub(crate) fn group(inner: Ir) -> Ir {
Ir::Group {
inner: Rc::new(inner),
expand: false,
hug: false,
hug_excuse_overflow: false,
}
}
pub(crate) fn group_expanded(inner: Ir) -> Ir {
Ir::Group {
inner: Rc::new(inner),
expand: true,
hug: false,
hug_excuse_overflow: false,
}
}
pub(crate) fn group_hug(inner: Ir) -> Ir {
Ir::Group {
inner: Rc::new(inner),
expand: false,
hug: true,
hug_excuse_overflow: false,
}
}
pub(crate) fn group_hug_excused(inner: Ir) -> Ir {
Ir::Group {
inner: Rc::new(inner),
expand: false,
hug: true,
hug_excuse_overflow: true,
}
}
pub(crate) fn conditional_group(candidates: impl IntoIterator<Item = Ir>) -> Ir {
let cands: Vec<Ir> = candidates.into_iter().collect();
assert!(
!cands.is_empty(),
"Ir::conditional_group requires at least one candidate"
);
Ir::ConditionalGroup(cands.into())
}
pub(crate) fn conditional_group_all_lines(candidates: impl IntoIterator<Item = Ir>) -> Ir {
let cands: Vec<Ir> = candidates.into_iter().collect();
assert!(
!cands.is_empty(),
"Ir::conditional_group_all_lines requires at least one candidate"
);
Ir::ConditionalGroupAllLines(cands.into())
}
pub(crate) fn indent(inner: Ir) -> Ir {
Ir::Indent(Rc::new(inner))
}
pub(crate) fn if_break(flat: Ir, broken: Ir) -> Ir {
Ir::IfBreak {
flat: Rc::new(flat),
broken: Rc::new(broken),
}
}
pub(crate) fn verbatim(s: impl Into<Rc<str>>) -> Ir {
let text: Rc<str> = s.into();
let force_break = text.contains('\n');
Ir::Verbatim { text, force_break }
}
pub(crate) fn verbatim_forced(s: impl Into<Rc<str>>) -> Ir {
Ir::Verbatim {
text: s.into(),
force_break: true,
}
}
pub(crate) fn line() -> Ir {
Ir::Line
}
pub(crate) fn soft_line() -> Ir {
Ir::SoftLine
}
pub(crate) fn contains_group(&self) -> bool {
match self {
Ir::Group { .. } | Ir::ConditionalGroup(_) | Ir::ConditionalGroupAllLines(_) => true,
Ir::Concat(items) => items.iter().any(Ir::contains_group),
Ir::Indent(inner) => inner.contains_group(),
Ir::IfBreak { flat, broken } => flat.contains_group() || broken.contains_group(),
Ir::Text(_)
| Ir::Verbatim { .. }
| Ir::HardLine
| Ir::EmptyLine
| Ir::Line
| Ir::SoftLine
| Ir::Nil => false,
}
}
pub(crate) fn hard_line() -> Ir {
Ir::HardLine
}
pub(crate) fn empty_line() -> Ir {
Ir::EmptyLine
}
pub(crate) fn nil() -> Ir {
Ir::Nil
}
pub(crate) fn contains_forced_break(&self) -> bool {
match self {
Ir::HardLine | Ir::EmptyLine => true,
Ir::Verbatim { force_break, .. } => *force_break,
Ir::Concat(items) => items.iter().any(Ir::contains_forced_break),
Ir::Indent(inner) => inner.contains_forced_break(),
Ir::Group { inner, expand, .. } => *expand || inner.contains_forced_break(),
Ir::ConditionalGroup(cands) | Ir::ConditionalGroupAllLines(cands) => {
cands.first().is_some_and(Ir::contains_forced_break)
}
Ir::Text(_) | Ir::Line | Ir::SoftLine | Ir::IfBreak { .. } | Ir::Nil => false,
}
}
}