use super::block::Block;
use super::shapes::{Shape, ShapeCatalog};
use super::syntax_tree::{Entry, ListNode};
use crate::core::{DefinitionForm, ExpressionForm, KernelForm, Keyword};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Unit {
Single(usize),
Pair(usize, usize),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LaterIndent {
Past(usize),
UnderLastOnFirstLine,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Arrangement {
pub first_line_units: usize,
pub later_indent: LaterIndent,
}
pub struct ListPlan {
pub units: Vec<Unit>,
pub arrangements: Vec<Arrangement>,
pub docstring_entry: Option<usize>,
pub fillable_units: Vec<bool>,
pub binding_list_entry: Option<usize>,
preferred_arrangement: Option<Arrangement>,
one_line_allowed: bool,
first_arm_entry: Option<usize>,
}
impl Arrangement {
pub const BODY_INDENT: usize = 2;
const UNDER_HEAD_INDENT: usize = 1;
fn body() -> LaterIndent {
LaterIndent::Past(Self::BODY_INDENT)
}
fn aligned_after(first_line_units: usize) -> Self {
Self {
first_line_units,
later_indent: LaterIndent::UnderLastOnFirstLine,
}
}
}
impl ListPlan {
pub fn new(list: &ListNode, shapes: &ShapeCatalog) -> Self {
let head = list.head_name();
let shape = head.and_then(|name| shapes.of(name));
let kind = ListKind::of(head, shape);
let header_length = shape.map_or(0, |shape| shape.header_length);
let units = UnitGrouping::new(&list.entries, header_length).units();
let docstring_entry = Self::docstring_entry(list, head, shapes);
let fills_atom_runs = !shape.is_some_and(|shape| shape.one_per_line);
let body_forms = list.entries.len().saturating_sub(header_length + 1);
let definer = shape.is_some_and(|shape| shape.takes_docstring);
let preferred_arrangement = kind.trailing_fn_arrangement(list, &units);
Self {
fillable_units: units
.iter()
.map(|unit| fills_atom_runs && Self::is_fillable(list, *unit, docstring_entry))
.collect(),
units,
preferred_arrangement,
arrangements: kind.arrangements(),
docstring_entry,
binding_list_entry: Self::binding_list_entry(shape),
one_line_allowed: !definer || body_forms <= 1,
first_arm_entry: shape.filter(|shape| shape.arms).map(|_| header_length + 1),
}
}
pub fn binding_list(list: &ListNode) -> Self {
let units = BindingPairs::new(list.entries.len()).units();
Self {
fillable_units: vec![false; units.len()],
units,
arrangements: vec![Arrangement {
first_line_units: 1,
later_indent: LaterIndent::Past(Arrangement::UNDER_HEAD_INDENT),
}],
docstring_entry: None,
binding_list_entry: None,
preferred_arrangement: None,
one_line_allowed: list.entries.len() <= BindingPairs::PAIR_LENGTH,
first_arm_entry: None,
}
}
fn binding_list_entry(shape: Option<Shape>) -> Option<usize> {
shape
.filter(|shape| shape.bindings)
.map(|shape| shape.header_length)
}
pub fn is_arm(&self, entry: usize) -> bool {
self.first_arm_entry.is_some_and(|first| entry >= first)
}
pub fn preferred_arrangement(&self) -> Option<Arrangement> {
self.preferred_arrangement
}
pub fn allows_one_line(&self) -> bool {
self.one_line_allowed
}
pub fn arrangements_at(&self, column: usize) -> &[Arrangement] {
if column < Block::WIDTH {
&self.arrangements
} else {
&self.arrangements[self.arrangements.len() - 1..]
}
}
fn is_fillable(list: &ListNode, unit: Unit, docstring_entry: Option<usize>) -> bool {
let Unit::Single(entry) = unit else {
return false;
};
let single_line_atom = list.entries[entry]
.node
.as_atom()
.is_some_and(|atom| !atom.is_multi_line());
single_line_atom && docstring_entry != Some(entry)
}
fn docstring_entry(
list: &ListNode,
head: Option<&str>,
shapes: &ShapeCatalog,
) -> Option<usize> {
let position = shapes.docstring_position(head?)?;
let is_string = list.entries.get(position)?.node.as_atom()?.is_string();
let followed = list.entries.len() > position + 1;
(is_string && followed).then_some(position)
}
}
enum ListKind {
Data,
Use(Shape),
Export,
Shaped(Shape),
Call,
}
impl ListKind {
fn of(head: Option<&str>, shape: Option<Shape>) -> Self {
match (head, shape) {
(None, _) => Self::Data,
(Some(name), Some(shape)) if Self::is_kernel(name, DefinitionForm::Use) => {
Self::Use(shape)
}
(Some(name), None) if Self::is_kernel(name, DefinitionForm::Export) => Self::Export,
(Some(_), Some(shape)) => Self::Shaped(shape),
(Some(_), None) => Self::Call,
}
}
fn is_kernel(name: &str, form: DefinitionForm) -> bool {
name == KernelForm::Definition(form).name()
}
fn arrangements(&self) -> Vec<Arrangement> {
match self {
Self::Data => vec![Arrangement {
first_line_units: 1,
later_indent: LaterIndent::Past(Arrangement::UNDER_HEAD_INDENT),
}],
Self::Use(shape) => vec![Arrangement::aligned_after(shape.header_length + 2)],
Self::Export => vec![Arrangement::aligned_after(2)],
Self::Shaped(shape) => vec![Arrangement {
first_line_units: shape.header_length + 1,
later_indent: Arrangement::body(),
}],
Self::Call => vec![
Arrangement::aligned_after(2),
Arrangement {
first_line_units: 1,
later_indent: Arrangement::body(),
},
],
}
}
fn trailing_fn_arrangement(&self, list: &ListNode, units: &[Unit]) -> Option<Arrangement> {
let earlier_units = units.len().saturating_sub(1);
let has_earlier_argument = earlier_units > 1;
let ends_with_fn = matches!(units.last(), Some(Unit::Single(entry))
if Self::is_fn_literal(&list.entries[*entry]));
let is_call = matches!(self, Self::Call);
(is_call && has_earlier_argument && ends_with_fn).then_some(Arrangement {
first_line_units: earlier_units,
later_indent: Arrangement::body(),
})
}
fn is_fn_literal(entry: &Entry) -> bool {
let fn_name = KernelForm::Expression(ExpressionForm::Fn).name();
entry.node.as_list().and_then(ListNode::head_name) == Some(fn_name)
}
}
struct UnitGrouping<'e> {
entries: &'e [Entry],
single_count: usize,
}
impl<'e> UnitGrouping<'e> {
fn new(entries: &'e [Entry], header_length: usize) -> Self {
Self {
entries,
single_count: (header_length + 1).min(entries.len()),
}
}
fn units(&self) -> Vec<Unit> {
let mut units: Vec<Unit> = (0..self.single_count).map(Unit::Single).collect();
let mut index = self.single_count;
while index < self.entries.len() {
let unit = self.unit_at(index);
index += unit.entry_count();
units.push(unit);
}
units
}
fn unit_at(&self, index: usize) -> Unit {
if self.starts_pair(index) {
Unit::Pair(index, index + 1)
} else {
Unit::Single(index)
}
}
fn starts_pair(&self, index: usize) -> bool {
let entry = &self.entries[index];
let is_keyword = entry
.node
.symbol_name()
.is_some_and(|name| Keyword::field_name(name).is_some());
let value_follows = self
.entries
.get(index + 1)
.is_some_and(|value| !value.is_interrupted());
is_keyword && entry.trailing_comment.is_none() && value_follows
}
}
struct BindingPairs {
entry_count: usize,
}
impl BindingPairs {
const PAIR_LENGTH: usize = 2;
fn new(entry_count: usize) -> Self {
Self { entry_count }
}
fn units(&self) -> Vec<Unit> {
(0..self.entry_count)
.step_by(Self::PAIR_LENGTH)
.map(|name| self.unit_at(name))
.collect()
}
fn unit_at(&self, name: usize) -> Unit {
let value = name + 1;
if value < self.entry_count {
Unit::Pair(name, value)
} else {
Unit::Single(name)
}
}
}
impl Unit {
pub fn first_entry(self) -> usize {
match self {
Unit::Single(entry) | Unit::Pair(entry, _) => entry,
}
}
pub fn last_entry(self) -> usize {
match self {
Unit::Single(entry) | Unit::Pair(_, entry) => entry,
}
}
pub fn entries(self) -> impl Iterator<Item = usize> {
self.first_entry()..=self.last_entry()
}
fn entry_count(self) -> usize {
self.last_entry() - self.first_entry() + 1
}
}