use super::*;
const NULL_TEXT: &str = "()";
const INDENT_WIDTH: i32 = 2;
const MARGIN_WIDTH: i32 = 80;
enum PrintPlan {
Null,
Atom(i32),
List(i32, Vec<PrintPlan>, ListPrintPlan),
}
enum ListPrintPlan {
Monoline,
Multiline,
}
impl PrintPlan {
fn width(&self) -> i32 {
match self {
PrintPlan::Null => NULL_TEXT.len().try_into().unwrap(),
PrintPlan::Atom(w) => *w,
PrintPlan::List(w, _, _) => *w,
}
}
}
pub fn print_sexp(sexp_vec: Vec<SExp>) {
for sexp in sexp_vec.into_iter() {
let print_plan = plan(&sexp, MARGIN_WIDTH);
print_impl(sexp, print_plan, 0);
}
}
fn plan(sexp: &SExp, available_width: i32) -> PrintPlan {
match sexp {
SExp::Null(_) => PrintPlan::Null,
SExp::Atom(v) => {
PrintPlan::Atom(v.len().try_into().unwrap())
}
SExp::List(es, _) => {
let elem_plans: Vec<PrintPlan> = es.iter().map(|x| plan(x, 0)).collect();
let monoline_width =
1 + elem_plans.iter().map(|x| x.width()).sum::<i32>() + ((es.len() - 1) as i32) + 1;
if available_width == 0 || monoline_width <= available_width {
PrintPlan::List(monoline_width, elem_plans, ListPrintPlan::Monoline)
} else {
let ml_elem_plans: Vec<PrintPlan> = es
.iter()
.map(|x| plan(x, available_width - INDENT_WIDTH))
.collect();
let width = INDENT_WIDTH + ml_elem_plans.iter().map(|x| x.width()).max().unwrap() + 1;
PrintPlan::List(width, ml_elem_plans, ListPrintPlan::Multiline)
}
}
}
}
fn print_impl(sexp: SExp, plan: PrintPlan, indent: i32) {
match (sexp, plan) {
(SExp::Null(bookend_style), PrintPlan::Null) => {
print!(
"{}",
match bookend_style {
SExpBookendStyle::Parentheses => "()",
SExpBookendStyle::CurlyBraces => "{}",
SExpBookendStyle::SquareBrackets => "[]",
}
);
}
(SExp::Atom(s), PrintPlan::Atom(_)) => {
print!("{}", s)
}
(SExp::List(es, bookend_style), PrintPlan::List(_, es_pps, linebreak)) => {
let es_len = es.len();
let insert_padding_space = if let PrintPlan::List(_, _, ListPrintPlan::Multiline) = es_pps[0]
{
true
} else {
false
};
let (open_token, close_token) = match bookend_style {
SExpBookendStyle::Parentheses => ('(', ')'),
SExpBookendStyle::CurlyBraces => ('{', '}'),
SExpBookendStyle::SquareBrackets => ('[', ']'),
};
print!("{}", open_token);
if insert_padding_space {
print!(" ");
}
match linebreak {
ListPrintPlan::Monoline => {
for (i, (e, pp)) in (0..es.len()).zip(es.into_iter().zip(es_pps.into_iter())) {
print_impl(e, pp, indent);
if i < es_len - 1 {
print!(" ")
}
}
}
ListPrintPlan::Multiline => {
for (i, (e, pp)) in (0..es.len()).zip(es.into_iter().zip(es_pps.into_iter())) {
print_impl(e, pp, indent + INDENT_WIDTH);
if i < es_len - 1 {
println!();
for _ in 0..(indent + INDENT_WIDTH) {
print!(" ");
}
}
}
}
}
if insert_padding_space {
print!(" ");
}
print!("{}", close_token);
}
_ => panic!("sexp-plan mismatch"),
}
}