mod attribute;
mod callbacks;
mod element;
mod whitespace;
#[cfg(test)]
mod tests;
use vize_carton::{Box, Bump, String, Vec};
use vize_relief::{
ast::*,
errors::{CompilerError, ErrorCode},
options::{ParserOptions, WhitespaceStrategy},
};
use crate::tokenizer::Tokenizer;
use callbacks::ParserCallbacks;
use whitespace::condense_whitespace;
pub struct Parser<'a> {
allocator: &'a Bump,
source: &'a str,
options: ParserOptions,
stack: Vec<'a, ParserStackEntry<'a>>,
root: Option<RootNode<'a>>,
current_element: Option<CurrentElement<'a>>,
current_attr: Option<CurrentAttribute<'a>>,
current_dir: Option<CurrentDirective<'a>>,
errors: Vec<'a, CompilerError>,
newlines: Vec<'a, usize>,
in_pre: bool,
in_v_pre: bool,
}
#[derive(Debug)]
pub(super) struct ParserStackEntry<'a> {
pub(super) element: ElementNode<'a>,
pub(super) in_pre: bool,
pub(super) in_v_pre: bool,
}
pub(super) struct CurrentElement<'a> {
pub(super) tag: String,
pub(super) tag_start: usize,
#[allow(dead_code)]
pub(super) tag_end: usize,
pub(super) ns: Namespace,
pub(super) is_self_closing: bool,
pub(super) props: Vec<'a, PropNode<'a>>,
}
pub(super) struct CurrentAttribute<'a> {
pub(super) name: String,
pub(super) name_start: usize,
pub(super) name_end: usize,
pub(super) value_start: Option<usize>,
pub(super) value_end: Option<usize>,
pub(super) value_content: Option<String>,
pub(super) _marker: std::marker::PhantomData<&'a ()>,
}
pub(super) struct CurrentDirective<'a> {
pub(super) name: String,
pub(super) raw_name: String,
pub(super) name_start: usize,
#[allow(dead_code)]
pub(super) name_end: usize,
pub(super) arg: Option<(String, usize, usize, bool)>, pub(super) modifiers: Vec<'a, (String, usize, usize)>,
pub(super) value_start: Option<usize>,
pub(super) value_end: Option<usize>,
pub(super) value_content: Option<String>,
pub(super) _marker: std::marker::PhantomData<&'a ()>,
}
impl<'a> Parser<'a> {
pub fn new(allocator: &'a Bump, source: &'a str) -> Self {
Self::with_options(allocator, source, ParserOptions::default())
}
pub fn with_options(allocator: &'a Bump, source: &'a str, options: ParserOptions) -> Self {
Self {
allocator,
source,
options,
stack: Vec::new_in(allocator),
root: None,
current_element: None,
current_attr: None,
current_dir: None,
errors: Vec::new_in(allocator),
newlines: Vec::new_in(allocator),
in_pre: false,
in_v_pre: false,
}
}
pub fn parse(mut self) -> (RootNode<'a>, Vec<'a, CompilerError>) {
let root = RootNode::new(self.allocator, self.source);
self.root = Some(root);
let delimiter_open: Vec<'a, u8> =
Vec::from_iter_in(self.options.delimiters.0.bytes(), self.allocator);
let delimiter_close: Vec<'a, u8> =
Vec::from_iter_in(self.options.delimiters.1.bytes(), self.allocator);
let mut tokenizer = Tokenizer::with_delimiters(
self.source,
ParserCallbacks { parser: &mut self },
&delimiter_open,
&delimiter_close,
);
tokenizer.tokenize();
self.handle_unclosed_elements();
if let Some(ref mut root) = self.root
&& self.options.whitespace == WhitespaceStrategy::Condense
{
condense_whitespace(&mut root.children, self.options.is_pre_tag);
}
let root = match self.root.take() {
Some(root) => root,
None => RootNode::new(self.allocator, self.source),
};
(root, self.errors)
}
fn get_source(&self, start: usize, end: usize) -> &str {
&self.source[start..end]
}
fn get_pos(&self, offset: usize) -> Position {
let line = match self.newlines.binary_search(&offset) {
Ok(i) => i + 1,
Err(i) => i + 1,
};
let column = if line == 1 {
offset + 1
} else if line > 1 && line - 2 < self.newlines.len() {
offset - self.newlines[line - 2]
} else {
offset + 1
};
Position::new(offset as u32, line as u32, column as u32)
}
fn create_loc(&self, start: usize, end: usize) -> SourceLocation {
SourceLocation::new(
self.get_pos(start),
self.get_pos(end),
self.get_source(start, end),
)
}
fn add_child(&mut self, child: TemplateChildNode<'a>) {
if let Some(entry) = self.stack.last_mut() {
entry.element.children.push(child);
} else if let Some(ref mut root) = self.root {
root.children.push(child);
}
}
fn handle_unclosed_elements(&mut self) {
while let Some(entry) = self.stack.pop() {
let loc = entry.element.loc.clone();
self.errors
.push(CompilerError::new(ErrorCode::MissingEndTag, Some(loc)));
let boxed = Box::new_in(entry.element, self.allocator);
self.add_child(TemplateChildNode::Element(boxed));
}
}
}
pub fn parse<'a>(allocator: &'a Bump, source: &'a str) -> (RootNode<'a>, Vec<'a, CompilerError>) {
Parser::new(allocator, source).parse()
}
pub fn parse_with_options<'a>(
allocator: &'a Bump,
source: &'a str,
options: ParserOptions,
) -> (RootNode<'a>, Vec<'a, CompilerError>) {
Parser::with_options(allocator, source, options).parse()
}