use core::mem;
use alloc::string::String;
use crate::fmt;
use crate::fmt::config::{Config, Indentation};
use crate::fmt::cursor;
use crate::lang::Lang;
use crate::tokens::{Item, Kind};
static SPACES: &str = " ";
static TABS: &str =
"\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
#[derive(Debug, Clone, Copy)]
enum Whitespace {
Initial,
None,
Push,
Line,
}
impl Whitespace {
fn into_indent(self) -> Option<usize> {
match self {
Self::Initial => Some(0),
Self::Push => Some(1),
Self::Line => Some(2),
Self::None => None,
}
}
}
impl Default for Whitespace {
fn default() -> Self {
Self::None
}
}
pub struct Formatter<'a> {
write: &'a mut (dyn fmt::Write + 'a),
config: &'a Config,
line: Whitespace,
spaces: usize,
indent: i16,
}
impl<'a> Formatter<'a> {
pub(crate) fn new(write: &'a mut (dyn fmt::Write + 'a), config: &'a Config) -> Formatter<'a> {
Formatter {
write,
line: Whitespace::Initial,
spaces: 0usize,
indent: 0i16,
config,
}
}
pub(crate) fn format_items<L>(
&mut self,
items: &[(usize, Item<L>)],
config: &L::Config,
format: &L::Format,
) -> fmt::Result<()>
where
L: Lang,
{
let mut cursor = cursor::Cursor::new(items);
self.format_cursor(&mut cursor, config, format, false)
}
pub(crate) fn write_trailing_line(&mut self) -> fmt::Result {
self.line = Whitespace::default();
self.spaces = 0;
self.write.write_trailing_line(self.config)?;
Ok(())
}
fn write_str(&mut self, s: &str) -> fmt::Result {
if !s.is_empty() {
self.flush_whitespace()?;
self.write.write_str(s)?;
}
Ok(())
}
fn push(&mut self) {
self.line = match self.line {
Whitespace::Initial => return,
Whitespace::Line => return,
_ => Whitespace::Push,
};
self.spaces = 0;
}
fn line(&mut self) {
self.line = match self.line {
Whitespace::Initial => return,
_ => Whitespace::Line,
};
self.spaces = 0;
}
fn space(&mut self) {
self.spaces += 1;
}
fn indentation(&mut self, n: i16) {
self.push();
self.indent += n;
}
fn format_cursor<L>(
&mut self,
cursor: &mut cursor::Cursor<'_, L>,
config: &L::Config,
format: &L::Format,
end_on_close_quote: bool,
) -> fmt::Result
where
L: Lang,
{
use crate::lang::LangItem as _;
let mut buf = String::new();
let mut stack = smallvec::SmallVec::<[Frame; 4]>::new();
stack.push(Frame::default());
while let (Some(item), Some(head)) = (cursor.next(), stack.last_mut()) {
let Frame {
in_quote,
has_eval,
end_on_eval,
} = head;
match &item.kind {
Kind::Register(..) => (),
Kind::Indentation(0) => (),
Kind::Literal(literal) => {
if *in_quote {
L::write_quoted(self, literal)?;
} else {
self.write_str(literal)?;
}
}
Kind::OpenQuote(e) if !*in_quote => {
*has_eval = *e;
*in_quote = true;
L::open_quote(self, config, format, *has_eval)?;
}
Kind::OpenQuote(false) if *in_quote => {
self.quoted_quote(cursor, &mut buf, config, format)?;
L::write_quoted(self, &buf)?;
buf.clear();
}
Kind::CloseQuote if end_on_close_quote => {
return Ok(());
}
Kind::CloseQuote if *in_quote => {
*in_quote = false;
L::close_quote(self, config, format, mem::take(has_eval))?;
}
Kind::Lang(lang) => {
lang.format(self, config, format)?;
}
Kind::Push => {
self.push();
}
Kind::Line => {
self.line();
}
Kind::Space => {
self.space();
}
Kind::Indentation(n) => {
self.indentation(*n);
}
Kind::OpenEval if *in_quote => {
if cursor.peek::<cursor::Literal>() && cursor.peek1::<cursor::CloseEval>() {
let literal = cursor.parse::<cursor::Literal>()?;
L::string_eval_literal(self, config, format, literal)?;
cursor.parse::<cursor::CloseEval>()?;
} else {
L::start_string_eval(self, config, format)?;
stack.push(Frame {
in_quote: false,
has_eval: false,
end_on_eval: true,
});
}
}
Kind::CloseEval if *end_on_eval => {
L::end_string_eval(self, config, format)?;
stack.pop();
}
_ => {
return Err(core::fmt::Error);
}
}
}
return Ok(());
#[derive(Default, Clone)]
struct Frame {
in_quote: bool,
has_eval: bool,
end_on_eval: bool,
}
}
fn quoted_quote<L>(
&mut self,
cursor: &mut cursor::Cursor<'_, L>,
buf: &mut String,
config: &L::Config,
format: &L::Format,
) -> fmt::Result<()>
where
L: Lang,
{
use crate::fmt::FmtWriter;
let mut w = FmtWriter::new(buf);
let out = &mut Formatter::new(&mut w, self.config);
L::open_quote(out, config, format, false)?;
out.format_cursor(cursor, config, format, true)?;
L::close_quote(out, config, format, false)?;
Ok(())
}
fn flush_whitespace(&mut self) -> fmt::Result {
let mut spaces = mem::take(&mut self.spaces);
if let Some(lines) = mem::take(&mut self.line).into_indent() {
for _ in 0..lines {
self.write.write_line(self.config)?;
}
let level = i16::max(self.indent, 0) as usize;
match self.config.indentation {
Indentation::Space(n) => {
spaces += level * n;
}
Indentation::Tab => {
let mut tabs = level;
while tabs > 0 {
let len = usize::min(tabs, TABS.len());
self.write.write_str(&TABS[0..len])?;
tabs -= len;
}
}
}
}
while spaces > 0 {
let len = usize::min(spaces, SPACES.len());
self.write.write_str(&SPACES[0..len])?;
spaces -= len;
}
Ok(())
}
}
impl core::fmt::Write for Formatter<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
if !s.is_empty() {
Formatter::write_str(self, s)?;
}
Ok(())
}
}
impl core::fmt::Debug for Formatter<'_> {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
fmt.debug_struct("Formatter")
.field("line", &self.line)
.field("spaces", &self.spaces)
.field("indent", &self.indent)
.field("config", self.config)
.finish()
}
}