use fervid_core::{
AttributeOrBinding, FervidAtom, StrOrExpr, VBindDirective, VCustomDirective, VForDirective,
VModelDirective, VOnDirective, VSlotDirective, VueDirectives,
};
use nom::{
branch::alt,
bytes::complete::{tag, take_till},
character::complete::char,
combinator::fail,
error::{ErrorKind, ParseError},
multi::many0,
sequence::{delimited, preceded},
Err, IResult,
};
use swc_core::common::DUMMY_SP;
use crate::parser_old::{
ecma::{parse_js, parse_js_pat},
html_utils::html_name,
};
pub fn parse_attributes(
input: &str,
) -> IResult<&str, (Vec<AttributeOrBinding>, Option<Box<VueDirectives>>)> {
let mut directives = None;
let mut attrs = Vec::new();
let mut input = input;
loop {
let len = input.len();
input = input.trim_start();
let directive_result = parse_directive(input, &mut attrs, &mut directives);
match directive_result {
Ok((new_input, _)) => {
if new_input.len() == len {
return Err(Err::Error(ParseError::from_error_kind(
input,
ErrorKind::Many0,
)));
}
input = new_input;
}
Err(_) => {
let attribute_result = parse_vanilla_attr(input, &mut attrs);
match attribute_result {
Err(Err::Error(_)) => return Ok((input, (attrs, directives))),
Err(e) => return Err(e),
Ok((new_input, _)) => {
if new_input.len() == len {
return Err(Err::Error(ParseError::from_error_kind(
input,
ErrorKind::Many0,
)));
}
input = new_input;
}
}
}
}
}
}
fn parse_vanilla_attr<'i>(
input: &'i str,
out: &mut Vec<AttributeOrBinding>,
) -> IResult<&'i str, ()> {
let (input, attr_name) = html_name(input)?;
let eq: Result<(&str, char), nom::Err<nom::error::Error<_>>> = char('=')(input);
match eq {
Err(_) => {
out.push(AttributeOrBinding::RegularAttribute {
name: attr_name.into(),
value: attr_name.into(),
span: DUMMY_SP
});
Ok((input, ()))
}
Ok((input, _)) => {
let (input, attr_value) = parse_attr_value(input)?;
#[cfg(dbg_print)]
println!("Dynamic attr: value = {:?}", attr_value);
out.push(AttributeOrBinding::RegularAttribute {
name: attr_name.into(),
value: attr_value.into(),
span: DUMMY_SP
});
Ok((input, ()))
}
}
}
fn parse_attr_value(input: &str) -> IResult<&str, &str> {
delimited(char('"'), take_till(|c| c == '"'), char('"'))(input)
}
fn parse_directive<'i>(
input: &'i str,
attributes: &mut Vec<AttributeOrBinding>,
directives: &mut Option<Box<VueDirectives>>,
) -> IResult<&'i str, ()> {
let (input, prefix) = alt((tag("v-"), tag("@"), tag("#"), tag(":"), tag(".")))(input)?;
let mut is_bind_prop = false;
let mut is_dynamic = false;
let mut has_argument = false;
let (input, directive_name) = match prefix {
"v-" => {
let (input, name) = html_name(input)?;
if let Some(':') = input.chars().next() {
has_argument = true;
(&input[1..], name)
} else {
(input, name)
}
}
"@" => {
has_argument = true;
(input, "on")
}
":" => {
has_argument = true;
(input, "bind")
}
"." => {
has_argument = true;
is_bind_prop = true;
(input, "bind")
}
"#" => {
has_argument = true;
(input, "slot")
}
_ => {
return Err(nom::Err::Error(nom::error::Error {
code: nom::error::ErrorKind::Tag,
input,
}))
}
};
let (input, argument) = if has_argument {
let (input, arg) = if input.starts_with("[") {
is_dynamic = true;
delimited(char('['), html_name, char(']'))(input)?
} else {
html_name(input)?
};
(input, Some(arg))
} else {
(input, None)
};
#[cfg(dbg_print)]
{
println!();
println!("Parsed directive {:?}", directive_name);
println!("Has argument: {}, argument: {:?}", has_argument, argument);
}
let (input, modifiers): (&str, Vec<&str>) =
many0(preceded(char('.'), html_name))(input).unwrap_or((input, vec![]));
let (input, value) = if !input.starts_with('=') {
(input, None)
} else {
let (input, value) = parse_attr_value(&input[1..])?;
(input, Some(value))
};
macro_rules! fail {
() => {
return fail(input);
};
}
macro_rules! expect_value {
() => {
if let Some(value) = value {
value
} else {
fail!();
}
};
}
macro_rules! get_directives {
() => {
directives.get_or_insert_with(|| Box::new(VueDirectives::default()))
};
}
macro_rules! push_directive {
($key: ident, $value: expr) => {
let directives = get_directives!();
directives.$key = Some($value);
};
}
macro_rules! push_directive_js {
($key: ident, $value: expr) => {
match parse_js($value, 0, 0) {
Ok(parsed) => {
let directives = get_directives!();
directives.$key = Some(parsed);
}
Result::Err(_) => {}
}
};
}
let argument = argument.map(|v| FervidAtom::from(v));
let modifiers: Vec<FervidAtom> = modifiers.into_iter().map(|v| FervidAtom::from(v)).collect();
match directive_name {
"bind" => {
let mut is_camel = false;
let mut is_prop = is_bind_prop;
let mut is_attr = false;
for modifier in modifiers.iter() {
match modifier.as_ref() {
"camel" => is_camel = true,
"prop" => is_prop = true,
"attr" => is_attr = true,
_ => {}
}
}
let value = expect_value!();
let Ok(parsed_expr) = parse_js(value, 0, 0) else {
fail!();
};
let argument = convert_argument(argument, is_dynamic, input)?;
attributes.push(AttributeOrBinding::VBind(VBindDirective {
argument,
value: parsed_expr,
is_camel,
is_prop,
is_attr,
span: DUMMY_SP
}));
}
"on" => {
let argument = convert_argument(argument, is_dynamic, input)?;
attributes.push(AttributeOrBinding::VOn(VOnDirective {
event: argument,
handler: value.and_then(|value| {
let parse_result = parse_js(value, 0, 0);
match parse_result {
Ok(parsed_expr) => Some(parsed_expr),
Err(_) => None,
}
}),
modifiers,
span: DUMMY_SP
}));
}
"if" => {
let value = expect_value!();
match parse_js(value, 0, 0) {
Ok(condition) => {
push_directive!(v_if, condition);
}
Result::Err(_) => {}
}
}
"else-if" => {
let value = expect_value!();
match parse_js(value, 0, 0) {
Ok(condition) => {
push_directive!(v_else_if, condition);
}
Result::Err(_) => {}
}
}
"else" => {
push_directive!(v_else, ());
}
"for" => {
let value = expect_value!();
let Some((itervar, iterable)) = split_itervar_and_iterable(value) else {
fail!();
};
match parse_js(itervar, 0, 0) {
Ok(itervar) => match parse_js(iterable, 0, 0) {
Ok(iterable) => {
push_directive!(
v_for,
VForDirective {
iterable,
itervar,
patch_flags: Default::default(),
span: DUMMY_SP
}
);
}
Result::Err(_) => {}
},
Result::Err(_) => {}
};
}
"model" => {
let value = expect_value!();
let argument = convert_argument(argument, is_dynamic, input)?;
match parse_js(value, 0, 0) {
Ok(model_binding) => {
let directives = get_directives!();
directives.v_model.push(VModelDirective {
argument,
value: model_binding,
update_handler: None,
modifiers,
span: DUMMY_SP, });
}
Result::Err(_) => {}
}
}
"slot" => {
let value = value.and_then(|v| {
match parse_js_pat(v, 0, 0) {
Ok(value) => Some(Box::new(value)),
Result::Err(_) => None,
}
});
let argument = convert_argument(argument, is_dynamic, input)?;
push_directive!(
v_slot,
VSlotDirective {
slot_name: argument,
value,
}
);
}
"show" => {
let value = expect_value!();
push_directive_js!(v_show, value);
}
"html" => {
let value = expect_value!();
push_directive_js!(v_html, value);
}
"text" => {
let value = expect_value!();
push_directive_js!(v_text, value);
}
"once" => {
push_directive!(v_once, ());
}
"pre" => {
push_directive!(v_pre, ());
}
"memo" => {
let value = expect_value!();
push_directive_js!(v_memo, value);
}
"cloak" => {
push_directive!(v_cloak, ());
}
_ => 'custom: {
let argument = convert_argument(argument, is_dynamic, input)?;
let Some(value) = value else {
let directives = get_directives!();
directives.custom.push(VCustomDirective {
name: directive_name.into(),
argument,
modifiers,
value: None,
});
break 'custom;
};
match parse_js(value, 0, 0) {
Ok(parsed) => {
let directives = get_directives!();
directives.custom.push(VCustomDirective {
name: directive_name.into(),
argument,
modifiers,
value: Some(parsed),
});
}
Result::Err(_) => {}
}
}
};
Ok((input, ()))
}
fn convert_argument<'s>(
argument: Option<FervidAtom>,
is_dynamic: bool,
input: &'s str,
) -> Result<Option<StrOrExpr>, nom::Err<nom::error::Error<&'s str>>> {
match argument {
Some(raw_arg) => {
if is_dynamic {
let Ok(dynamic_argument) = parse_js(&raw_arg, 0, 0) else {
return Err(nom::Err::Error(nom::error::Error::from_error_kind(
input,
ErrorKind::Fail,
)));
};
Ok(Some(StrOrExpr::Expr(dynamic_argument)))
} else {
Ok(Some(StrOrExpr::Str(raw_arg)))
}
}
None => Ok(None),
}
}
fn split_itervar_and_iterable<'a>(raw: &'a str) -> Option<(&'a str, &'a str)> {
let mut split = raw.splitn(2, " in ");
if let (Some(itervar), Some(iterable)) = (split.next(), split.next()) {
return Some((itervar.trim(), iterable.trim()));
}
let mut split = raw.splitn(2, " of ");
if let (Some(itervar), Some(iterable)) = (split.next(), split.next()) {
return Some((itervar.trim(), iterable.trim()));
}
None
}