1#![forbid(unsafe_code, unconditional_recursion)]
2#![no_std]
3#![recursion_limit = "8"]
4
5extern crate alloc;
6extern crate core;
7
8use alloc::{borrow, string::String, vec, vec::Vec};
9
10#[cfg(test)]
11mod tests;
12
13pub mod indention;
14use indention::{Indented, IntoUnindented};
15
16pub mod view;
17pub use view::View;
18
19#[derive(Clone, Debug)]
20#[cfg_attr(any(feature = "extra-traits", test), derive(PartialEq))]
21pub struct Block<S> {
22 pub head: S,
23 pub subs: Vec<Block<S>>,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27#[cfg_attr(feature = "extra-traits", derive(PartialOrd, Ord, Hash))]
28pub enum EmptyLinesMode {
29 Skip,
30 HonorIndent,
31 PrevIndent,
32}
33
34impl<A> Block<A> {
35 pub fn map_ref<B, F>(&self, mut f: F) -> Block<B>
36 where
37 F: FnMut(&A) -> B,
38 {
39 fn intern_<A, B, F: FnMut(&A) -> B>(this: &Block<A>, f: &mut F) -> Block<B> {
40 let head = f(&this.head);
41 Block {
42 head,
43 subs: this.subs.iter().map(move |i| intern_(i, f)).collect(),
44 }
45 }
46 intern_(self, &mut f)
47 }
48
49 pub fn map<B, F>(self, mut f: F) -> Block<B>
50 where
51 F: FnMut(A) -> B,
52 {
53 fn intern_<A, B, F: FnMut(A) -> B>(this: Block<A>, f: &mut F) -> Block<B> {
54 let Block { head, subs } = this;
55 let head = f(head);
56 Block {
57 head,
58 subs: subs.into_iter().map(move |i| intern_(i, f)).collect(),
59 }
60 }
61 intern_(self, &mut f)
62 }
63}
64
65impl<'a, T: borrow::ToOwned> Block<&'a T> {
66 #[inline]
67 pub fn to_owned(&self) -> Block<T::Owned> {
68 self.map_ref(|&i| i.to_owned())
69 }
70}
71
72pub fn parse_nested_blocks(
74 s: &str,
75 empty_lines_mode: EmptyLinesMode,
76) -> Vec<Block<Indented<&str>>> {
77 parse_nested_blocks_from_lines(s.lines(), empty_lines_mode)
78}
79
80pub fn parse_nested_blocks_from_lines<'a, LI>(
88 lines: LI,
89 empty_lines_mode: EmptyLinesMode,
90) -> Vec<Block<Indented<&'a str>>>
91where
92 LI: Iterator<Item = &'a str>,
93{
94 let mut base = vec![];
95 let mut stack: Vec<Block<Indented<&str>>> = vec![];
96
97 let mut lit = lines.map(|i| Block {
98 head: Indented::new(i),
99 subs: Vec::new(),
100 });
101
102 loop {
103 let i = lit.next();
104
105 let i_indent = if let Some(j) = &i {
106 match empty_lines_mode {
107 _ if !j.head.data.is_empty() => Some(j),
108 EmptyLinesMode::HonorIndent => Some(j),
109 EmptyLinesMode::PrevIndent => stack.last(),
110 EmptyLinesMode::Skip => continue,
111 }
112 .map(|x| x.head.indent)
113 } else {
114 None
115 }
116 .unwrap_or("");
117
118 while let Some(old_top) = stack.pop() {
120 let old_top_indent = old_top.head.indent;
121 if i_indent.starts_with(old_top_indent) && i_indent.len() != old_top_indent.len() {
122 stack.push(old_top);
123 break;
124 } else {
125 stack
127 .last_mut()
128 .map(|top2| &mut top2.subs)
129 .unwrap_or(&mut base)
130 .push(old_top);
131 }
132 }
133
134 assert!(i_indent.starts_with(stack.last().map(|top| top.head.indent).unwrap_or("")));
135 match i {
136 Some(j) => stack.push(j),
137 None => break,
138 }
139 }
140
141 assert!(stack.is_empty());
142 base
143}
144
145pub fn blocks_to_string<'ast, BIter, B, S, F>(blks: BIter, single_indent: &str) -> String
146where
147 BIter: core::iter::IntoIterator<Item = B>,
148 B: IntoUnindented<Output = view::View<'ast, S, F>> + 'ast,
149 S: 'ast,
150 F: Clone + view::ViewFn<&'ast S>,
151 F::Output: alloc::string::ToString,
152{
153 blks.into_iter()
154 .map(IntoUnindented::into_unindented)
155 .fold(String::new(), |mut acc, x| {
156 x.append_to_string(&mut acc, single_indent, 0);
157 acc
158 })
159}