use core::mem::take;
use crate::alloc::prelude::*;
use crate::alloc::{self, VecDeque};
use crate::ast::{Kind, Span};
use crate::compile::{Error, ErrorKind, FmtOptions, Result, WithSpan};
use crate::grammar::{Ignore, Node, Tree};
use crate::{Diagnostics, SourceId};
use super::{INDENT, NL, NL_CHAR, WS};
pub(super) enum Comments {
Line,
Prefix,
Suffix,
Infix,
}
#[derive(Clone, Copy, Debug)]
struct Comment {
span: Span,
before: usize,
line: bool,
}
#[repr(transparent)]
pub(super) struct Source(str);
impl Source {
fn new(source: &str) -> &Self {
unsafe { &*(source as *const str as *const Self) }
}
pub(super) fn get(&self, span: Span) -> Result<&str> {
let Some(source) = self.0.get(span.range()) else {
return Err(Error::new(span, ErrorKind::BadSpan { len: self.0.len() }));
};
Ok(source)
}
pub(super) fn is_at_least(&self, span: Span, mut count: usize) -> Result<bool> {
let source = self.get(span)?;
for c in source.chars() {
if c.is_whitespace() {
continue;
}
let Some(c) = count.checked_sub(1) else {
return Ok(true);
};
count = c;
}
Ok(false)
}
}
#[repr(transparent)]
pub(super) struct Buffer(String);
impl Buffer {
fn new(output: &mut String) -> &mut Self {
unsafe { &mut *(output as *mut String as *mut Self) }
}
#[inline]
fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
fn str(&mut self, s: &str) -> alloc::Result<()> {
self.0.try_push_str(s)
}
fn lines(&mut self, indent: usize, lines: usize) -> alloc::Result<()> {
if lines == 0 {
return Ok(());
}
for _ in 0..lines {
self.0.try_push_str(NL)?;
}
for _ in 0..indent {
self.0.try_push_str(INDENT)?;
}
Ok(())
}
}
pub(crate) struct Formatter<'a> {
span: Span,
pub(super) source: &'a Source,
source_id: SourceId,
o: &'a mut Buffer,
pub(super) options: &'a FmtOptions,
diagnostics: &'a mut Diagnostics,
comments: VecDeque<Comment>,
lines: usize,
use_lines: bool,
ws: bool,
indent: usize,
}
impl<'a> Formatter<'a> {
pub(super) fn new(
span: Span,
source: &'a str,
source_id: SourceId,
o: &'a mut String,
options: &'a FmtOptions,
diagnostics: &'a mut Diagnostics,
) -> Self {
Self {
span,
source: Source::new(source),
source_id,
o: Buffer::new(o),
options,
diagnostics,
comments: VecDeque::new(),
lines: 0,
use_lines: false,
ws: false,
indent: 0,
}
}
pub(crate) fn ignore(&mut self, node: Node<'a>) -> Result<()> {
self.process_comments(node.walk_from())?;
Ok(())
}
pub(crate) fn write_owned(&mut self, node: Node<'a>) -> Result<()> {
self.flush_whitespace(false)?;
self.write_node(&node)?;
self.process_comments(node.walk_from())?;
Ok(())
}
pub(crate) fn write_raw(&mut self, node: Node<'a>) -> Result<()> {
self.write_node(&node)?;
self.process_comments(node.walk_from())?;
Ok(())
}
pub(crate) fn lit(&mut self, s: &str) -> Result<()> {
self.flush_whitespace(true)?;
self.o.str(s).with_span(self.span)?;
Ok(())
}
pub(super) fn comments(&mut self, comments: Comments) -> Result<()> {
if self.comments.is_empty() {
return Ok(());
}
match comments {
Comments::Line => {
self.comments_line(false)?;
}
Comments::Prefix | Comments::Suffix => {
self.comments_ws(
matches!(comments, Comments::Suffix),
matches!(comments, Comments::Prefix),
)?;
}
Comments::Infix => {
self.comments_ws(false, false)?;
}
}
Ok(())
}
pub(super) fn indent(&mut self, indent: isize) -> Result<()> {
if indent != 0 {
self.indent = self.checked_indent(indent)?;
}
Ok(())
}
pub(crate) fn nl(&mut self, lines: usize) -> Result<()> {
if lines == 0 {
return Ok(());
}
self.comments_line(true)?;
if self.lines == 0 {
self.lines = lines;
}
self.use_lines = true;
Ok(())
}
pub(super) fn ws(&mut self) -> Result<()> {
self.comments_ws(true, false)?;
self.ws = true;
Ok(())
}
pub(super) fn flush_prefix_comments(&mut self, tree: &'a Tree) -> Result<()> {
self.process_comments(tree.walk())?;
self.comments(Comments::Line)?;
self.use_lines = self.lines > 0;
Ok(())
}
fn comments_line(&mut self, same_line: bool) -> Result<()> {
while let Some(c) = self.comments.front() {
if same_line && c.before != 0 {
break;
}
if !self.o.is_empty() {
if c.before == 0 {
self.o.str(WS).with_span(c.span)?;
} else {
self.o
.lines(self.indent, c.before.min(2))
.with_span(c.span)?;
}
}
let source = self.source.get(c.span)?;
let source = if c.line { source.trim_end() } else { source };
self.o.str(source).with_span(c.span)?;
_ = self.comments.pop_front();
}
Ok(())
}
fn comments_ws(&mut self, prefix: bool, suffix: bool) -> Result<()> {
if self.comments.is_empty() {
return Ok(());
}
let mut any = false;
while let Some(c) = self.comments.front() {
if c.line {
break;
}
if (prefix || any) && !self.o.is_empty() {
self.o.str(WS).with_span(c.span)?;
}
let source = self.source.get(c.span)?;
self.o.str(source).with_span(c.span)?;
any = true;
_ = self.comments.pop_front();
}
if suffix && any {
self.o.str(WS).with_span(self.span)?;
}
Ok(())
}
fn process_comments<I>(&mut self, iter: I) -> Result<()>
where
I: IntoIterator<Item = Node<'a>>,
{
for node in iter {
if !node.has_children() && !self.write_comment(node)? {
break;
}
}
Ok(())
}
fn write_comment(&mut self, node: Node<'a>) -> Result<bool> {
let span = node.span();
match node.kind() {
Kind::Comment | Kind::MultilineComment(..) => {
self.comments
.try_push_back(Comment {
span,
before: take(&mut self.lines),
line: matches!(node.kind(), Kind::Comment),
})
.with_span(span)?;
Ok(true)
}
Kind::Whitespace => {
let source = self.source.get(span)?;
let count = source.chars().filter(|c| *c == NL_CHAR).count();
if self.lines == 0 {
self.lines = count;
}
Ok(true)
}
_ => Ok(false),
}
}
fn checked_indent(&mut self, level: isize) -> Result<usize> {
let Some(indent) = self.indent.checked_add_signed(level) else {
return Err(Error::new(
self.span,
ErrorKind::BadIndent {
level,
indent: self.indent,
},
));
};
Ok(indent)
}
fn write_node(&mut self, node: &Node<'_>) -> Result<()> {
let source = self.source.get(node.span())?;
self.span = node.span();
self.o.str(source).with_span(self.span)?;
Ok(())
}
pub(crate) fn flush_whitespace(&mut self, preserve: bool) -> Result<()> {
if self.use_lines && self.lines > 0 {
self.o.lines(self.indent, self.lines.min(2))?;
self.ws = false;
self.use_lines = false;
self.lines = 0;
}
if self.ws {
self.o.str(WS).with_span(self.span)?;
self.ws = false;
}
if !preserve {
self.lines = 0;
self.use_lines = false;
}
Ok(())
}
}
impl<'a> Ignore<'a> for Formatter<'a> {
fn error(&mut self, error: Error) -> alloc::Result<()> {
self.diagnostics.error(self.source_id, error)
}
fn ignore(&mut self, node: Node<'a>) -> Result<()> {
Formatter::ignore(self, node)
}
}