use oxc_allocator::{Allocator, ArenaVec};
use crate::ast::Root;
use crate::diagnostic::{Diagnostic, DiagnosticKind};
use crate::options::ParserOptions;
use crate::pos::Span;
#[derive(Debug)]
pub struct ParserReturn<'a> {
pub root: Root<'a>,
pub diagnostics: Vec<Diagnostic>,
pub blanks: Vec<Span>,
}
pub struct Parser<'a> {
allocator: &'a Allocator,
source: &'a str,
options: ParserOptions,
}
impl<'a> Parser<'a> {
pub fn new(allocator: &'a Allocator, source: &'a str) -> Self {
Self::with_options(allocator, source, ParserOptions::default())
}
pub fn with_options(allocator: &'a Allocator, source: &'a str, options: ParserOptions) -> Self {
Self { allocator, source, options }
}
pub fn parse(self) -> ParserReturn<'a> {
let mut diagnostics = vec![];
if u32::try_from(self.source.len()).is_err() {
diagnostics.push(Diagnostic::new(DiagnosticKind::SourceTooLong, Span::empty(0)));
return ParserReturn {
root: Root { children: ArenaVec::new_in(&self.allocator), span: Span::empty(0) },
diagnostics,
blanks: vec![],
};
}
#[expect(clippy::cast_possible_truncation)]
let source_len = self.source.len() as u32;
let engine = crate::block::Engine::new(self.source, self.options.constructs);
let (irs, blanks, refs) = engine.run();
let root = crate::block::build(
self.allocator,
self.source,
&blanks.loosening,
irs,
Span::new(0, source_len),
&self.options.constructs,
&refs,
);
ParserReturn { root, diagnostics, blanks: blanks.all }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Block, HeadingKind};
#[test]
fn blank_lines_listed_vs_loosening() {
for (source, blank_at, tight) in
[("- > a\n >\n > b\n", 9, true), ("- a\n\n- b\n", 4, false)]
{
let allocator = Allocator::default();
let ret = Parser::new(&allocator, source).parse();
assert_eq!(ret.blanks, vec![Span::empty(blank_at)], "{source:?}");
let Some(Block::List(list)) = ret.root.children.first() else { panic!("list") };
assert_eq!(list.tight, tight, "{source:?}");
}
}
#[test]
fn setext_heading_keeps_lazy_lines() {
let allocator = Allocator::default();
let ret = Parser::new(&allocator, "> a\nb\n> ===\n").parse();
let Some(Block::Blockquote(quote)) = ret.root.children.first() else { panic!("quote") };
let Some(Block::Heading(heading)) = quote.children.first() else { panic!("heading") };
let HeadingKind::Setext { lazy_lines, .. } = &heading.kind else { panic!("setext") };
assert_eq!(lazy_lines.as_slice(), &[Span::new(4, 5)]);
}
}