use alloc::boxed::Box;
use core::fmt;
use vize_l0::{SourceBlock, String};
use vize_l3::decision::native::NativeTemplateDomAnalysis;
use super::{DomError, emit_template};
use crate::{
module::{AssemblyError, assemble_template},
runtime::{HelperSet, Runtime, vocabulary},
write::{EmitDocument, Emitted, LinkSink, Recorded, SpanLink},
};
pub struct NativeTemplateDomOutput<'owner, 'arena> {
analysis: NativeTemplateDomAnalysis<'owner, 'arena>,
block: SourceBlock<'arena>,
document: EmitDocument,
helpers: HelperSet,
}
impl<'owner, 'arena> NativeTemplateDomOutput<'owner, 'arena> {
#[must_use]
pub fn analysis(&self) -> &NativeTemplateDomAnalysis<'owner, 'arena> {
&self.analysis
}
#[must_use]
pub const fn source_block(&self) -> SourceBlock<'arena> {
self.block
}
#[must_use]
pub fn code(&self) -> &str {
self.document.as_str()
}
#[must_use]
pub fn links(&self) -> &[SpanLink] {
self.document.links()
}
#[must_use]
pub fn helpers(&self) -> &HelperSet {
&self.helpers
}
#[must_use]
pub fn source_map(&self, filename: &str) -> String {
self.document.source_map(filename, self.block.root_source())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NativeTemplateDomOutputError {
Dom(DomError),
Assembly(AssemblyError),
SourceOwnerMismatch,
InvalidGeneratedRange,
InvalidAuthoredRange,
}
pub struct NativeTemplateDomOutputFailure<'owner, 'arena> {
analysis: Box<NativeTemplateDomAnalysis<'owner, 'arena>>,
error: NativeTemplateDomOutputError,
}
impl<'owner, 'arena> NativeTemplateDomOutputFailure<'owner, 'arena> {
#[must_use]
pub fn analysis(&self) -> &NativeTemplateDomAnalysis<'owner, 'arena> {
&self.analysis
}
#[must_use]
pub const fn error(&self) -> NativeTemplateDomOutputError {
self.error
}
}
impl fmt::Debug for NativeTemplateDomOutputFailure<'_, '_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("NativeTemplateDomOutputFailure")
.field("error", &self.error)
.finish_non_exhaustive()
}
}
pub fn emit_template_output<'owner, 'arena>(
analysis: NativeTemplateDomAnalysis<'owner, 'arena>,
) -> Result<NativeTemplateDomOutput<'owner, 'arena>, NativeTemplateDomOutputFailure<'owner, 'arena>>
{
match record(&analysis) {
Ok((block, document, helpers)) => Ok(NativeTemplateDomOutput {
analysis,
block,
document,
helpers,
}),
Err(error) => Err(NativeTemplateDomOutputFailure {
analysis: Box::new(analysis),
error,
}),
}
}
fn record<'arena>(
analysis: &NativeTemplateDomAnalysis<'_, 'arena>,
) -> Result<(SourceBlock<'arena>, EmitDocument, HelperSet), NativeTemplateDomOutputError> {
let block = analysis.owner().selected().component().block();
if !core::ptr::eq(block.root_source(), analysis.artifact().source()) {
return Err(NativeTemplateDomOutputError::SourceOwnerMismatch);
}
let render = emit_template::<Recorded>(analysis).map_err(NativeTemplateDomOutputError::Dom)?;
let Emitted {
text,
links,
helpers,
} = assemble_template(render, vocabulary(Runtime::VueDom))
.map_err(NativeTemplateDomOutputError::Assembly)?;
let document = links.into_document(text);
if u32::try_from(document.len()).is_err() {
return Err(NativeTemplateDomOutputError::InvalidGeneratedRange);
}
for link in document.links() {
if document
.as_str()
.get(link.generated.start as usize..link.generated.end as usize)
.is_none()
{
return Err(NativeTemplateDomOutputError::InvalidGeneratedRange);
}
if !block.contains_block_span(link.authored) {
return Err(NativeTemplateDomOutputError::InvalidAuthoredRange);
}
}
Ok((block, document, helpers))
}