use alloc::vec::Vec as OwnedVec;
use vize_l0::{Allocator, SourceBlock, SourceFrameError, SourceRoot, Vec};
use super::text::{self, TextBinding, TextBoundary, TextBoundaryKind};
use crate::dialect::LegacyVueVersion;
use crate::dialect::vue::surface::{SurfacePolicy, sink::VueSink};
use crate::event::Recorder;
use crate::markup::entity::DecodedEntity;
use crate::markup::parse::{SurfaceError, construct};
use crate::markup::surface::SurfaceTree;
use crate::markup::token::{LexErrorCode, LexMode, QuoteType};
use crate::markup::{Component, LexOptions, Lexer, Sink};
mod body;
pub use body::{TextChild, TextChildren, TextRefusal, TextView};
mod retained;
pub use retained::{
ComponentExpressionPool, RetainedTextChild, RetainedTextChildren, RetainedTextRefusal,
RetainedTextView,
};
#[derive(Debug)]
pub struct ComponentParse<'a> {
block: SourceBlock<'a>,
tree: SurfaceTree<'a>,
authored: Option<SurfaceTree<'a>>,
errors: Vec<'a, SurfaceError>,
bindings: OwnedVec<TextBinding<'a>>,
unsupported: OwnedVec<TextBoundary>,
}
impl<'a> ComponentParse<'a> {
pub const fn version(&self) -> LegacyVueVersion {
LegacyVueVersion::V2
}
pub const fn block(&self) -> SourceBlock<'a> {
self.block
}
pub fn tree(&self) -> &SurfaceTree<'a> {
&self.tree
}
pub fn authored(&self) -> Option<&SurfaceTree<'a>> {
self.authored.as_ref()
}
pub fn errors(&self) -> &[SurfaceError] {
&self.errors
}
pub fn bindings(&self) -> &[TextBinding<'a>] {
&self.bindings
}
pub fn unsupported(&self) -> &[TextBoundary] {
&self.unsupported
}
}
pub fn parse_component<'a>(
allocator: &'a Allocator,
source: &'a str,
) -> Result<ComponentParse<'a>, SourceFrameError> {
Ok(parse_component_block(
allocator,
SourceRoot::new(source)?.whole_block(),
))
}
pub fn parse_component_block<'a>(
allocator: &'a Allocator,
block: SourceBlock<'a>,
) -> ComponentParse<'a> {
#[cfg(test)]
crate::container::vue::vue2_entry_hooks::component_entry();
projection(allocator, block, false)
}
pub fn parse_component_with_authored_block<'a>(
allocator: &'a Allocator,
block: SourceBlock<'a>,
) -> ComponentParse<'a> {
projection(allocator, block, true)
}
fn projection<'a>(
allocator: &'a Allocator,
block: SourceBlock<'a>,
authored: bool,
) -> ComponentParse<'a> {
let source = block.source();
let mut boundaries = Vec::new_in(&allocator);
let mut bindings = OwnedVec::new();
let mut unsupported = OwnedVec::new();
let (tree, authored, mut errors) =
construct::<false>(allocator, source, authored, |events, errors| {
let recorder = Recorder { events, errors };
let inner = VueSink::<Vue2Policy>::new(allocator, source, recorder, &mut boundaries);
let sink = TextSink {
allocator,
block,
inner,
bindings: &mut bindings,
unsupported: &mut unsupported,
};
Lexer::<Component, _>::new(source, sink, LexOptions::default()).run();
});
for error in &mut errors {
error.offset += block.start();
}
ComponentParse {
block,
tree,
authored,
errors,
bindings,
unsupported,
}
}
struct Vue2Policy;
impl SurfacePolicy for Vue2Policy {
type Boundary = core::convert::Infallible;
fn pre(raw: &str, _offset: u32, _source: &str) -> Result<bool, Self::Boundary> {
Ok(raw == "v-pre")
}
}
struct TextSink<'a, 'v> {
allocator: &'a Allocator,
block: SourceBlock<'a>,
inner: VueSink<'a, 'v, Vue2Policy>,
bindings: &'v mut OwnedVec<TextBinding<'a>>,
unsupported: &'v mut OwnedVec<TextBoundary>,
}
macro_rules! forward {
($($name:ident($($arg:ident: $ty:ty),*);)*) => {
$(fn $name(&mut self, $($arg: $ty),*) { self.inner.$name($($arg),*); })*
};
}
impl Sink for TextSink<'_, '_> {
forward! {
on_text(start: usize, end: usize);
on_attrib_data(start: usize, end: usize);
on_attrib_entity(ch: char, start: usize, end: usize);
on_attrib_entity_value(value: DecodedEntity, start: usize, end: usize);
on_attrib_end(quote: QuoteType, end: usize);
on_attrib_name(start: usize, end: usize);
on_attrib_name_end(end: usize);
on_dir_name(start: usize, end: usize);
on_dir_arg(start: usize, end: usize);
on_dir_modifier(start: usize, end: usize);
on_comment(start: usize, end: usize);
on_cdata(start: usize, end: usize);
on_processing_instruction(start: usize, end: usize);
on_open_tag_name(start: usize, end: usize);
on_open_tag_end(end: usize);
on_self_closing_tag(end: usize);
on_close_tag(start: usize, end: usize);
on_end();
on_error(code: LexErrorCode, index: usize);
}
fn on_text_entity(&mut self, ch: char, start: usize, end: usize) {
if matches!(ch, '{' | '}') {
self.unsupported.push(TextBoundary {
span: vize_l0::Span::new(
self.block.start() + start as u32,
self.block.start() + end as u32,
),
kind: TextBoundaryKind::EncodedDelimiter,
});
}
self.inner.on_text_entity(ch, start, end);
}
fn on_text_entity_value(&mut self, value: DecodedEntity, start: usize, end: usize) {
let mut brace = false;
value.for_each(|ch| brace |= matches!(ch, '{' | '}'));
if brace {
self.unsupported.push(TextBoundary {
span: vize_l0::Span::new(
self.block.start() + start as u32,
self.block.start() + end as u32,
),
kind: TextBoundaryKind::EncodedDelimiter,
});
}
self.inner.on_text_entity_value(value, start, end);
}
fn on_interpolation(&mut self, start: usize, end: usize) {
if let Some(binding) = text::observe(self.allocator, self.block, start, end) {
self.bindings.push(binding);
}
self.inner.on_interpolation(start, end);
}
fn mode(&self) -> LexMode {
self.inner.mode()
}
fn svg_title_children(&self) -> bool {
self.inner.svg_title_children()
}
}