use alloc::string::String;
use alloc::vec::Vec;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SerializerStyle<'a> {
pub line_break: &'a str,
pub indentation: &'a str,
}
pub struct Serializer<'a, 'b> {
style: SerializerStyle<'a>,
out: &'b mut String,
state: State,
}
enum State {
Beginning,
Writing(WritingState),
Finished,
}
struct WritingState {
stack: Vec<StackItem>,
list_beginning: bool,
current_list_line_broken: bool,
line_len: usize,
}
struct StackItem {
line_broken: bool,
}
impl<'a, 'b> Serializer<'a, 'b> {
pub fn new(style: SerializerStyle<'a>, out: &'b mut String) -> Self {
Self {
style,
out,
state: State::Beginning,
}
}
fn write_indent(indentation: &str, n: usize, out: &mut String) -> usize {
let prev_len = out.len();
for _ in 0..n {
out.push_str(indentation);
}
out.len() - prev_len
}
pub fn put_atom(&mut self, atom: &str, break_line_at: usize) {
assert!(crate::check_atom(atom), "invalid atom {:?}", atom);
match self.state {
State::Beginning => {
self.out.push_str(atom);
self.state = State::Finished;
}
State::Writing(ref mut state) => {
if state.line_len < break_line_at {
if !state.list_beginning {
self.out.push(' ');
state.line_len += 1;
}
self.out.push_str(atom);
state.line_len += atom.len();
} else {
self.out.push_str(self.style.line_break);
let indent_len =
Self::write_indent(self.style.indentation, state.stack.len() + 1, self.out);
self.out.push_str(atom);
state.current_list_line_broken = true;
state.line_len = indent_len + atom.len();
}
state.list_beginning = false;
}
State::Finished => panic!("writing already finished"),
}
}
#[allow(clippy::branches_sharing_code)]
pub fn begin_list(&mut self, break_line_at: usize) {
match self.state {
State::Beginning => {
self.out.push('(');
self.state = State::Writing(WritingState {
stack: Vec::new(),
list_beginning: true,
current_list_line_broken: false,
line_len: 1,
});
}
State::Writing(ref mut state) => {
if state.line_len < break_line_at {
if !state.list_beginning {
self.out.push(' ');
state.line_len += 1;
}
self.out.push('(');
state.line_len += 1;
} else {
self.out.push_str(self.style.line_break);
state.line_len =
Self::write_indent(self.style.indentation, state.stack.len() + 1, self.out);
self.out.push('(');
state.current_list_line_broken = true;
state.line_len += 1;
}
state.stack.push(StackItem {
line_broken: state.current_list_line_broken,
});
state.list_beginning = true;
state.current_list_line_broken = false;
}
State::Finished => panic!("writing already finished"),
}
}
pub fn end_list(&mut self) {
match self.state {
State::Beginning => panic!("no list to end"),
State::Writing(ref mut state) => {
if state.current_list_line_broken {
self.out.push_str(self.style.line_break);
state.line_len =
Self::write_indent(self.style.indentation, state.stack.len(), self.out);
}
self.out.push(')');
state.line_len += 1;
if let Some(previous) = state.stack.pop() {
state.current_list_line_broken |= previous.line_broken;
state.list_beginning = false;
} else {
self.state = State::Finished;
}
}
State::Finished => panic!("writing already finished"),
}
}
pub fn finish(self, insert_line_break: bool) {
match self.state {
State::Finished => {
if insert_line_break {
self.out.push_str(self.style.line_break);
}
}
_ => panic!("writing not finished yet"),
}
}
}