use vize_carton::{Box, String, Vec, appends};
use vize_relief::{
ast::{
AttributeNode, ConstantType, DirectiveNode, ExpressionNode, PropNode, SimpleExpressionNode,
TextNode,
},
errors::{CompilerError, ErrorCode},
};
use crate::tokenizer::QuoteType;
use super::{CurrentAttribute, CurrentDirective, Parser};
impl<'a> Parser<'a> {
pub(super) fn on_attrib_name_impl(&mut self, start: usize, end: usize) {
let name = self.get_source(start, end);
self.current_attr = Some(CurrentAttribute {
name: name.into(),
name_start: start,
name_end: end,
value_start: None,
value_end: None,
value_content: None,
_marker: std::marker::PhantomData,
});
}
pub(super) fn on_attrib_name_end_impl(&mut self, end: usize) {
if let Some(ref mut attr) = self.current_attr {
attr.name_end = end;
}
if let Some(ref mut dir) = self.current_dir {
dir.name_end = end;
}
}
pub(super) fn on_dir_name_impl(&mut self, start: usize, end: usize) {
let raw_name = self.get_source(start, end);
let name = super::callbacks::parse_directive_name(raw_name);
self.current_dir = Some(CurrentDirective {
name: name.into(),
raw_name: raw_name.into(),
name_start: start,
name_end: end,
arg: None,
modifiers: Vec::new_in(self.allocator),
value_start: None,
value_end: None,
value_content: None,
_marker: std::marker::PhantomData,
});
}
pub(super) fn on_dir_arg_impl(&mut self, start: usize, end: usize) {
let arg: String = self.get_source(start, end).into();
let is_dynamic = start > 0 && self.source.as_bytes().get(start - 1) == Some(&b'[');
if let Some(ref mut dir) = self.current_dir {
dir.arg = Some((arg, start, end, is_dynamic));
}
}
pub(super) fn on_dir_modifier_impl(&mut self, start: usize, end: usize) {
if start >= end {
let loc = self.create_loc(start, end);
self.errors.push(CompilerError::new(
ErrorCode::MissingDirectiveModifier,
Some(loc),
));
return;
}
let modifier: String = self.get_source(start, end).into();
if let Some(ref mut dir) = self.current_dir {
dir.modifiers.push((modifier, start, end));
}
}
pub(super) fn on_attrib_data_impl(&mut self, start: usize, end: usize) {
let source = self.get_source(start, end).to_owned();
self.accumulate_attr_or_dir_value(&source, start, end);
}
pub(super) fn on_attrib_entity_impl(&mut self, ch: char, start: usize, end: usize) {
let mut content = [0_u8; 4];
self.accumulate_attr_or_dir_value(ch.encode_utf8(&mut content), start, end);
}
fn accumulate_attr_or_dir_value(&mut self, content: &str, start: usize, end: usize) {
if let Some(ref mut attr) = self.current_attr {
Self::accumulate_value(
&mut attr.value_content,
&mut attr.value_start,
&mut attr.value_end,
content,
start,
end,
);
}
if let Some(ref mut dir) = self.current_dir {
Self::accumulate_value(
&mut dir.value_content,
&mut dir.value_start,
&mut dir.value_end,
content,
start,
end,
);
}
}
#[inline]
fn accumulate_value(
content_field: &mut Option<String>,
start_field: &mut Option<usize>,
end_field: &mut Option<usize>,
content: &str,
start: usize,
end: usize,
) {
if content_field.is_none() {
*start_field = Some(start);
*content_field = Some(content.into());
} else if let Some(existing) = content_field.as_mut() {
existing.push_str(content);
}
*end_field = Some(end);
}
pub(super) fn on_attrib_end_impl(&mut self, quote: QuoteType, end: usize) {
if let Some(attr) = self.current_attr.take() {
self.finish_attribute(attr, quote, end);
}
if let Some(dir) = self.current_dir.take() {
self.finish_directive(dir, quote, end);
}
}
fn prop_loc_end(&self, quote: QuoteType, end: usize, name_end: usize) -> usize {
match quote {
QuoteType::NoValue => name_end,
QuoteType::Double | QuoteType::Single => (end + 1).min(self.source.len()),
QuoteType::Unquoted => end,
}
}
fn has_duplicate_attribute(&self, name: &str) -> bool {
self.current_element.as_ref().is_some_and(|current| {
current.props.iter().any(|prop| {
matches!(prop, PropNode::Attribute(existing) if existing.name.as_str() == name)
})
})
}
fn finish_attribute(&mut self, attr: CurrentAttribute<'a>, quote: QuoteType, end: usize) {
let loc_end = self.prop_loc_end(quote, end, attr.name_end);
let loc = self.create_loc(attr.name_start, loc_end);
let name_loc = self.create_loc(attr.name_start, attr.name_end);
if self.has_duplicate_attribute(attr.name.as_str()) {
let mut message = String::with_capacity(attr.name.len() + 79);
appends!(
message,
"Duplicate attribute `",
attr.name.as_str(),
"`. Keeping the repeated attribute so parsing can continue."
);
self.errors.push(CompilerError::with_message(
ErrorCode::DuplicateAttribute,
message,
Some(name_loc.clone()),
));
}
let mut attr_node = AttributeNode::new(attr.name.clone(), loc);
attr_node.name_loc = name_loc;
if let (Some(v_start), Some(v_end), Some(v_content)) =
(attr.value_start, attr.value_end, attr.value_content)
{
let value_loc = self.create_loc(v_start, v_end);
attr_node.value = Some(TextNode::new(v_content, value_loc));
} else if matches!(quote, QuoteType::Double | QuoteType::Single) {
let empty_loc: vize_relief::SourceLocation = self.create_loc(end, end);
attr_node.value = Some(TextNode::new("", empty_loc));
}
if let Some(ref mut current) = self.current_element {
let boxed = Box::new_in(attr_node, self.allocator);
current.props.push(PropNode::Attribute(boxed));
}
}
fn finish_directive(&mut self, dir: CurrentDirective<'a>, quote: QuoteType, end: usize) {
let loc_end = self.prop_loc_end(quote, end, dir.name_end);
let loc = self.create_loc(dir.name_start, loc_end);
if dir.name.is_empty() {
let mut message = String::with_capacity(dir.raw_name.len() + 80);
appends!(
message,
"Directive `",
dir.raw_name.as_str(),
"` is missing a name. Ignoring it so the rest of the tag can be parsed."
);
self.errors.push(CompilerError::with_message(
ErrorCode::MissingDirectiveName,
message,
Some(loc),
));
return;
}
let mut dir_node = DirectiveNode::new(self.allocator, dir.name.clone(), loc);
dir_node.raw_name = Some(dir.raw_name);
let shorthand_exp = if dir.name == "bind" && dir.value_start.is_none() {
if let Some((ref arg_content, arg_start, arg_end, false)) = dir.arg {
Some((vize_carton::camelize(arg_content), arg_start, arg_end))
} else {
None
}
} else {
None
};
if let Some((arg_content, arg_start, arg_end, is_dynamic)) = dir.arg {
let arg_loc = self.create_loc(arg_start, arg_end);
let mut arg_expr = SimpleExpressionNode::new(arg_content, !is_dynamic, arg_loc);
if is_dynamic {
arg_expr.const_type = ConstantType::NotConstant;
}
let arg_boxed = Box::new_in(arg_expr, self.allocator);
dir_node.arg = Some(ExpressionNode::Simple(arg_boxed));
}
for (mod_content, mod_start, mod_end) in dir.modifiers {
let mod_loc = self.create_loc(mod_start, mod_end);
let mod_expr = SimpleExpressionNode::new(mod_content, true, mod_loc);
dir_node.modifiers.push(mod_expr);
}
if let (Some(v_start), Some(v_end), Some(v_content)) =
(dir.value_start, dir.value_end, dir.value_content)
{
let exp_loc = self.create_loc(v_start, v_end);
let exp_node = SimpleExpressionNode::new(v_content, false, exp_loc);
let exp_boxed = Box::new_in(exp_node, self.allocator);
dir_node.exp = Some(ExpressionNode::Simple(exp_boxed));
} else if let Some((camelized, s_start, s_end)) = shorthand_exp {
let exp_loc = self.create_loc(s_start, s_end);
let exp_node = SimpleExpressionNode::new(&*camelized, false, exp_loc);
let exp_boxed = Box::new_in(exp_node, self.allocator);
dir_node.exp = Some(ExpressionNode::Simple(exp_boxed));
dir_node.shorthand = true;
}
if let Some(ref mut current) = self.current_element {
let boxed = Box::new_in(dir_node, self.allocator);
current.props.push(PropNode::Directive(boxed));
}
}
}