use crate::clojure::result_ir::{MapEntry, Value};
use super::{
layout_solver::{Program, ProgramBuilder, TextBuilder},
style::Style,
};
use Style as S;
use TextBuilder as TB;
use Value as V;
pub fn convert_to_layout_program<'a>(value: &Value<'a>) -> Program<'a> {
let mut builder = ProgramBuilder::new();
program_value(&mut builder, value);
builder.build()
}
fn program_value<'a>(builder: &mut ProgramBuilder<'a>, value: &Value<'a>) {
match value {
V::Nil => {
builder.add_text(TB::new().add("nil", S::NilValue));
}
V::Boolean { value } => {
builder.add_text(
TB::new().add(if *value { "true" } else { "false" }, S::BooleanValue),
);
}
V::Number { literal } => {
builder.add_text(TB::new().add(*literal, S::NumberValue));
}
V::String { literal } => {
builder.add_text(TB::new().add("\"", S::StringDecoration));
builder.add_text(
TB::new()
.add(&literal[1..literal.len() - 1], S::StringValue),
);
builder.add_text(TB::new().add("\"", S::StringDecoration));
}
V::SymbolicValue { literal } => {
builder.add_text(TB::new().add("##", S::SymbolicValueDecoration));
builder.add_text(TB::new().add(*literal, S::SymbolicValue));
}
V::Symbol { namespace, name } => {
builder.add_text(
TB::new()
.apply(|b| {
if let Some(n) = namespace {
b.add(*n, S::SymbolNamespace).add("/", S::SymbolDecoration)
} else {
b
}
})
.add(*name, S::SymbolName),
);
}
V::Keyword {
namespace,
name,
alias,
} => builder.add_text(
TB::new()
.add(if *alias { "::" } else { ":" }, S::KeywordDecoration)
.apply(|b| {
if let Some(n) = namespace {
b.add(*n, S::KeywordNamespace)
.add("/", S::KeywordDecoration)
} else {
b
}
})
.add(*name, S::KeywordName),
),
V::VarQuoted { namespace, name } => builder.add_text(
TB::new()
.add("#'", S::VarQuoteDecoration)
.apply(|b| {
if let Some(n) = namespace {
b.add(*n, S::VarQuoteNamespace)
.add("/", S::VarQuoteDecoration)
} else {
b
}
})
.add(*name, S::VarQuoteName),
),
V::TaggedLiteral {
namespace,
name,
value,
} => {
builder.add_text(
TB::new()
.add("#", S::TaggedLiteralDecoration)
.apply(|b| {
if let Some(s) = namespace {
b.add(*s, S::TaggedLiteralNamespace)
.add("/", S::TaggedLiteralDecoration)
} else {
b
}
})
.add(*name, S::TaggedLiteralName),
);
builder.add_soft_space();
program_value(builder, value);
}
V::List { values } => program_seq(builder, values, true, "(", ")"),
V::Vector { values } => program_seq(builder, values, false, "[", "]"),
V::Set { values } => program_seq(builder, values, false, "#{", "}"),
V::Map { entries } => program_map(builder, entries),
}
}
fn program_seq<'a>(
builder: &mut ProgramBuilder<'a>,
values: &[Value<'a>],
anchor_after_first: bool,
opening_delim: &'static str,
closing_delim: &'static str,
) {
let only_simple_values = values.iter().all(|v| {
!matches!(
v,
Value::List { .. }
| Value::Vector { .. }
| Value::Set { .. }
| Value::Map { .. }
)
});
builder.add_text(TB::new().add(opening_delim, S::CollectionDelimiter));
let mut it = values.iter();
if anchor_after_first {
if let Some(first) = it.next() {
program_value(builder, first);
builder.add_soft_space();
}
}
if let Some(first) = it.next() {
let anchor = builder.set_anchor();
program_value(builder, first);
for value in it {
if only_simple_values {
builder.add_soft_space();
} else {
builder.break_hard(anchor);
}
program_value(builder, value);
}
}
builder.add_text(TB::new().add(closing_delim, S::CollectionDelimiter));
}
fn program_map<'a>(builder: &mut ProgramBuilder<'a>, entries: &[MapEntry<'a>]) {
builder.add_text(TB::new().add("{", S::CollectionDelimiter));
let key_width = entries
.iter()
.fold(Some(0), |acc, e| Some(acc?.max(rigid_width(&e.key)?)));
let mut it = entries.iter();
if let Some(first) = it.next() {
let anchor = builder.set_anchor();
if let Some(key_width) = key_width {
let value_anchor = builder.set_relative_anchor(
anchor,
1 + i16::try_from(key_width).expect("too large keys"),
);
program_value(builder, &first.key);
builder.jump_to(value_anchor);
program_value(builder, &first.value);
for entry in it {
builder.break_hard(anchor);
program_value(builder, &entry.key);
builder.jump_to(value_anchor);
program_value(builder, &entry.value);
}
} else {
program_value(builder, &first.key);
builder.add_soft_space();
program_value(builder, &first.value);
for entry in it {
builder.break_hard(anchor);
program_value(builder, &entry.key);
builder.add_soft_space();
program_value(builder, &entry.value);
}
}
}
builder.add_text(TB::new().add("}", S::CollectionDelimiter));
}
fn rigid_width(value: &Value) -> Option<usize> {
match value {
V::Nil => Some(ascii_len("nil")),
V::Number { literal } => Some(literal.len()),
V::String { literal } => Some(literal.len()),
V::Boolean { value } => Some(if *value {
ascii_len("true")
} else {
ascii_len("false")
}),
V::SymbolicValue { literal } => Some(literal.len() + 2),
V::TaggedLiteral { .. } => None,
V::Keyword {
namespace,
name,
alias,
} => Some(
if *alias { 2 } else { 1 }
+ namespace.map(|s| s.len() + 1).unwrap_or(0)
+ name.len(),
),
V::Symbol { namespace, name } => Some(if let Some(n) = namespace {
n.len() + 1 + name.len()
} else {
name.len()
}),
_ => None,
}
}
const fn ascii_len(s: &str) -> usize {
let mut i = 0;
let b = s.as_bytes();
let n = s.len();
while i < n {
if b[i] > 0x7F {
panic!("non-ascii str");
}
i += 1;
}
s.len()
}