use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::Rc};
use bumpalo::Bump;
use crate::{
Document, InlineNode, Location, SourceLocation, Warning, WarningKind, model::SourceRange,
};
#[derive(Debug, Default)]
pub(crate) struct SourceFiles {
primary: Option<PathBuf>,
included: HashMap<Vec<String>, PathBuf>,
}
impl SourceFiles {
pub(crate) fn new(primary: Option<PathBuf>, ranges: &[SourceRange]) -> Self {
let mut included = HashMap::new();
for range in ranges {
if !range.file_chain.is_empty()
&& let Some(file) = &range.file
&& !included.contains_key(range.file_chain.as_slice())
{
included.insert(range.file_chain.clone(), file.clone());
}
}
Self { primary, included }
}
fn location(&self, location: &Location) -> SourceLocation {
let file = match location
.start
.file
.as_deref()
.filter(|chain| !chain.is_empty())
{
Some(chain) => self.included.get(chain.as_slice()),
None => self.primary.as_ref(),
};
SourceLocation::at_location(file.cloned(), location.clone())
}
}
#[derive(Debug)]
pub(crate) struct OwnedInput {
pub(crate) source: Box<str>,
pub(crate) arena: Bump,
}
impl OwnedInput {
pub(crate) fn new(source: Box<str>) -> Self {
let arena = Bump::with_capacity(source.len());
Self { source, arena }
}
}
type InlineAst<'a> = Vec<InlineNode<'a>>;
self_cell::self_cell! {
struct ParsedDocumentCell {
owner: OwnedInput,
#[covariant]
dependent: Document,
}
impl {Debug}
}
self_cell::self_cell! {
struct ParsedInlineCell {
owner: OwnedInput,
#[covariant]
dependent: InlineAst,
}
impl {Debug}
}
#[derive(Debug)]
#[must_use = "ignoring a ParseResult drops any warnings the parser produced"]
pub struct ParseResult {
cell: ParsedDocumentCell,
warnings: Vec<Warning>,
source_recovery: Option<Box<Warning>>,
source_files: SourceFiles,
}
impl ParseResult {
pub(crate) fn try_new<E>(
owner: OwnedInput,
warnings_handle: Rc<RefCell<Vec<Warning>>>,
source_files: SourceFiles,
builder: impl for<'a> FnOnce(&'a OwnedInput) -> Result<Document<'a>, E>,
) -> Result<Self, E> {
let cell = ParsedDocumentCell::try_new(owner, builder)?;
let warnings = recover_warnings(warnings_handle);
let source_recovery = warnings
.iter()
.find(|warning| {
matches!(
warning.kind,
WarningKind::ContentRecovery { .. }
| WarningKind::UnterminatedDelimitedBlock { .. }
| WarningKind::UnterminatedTable { .. }
| WarningKind::TableUnknownFormat { .. }
| WarningKind::TableIncompleteRow
| WarningKind::TableCellOverflow { .. }
| WarningKind::TableColumnCount { .. }
)
})
.map(|warning| Box::new(Warning::new(warning.kind.clone(), warning.location.clone())));
Ok(Self {
cell,
warnings,
source_recovery,
source_files,
})
}
#[must_use]
pub fn document(&self) -> &Document<'_> {
self.cell.borrow_dependent()
}
#[must_use]
pub fn source(&self) -> &str {
&self.cell.borrow_owner().source
}
#[must_use]
pub fn source_location(&self, location: &Location) -> SourceLocation {
self.source_files.location(location)
}
#[must_use]
pub fn warnings(&self) -> &[Warning] {
&self.warnings
}
#[must_use]
pub fn source_recovery(&self) -> Option<&Warning> {
self.source_recovery.as_deref()
}
pub fn take_warnings(&mut self) -> Vec<Warning> {
std::mem::take(&mut self.warnings)
}
}
#[derive(Debug)]
#[must_use = "ignoring a ParseInlineResult drops any warnings the parser produced"]
pub struct ParseInlineResult {
cell: ParsedInlineCell,
warnings: Vec<Warning>,
}
impl ParseInlineResult {
pub(crate) fn try_new<E>(
owner: OwnedInput,
warnings_handle: Rc<RefCell<Vec<Warning>>>,
builder: impl for<'a> FnOnce(&'a OwnedInput) -> Result<Vec<InlineNode<'a>>, E>,
) -> Result<Self, E> {
let cell = ParsedInlineCell::try_new(owner, builder)?;
Ok(Self {
cell,
warnings: recover_warnings(warnings_handle),
})
}
pub(crate) fn from_infallible(
owner: OwnedInput,
builder: impl for<'a> FnOnce(&'a OwnedInput) -> Vec<InlineNode<'a>>,
) -> Self {
let cell = ParsedInlineCell::new(owner, builder);
Self {
cell,
warnings: Vec::new(),
}
}
#[must_use]
pub fn inlines(&self) -> &[InlineNode<'_>] {
self.cell.borrow_dependent()
}
#[must_use]
pub fn source(&self) -> &str {
&self.cell.borrow_owner().source
}
#[must_use]
pub fn warnings(&self) -> &[Warning] {
&self.warnings
}
pub fn take_warnings(&mut self) -> Vec<Warning> {
std::mem::take(&mut self.warnings)
}
}
fn recover_warnings(handle: Rc<RefCell<Vec<Warning>>>) -> Vec<Warning> {
Rc::try_unwrap(handle).map_or_else(
|shared| std::mem::take(&mut *shared.borrow_mut()),
RefCell::into_inner,
)
}
#[derive(Debug, Clone)]
pub struct OwnedSource(Box<str>);
impl OwnedSource {
#[must_use]
pub fn new(source: impl Into<Box<str>>) -> Self {
Self(source.into())
}
#[must_use]
pub fn source(&self) -> &str {
&self.0
}
}