#![forbid(unsafe_code, unconditional_recursion)]
#![no_std]
#![recursion_limit = "8"]
extern crate alloc;
extern crate core;
use alloc::{borrow, string::String, vec, vec::Vec};
#[cfg(test)]
mod tests;
pub mod indention;
use indention::{Indented, IntoUnindented};
pub mod view;
pub use view::View;
#[derive(Clone, Debug)]
#[cfg_attr(any(feature = "extra-traits", test), derive(PartialEq))]
pub struct Block<S> {
pub head: S,
pub subs: Vec<Block<S>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "extra-traits", derive(PartialOrd, Ord, Hash))]
pub enum EmptyLinesMode {
Skip,
HonorIndent,
PrevIndent,
}
impl<A> Block<A> {
pub fn map_ref<B, F>(&self, mut f: F) -> Block<B>
where
F: FnMut(&A) -> B,
{
fn intern_<A, B, F: FnMut(&A) -> B>(this: &Block<A>, f: &mut F) -> Block<B> {
let head = f(&this.head);
Block {
head,
subs: this.subs.iter().map(move |i| intern_(i, f)).collect(),
}
}
intern_(self, &mut f)
}
pub fn map<B, F>(self, mut f: F) -> Block<B>
where
F: FnMut(A) -> B,
{
fn intern_<A, B, F: FnMut(A) -> B>(this: Block<A>, f: &mut F) -> Block<B> {
let Block { head, subs } = this;
let head = f(head);
Block {
head,
subs: subs.into_iter().map(move |i| intern_(i, f)).collect(),
}
}
intern_(self, &mut f)
}
}
impl<'a, T: borrow::ToOwned> Block<&'a T> {
#[inline]
pub fn to_owned(&self) -> Block<T::Owned> {
self.map_ref(|&i| i.to_owned())
}
}
pub fn parse_nested_blocks(
s: &str,
empty_lines_mode: EmptyLinesMode,
) -> Vec<Block<Indented<&str>>> {
parse_nested_blocks_from_lines(s.lines(), empty_lines_mode)
}
pub fn parse_nested_blocks_from_lines<'a, LI>(
lines: LI,
empty_lines_mode: EmptyLinesMode,
) -> Vec<Block<Indented<&'a str>>>
where
LI: Iterator<Item = &'a str>,
{
let mut base = vec![];
let mut stack: Vec<Block<Indented<&str>>> = vec![];
let mut lit = lines.map(|i| Block {
head: Indented::new(i),
subs: Vec::new(),
});
loop {
let i = lit.next();
let i_indent = if let Some(j) = &i {
match empty_lines_mode {
_ if !j.head.data.is_empty() => Some(j),
EmptyLinesMode::HonorIndent => Some(j),
EmptyLinesMode::PrevIndent => stack.last(),
EmptyLinesMode::Skip => continue,
}
.map(|x| x.head.indent)
} else {
None
}
.unwrap_or("");
while let Some(old_top) = stack.pop() {
let old_top_indent = old_top.head.indent;
if i_indent.starts_with(old_top_indent) && i_indent.len() != old_top_indent.len() {
stack.push(old_top);
break;
} else {
stack
.last_mut()
.map(|top2| &mut top2.subs)
.unwrap_or(&mut base)
.push(old_top);
}
}
assert!(i_indent.starts_with(stack.last().map(|top| top.head.indent).unwrap_or("")));
match i {
Some(j) => stack.push(j),
None => break,
}
}
assert!(stack.is_empty());
base
}
pub fn blocks_to_string<'ast, BIter, B, S, F>(blks: BIter, single_indent: &str) -> String
where
BIter: core::iter::IntoIterator<Item = B>,
B: IntoUnindented<Output = view::View<'ast, S, F>> + 'ast,
S: 'ast,
F: Clone + view::ViewFn<&'ast S>,
F::Output: alloc::string::ToString,
{
blks.into_iter()
.map(IntoUnindented::into_unindented)
.fold(String::new(), |mut acc, x| {
x.append_to_string(&mut acc, single_indent, 0);
acc
})
}