WIP: remove FC
This commit is contained in:
1 parent
3740f81383
commit
92d9521a73
24 files changed
+801
-451
No files matched your search
+120
-103
@@ -9,80 +9,13 @@ use syn::{
|
||||
parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility,
|
||||
};
|
||||
|
||||
pub fn function_component_impl(
|
||||
// name: FunctionComponentName,
|
||||
component: FunctionComponent,
|
||||
) -> syn::Result<TokenStream> {
|
||||
// let FunctionComponentName { component_name } = name;
|
||||
|
||||
let FunctionComponent {
|
||||
block,
|
||||
props_type,
|
||||
arg,
|
||||
vis,
|
||||
attrs,
|
||||
name: function_name,
|
||||
return_type,
|
||||
} = component;
|
||||
|
||||
// if function_name == component_name {
|
||||
// return Err(syn::Error::new_spanned(
|
||||
// component_name,
|
||||
// "the component must not have the same name as the function",
|
||||
// ));
|
||||
// }
|
||||
|
||||
let quoted = quote! {
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
mod __component_blah {
|
||||
use super::*;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct Props<'a> {
|
||||
name: &'a str
|
||||
}
|
||||
|
||||
pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
|
||||
// Destructure the props into the parent scope
|
||||
// todo: handle expansion of lifetimes
|
||||
let Props {
|
||||
name
|
||||
} = ctx.props;
|
||||
|
||||
#block
|
||||
}
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
pub use __component_blah::component as #function_name;
|
||||
};
|
||||
// let quoted = quote! {
|
||||
// #[doc(hidden)]
|
||||
// #[allow(non_camel_case_types)]
|
||||
// #vis struct #function_name;
|
||||
|
||||
// impl ::yew_functional::FunctionProvider for #function_name {
|
||||
// type TProps = #props_type;
|
||||
|
||||
// fn run(#arg) -> #ret_type {
|
||||
// #block
|
||||
// }
|
||||
// }
|
||||
|
||||
// #(#attrs)*
|
||||
// #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>;
|
||||
// };
|
||||
Ok(quoted)
|
||||
}
|
||||
|
||||
/// A parsed version of the user's input
|
||||
pub struct FunctionComponent {
|
||||
// The actual contents of the function
|
||||
block: Box<Block>,
|
||||
|
||||
// The user's props type
|
||||
props_type: Box<Type>,
|
||||
|
||||
// // The user's props type
|
||||
// props_type: Box<Type>,
|
||||
arg: FnArg,
|
||||
vis: Visibility,
|
||||
attrs: Vec<Attribute>,
|
||||
@@ -117,7 +50,7 @@ impl Parse for FunctionComponent {
|
||||
.unwrap_or_else(|| syn::parse_quote! { _: &() });
|
||||
|
||||
// Extract the "context" object
|
||||
let props_type = validate_context_arg(&first_arg)?;
|
||||
// let props_type = validate_context_arg(&first_arg)?;
|
||||
|
||||
/*
|
||||
Extract the rest of the function arguments into a struct body
|
||||
@@ -159,7 +92,7 @@ impl Parse for FunctionComponent {
|
||||
let name = sig.ident;
|
||||
|
||||
Ok(Self {
|
||||
props_type,
|
||||
// props_type,
|
||||
block,
|
||||
arg: first_arg,
|
||||
vis,
|
||||
@@ -169,6 +102,89 @@ impl Parse for FunctionComponent {
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ToTokens for FunctionComponent {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
// let FunctionComponentName { component_name } = name;
|
||||
|
||||
let FunctionComponent {
|
||||
block,
|
||||
// props_type,
|
||||
arg,
|
||||
vis,
|
||||
attrs,
|
||||
name: function_name,
|
||||
return_type,
|
||||
} = self;
|
||||
|
||||
// if function_name == component_name {
|
||||
// return Err(syn::Error::new_spanned(
|
||||
// component_name,
|
||||
// "the component must not have the same name as the function",
|
||||
// ));
|
||||
// }
|
||||
|
||||
let quoted = quote! {
|
||||
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(PartialEq)]
|
||||
pub struct #function_name<'a> {
|
||||
// and some other attrs
|
||||
___p: std::marker::PhantomData<&'a ()>
|
||||
}
|
||||
|
||||
impl<'a> FC for #function_name<'a> {
|
||||
fn render(ctx: Context<'_>, props: &#function_name<'a>) -> DomTree {
|
||||
let #function_name {
|
||||
..
|
||||
} = props;
|
||||
|
||||
#block
|
||||
}
|
||||
}
|
||||
|
||||
// mod __component_blah {
|
||||
// use super::*;
|
||||
|
||||
// #[derive(PartialEq)]
|
||||
// pub struct Props<'a> {
|
||||
// name: &'a str
|
||||
// }
|
||||
|
||||
// pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
|
||||
// // Destructure the props into the parent scope
|
||||
// // todo: handle expansion of lifetimes
|
||||
// let Props {
|
||||
// name
|
||||
// } = ctx.props;
|
||||
|
||||
// #block
|
||||
// }
|
||||
// }
|
||||
// #[allow(non_snake_case)]
|
||||
// pub use __component_blah::component as #function_name;
|
||||
};
|
||||
|
||||
quoted.to_tokens(tokens);
|
||||
// let quoted = quote! {
|
||||
// #[doc(hidden)]
|
||||
// #[allow(non_camel_case_types)]
|
||||
// #vis struct #function_name;
|
||||
|
||||
// impl ::yew_functional::FunctionProvider for #function_name {
|
||||
// type TProps = #props_type;
|
||||
|
||||
// fn run(#arg) -> #ret_type {
|
||||
// #block
|
||||
// }
|
||||
// }
|
||||
|
||||
// #(#attrs)*
|
||||
// #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>;
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the user's input is actually a functional component
|
||||
pub fn ensure_fn_block(item: Item) -> syn::Result<ItemFn> {
|
||||
@@ -228,7 +244,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
|
||||
match output {
|
||||
ReturnType::Default => Err(syn::Error::new_spanned(
|
||||
output,
|
||||
"function components must return `dioxus::VNode`",
|
||||
"function components must return a `DomTree`",
|
||||
)),
|
||||
ReturnType::Type(_, ty) => Ok(ty),
|
||||
}
|
||||
@@ -268,39 +284,40 @@ pub fn validate_signature(sig: Signature) -> syn::Result<Signature> {
|
||||
Ok(sig)
|
||||
}
|
||||
|
||||
pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
|
||||
if let FnArg::Typed(arg) = first_arg {
|
||||
// Input arg is a reference to an &mut Context
|
||||
if let Type::Reference(ty) = &*arg.ty {
|
||||
if ty.lifetime.is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&ty.lifetime,
|
||||
"reference must not have a lifetime",
|
||||
));
|
||||
}
|
||||
// pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
|
||||
// if let FnArg::Typed(arg) = first_arg {
|
||||
// // if let Type::R
|
||||
// // Input arg is a reference to an &mut Context
|
||||
// // if let Type::Reference(ty) = &*arg.ty {
|
||||
// // if ty.lifetime.is_some() {
|
||||
// // return Err(syn::Error::new_spanned(
|
||||
// // &ty.lifetime,
|
||||
// // "reference must not have a lifetime",
|
||||
// // ));
|
||||
// // }
|
||||
|
||||
if ty.mutability.is_some() {
|
||||
return Err(syn::Error::new_spanned(
|
||||
&ty.mutability,
|
||||
"reference must not be mutable",
|
||||
));
|
||||
}
|
||||
// // if ty.mutability.is_some() {
|
||||
// // return Err(syn::Error::new_spanned(
|
||||
// // &ty.mutability,
|
||||
// // "reference must not be mutable",
|
||||
// // ));
|
||||
// // }
|
||||
|
||||
Ok(ty.elem.clone())
|
||||
} else {
|
||||
let msg = format!(
|
||||
"expected a reference to a `Context` object (try: `&mut {}`)",
|
||||
arg.ty.to_token_stream()
|
||||
);
|
||||
return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
|
||||
}
|
||||
} else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
first_arg,
|
||||
"function components can't accept a receiver",
|
||||
));
|
||||
}
|
||||
}
|
||||
// // Ok(ty.elem.clone())
|
||||
// // } else {
|
||||
// // let msg = format!(
|
||||
// // "expected a reference to a `Context` object (try: `&mut {}`)",
|
||||
// // arg.ty.to_token_stream()
|
||||
// // );
|
||||
// // return Err(syn::Error::new_spanned(arg.ty.clone(), msg));
|
||||
// // }
|
||||
// } else {
|
||||
// return Err(syn::Error::new_spanned(
|
||||
// first_arg,
|
||||
// "function components can't accept a receiver",
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn collect_inline_args() {}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use ::proc_macro::TokenStream;
|
||||
use ::quote::{quote, ToTokens};
|
||||
use ::std::ops::Not;
|
||||
use ::syn::{
|
||||
@@ -6,10 +5,7 @@ use ::syn::{
|
||||
punctuated::Punctuated,
|
||||
*,
|
||||
};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
// #[macro_use]
|
||||
// mod macros {
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
// #[cfg(not(feature = "verbose-expansions"))]
|
||||
macro_rules! debug_input {
|
||||
@@ -18,18 +14,6 @@ macro_rules! debug_input {
|
||||
};
|
||||
}
|
||||
|
||||
// #[cfg(feature = "verbose-expansions")]
|
||||
macro_rules! debug_input {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
expr => {
|
||||
eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr);
|
||||
expr
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// #[cfg(not(feature = "verbose-expansions"))]
|
||||
macro_rules! debug_output {
|
||||
($expr:expr) => {
|
||||
@@ -38,19 +22,30 @@ macro_rules! debug_output {
|
||||
}
|
||||
|
||||
// #[cfg(feature = "verbose-expansions")]
|
||||
macro_rules! debug_output {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
expr => {
|
||||
eprintln!("=>\n{}\n-------------------\n", expr);
|
||||
expr
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// macro_rules! debug_input {
|
||||
// ($expr:expr) => {
|
||||
// match $expr {
|
||||
// expr => {
|
||||
// eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr);
|
||||
// expr
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
|
||||
// #[cfg(feature = "verbose-expansions")]
|
||||
// macro_rules! debug_output {
|
||||
// ($expr:expr) => {
|
||||
// match $expr {
|
||||
// expr => {
|
||||
// eprintln!("=>\n{}\n-------------------\n", expr);
|
||||
// expr
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
|
||||
let IfmtInput {
|
||||
mut format_literal,
|
||||
mut positional_args,
|
||||
@@ -121,7 +116,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
|
||||
arg,
|
||||
) {
|
||||
Ok(segments) => segments.into_iter().collect(),
|
||||
Err(err) => return err.to_compile_error().into(),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
};
|
||||
match segments.len() {
|
||||
@@ -151,7 +146,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
|
||||
format_args!("{}", positional_args.len()),
|
||||
)
|
||||
.expect("`usize` or `char` Display impl cannot panic");
|
||||
let segments: Punctuated<TokenStream2, Token![.]> = segments
|
||||
let segments: Punctuated<TokenStream, Token![.]> = segments
|
||||
.into_iter()
|
||||
.map(|it| match it {
|
||||
Segment::Ident(ident) => ident.into_token_stream(),
|
||||
@@ -172,13 +167,13 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream {
|
||||
});
|
||||
format_literal = LitStr::new(out_format_literal, format_literal.span());
|
||||
|
||||
TokenStream::from(debug_output!(quote! {
|
||||
Ok(TokenStream::from(debug_output!(quote! {
|
||||
format_args!(
|
||||
#format_literal
|
||||
#(, #positional_args)*
|
||||
#(, #named_args)*
|
||||
)
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // dumb compiler does not see the struct being used...
|
||||
|
||||
@@ -6,6 +6,7 @@ mod fc;
|
||||
mod htm;
|
||||
mod ifmt;
|
||||
mod rsxt;
|
||||
mod util;
|
||||
|
||||
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
|
||||
/// We aim to keep functional parity with html templates.
|
||||
@@ -27,31 +28,34 @@ pub fn rsx(s: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
// #[proc_macro_attribute]
|
||||
// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
/// Label a function or static closure as a functional component.
|
||||
/// This macro reduces the need to create a separate properties struct.
|
||||
///
|
||||
/// Using this macro is fun and simple
|
||||
///
|
||||
/// ```ignore
|
||||
///
|
||||
/// #[fc]
|
||||
/// fn Example(ctx: Context, name: &str) -> DomTree {
|
||||
/// ctx.render(rsx! { h1 {"hello {name}"} })
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
use fc::{function_component_impl, FunctionComponent};
|
||||
|
||||
let item = parse_macro_input!(item as FunctionComponent);
|
||||
|
||||
function_component_impl(item)
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
.into()
|
||||
match syn::parse::<fc::FunctionComponent>(item) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
||||
use ifmt::*;
|
||||
|
||||
let item = parse_macro_input!(input as IfmtInput);
|
||||
|
||||
// #[allow(unused)]
|
||||
// const FUNCTION_NAME: &str = "format_args_f";
|
||||
|
||||
// debug_input!(&input);
|
||||
|
||||
ifmt::format_args_f_impl(item)
|
||||
// .unwrap_or_else(|err| err.to_compile_error())
|
||||
// .into()
|
||||
format_args_f_impl(item)
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
+183
-87
@@ -88,6 +88,7 @@ impl ToTokens for RsxRender {
|
||||
enum Node {
|
||||
Element(Element),
|
||||
Text(TextNode),
|
||||
Component(Component),
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&Node> {
|
||||
@@ -95,28 +96,29 @@ impl ToTokens for ToToksCtx<&Node> {
|
||||
match &self.inner {
|
||||
Node::Element(el) => self.recurse(el).to_tokens(tokens),
|
||||
Node::Text(txt) => self.recurse(txt).to_tokens(tokens),
|
||||
Node::Component(c) => self.recurse(c).to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl Node {
|
||||
// fn peek(s: ParseStream) -> bool {
|
||||
// (s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr)
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Parse for Node {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
let fork = s.fork();
|
||||
let fork1 = s.fork();
|
||||
let fork2 = s.fork();
|
||||
|
||||
let ret = if let Ok(text) = fork.parse::<TextNode>() {
|
||||
s.advance_to(&fork);
|
||||
Ok(Self::Text(text))
|
||||
} else if let Ok(el) = s.parse::<Element>() {
|
||||
Ok(Self::Element(el))
|
||||
// todo: map issues onto the second fork if any arise
|
||||
// it's unlikely that textnodes or components would fail?
|
||||
|
||||
let ret = if let Ok(text) = fork1.parse::<TextNode>() {
|
||||
s.advance_to(&fork1);
|
||||
Self::Text(text)
|
||||
} else if let Ok(element) = fork2.parse::<Element>() {
|
||||
s.advance_to(&fork2);
|
||||
Self::Element(element)
|
||||
} else if let Ok(comp) = s.parse::<Component>() {
|
||||
Self::Component(comp)
|
||||
} else {
|
||||
// TODO: Span information
|
||||
panic!("Not a valid child node");
|
||||
return Err(Error::new(s.span(), "Failed to parse as a valid child"));
|
||||
};
|
||||
|
||||
// consume comma if it exists
|
||||
@@ -124,7 +126,120 @@ impl Parse for Node {
|
||||
if s.peek(Token![,]) {
|
||||
let _ = s.parse::<Token![,]>();
|
||||
}
|
||||
ret
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
struct Component {
|
||||
name: Ident,
|
||||
body: Vec<Field>,
|
||||
// attrs: Vec<Attr>,
|
||||
children: MaybeExpr<Vec<Node>>,
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&Component> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.inner.name;
|
||||
|
||||
// let mut toks = quote! {};
|
||||
|
||||
// for attr in self.inner.attrs.iter() {
|
||||
// self.recurse(attr).to_tokens(&mut toks);
|
||||
// }
|
||||
|
||||
let body = &self.inner.body;
|
||||
// panic!("tokens are {:#?}", toks);
|
||||
|
||||
// tokens.append_all(quote! {
|
||||
// dioxus::builder::virtual_child(ctx, #name{ #body, ___p: PhantomData{} })
|
||||
// // dioxus::builder::ElementBuilder::new(ctx, #name)
|
||||
// });
|
||||
|
||||
// no children right now
|
||||
|
||||
// match &self.inner.children {
|
||||
// MaybeExpr::Expr(expr) => tokens.append_all(quote! {
|
||||
// .children(#expr)
|
||||
// }),
|
||||
// MaybeExpr::Literal(nodes) => {
|
||||
// let mut children = nodes.iter();
|
||||
// if let Some(child) = children.next() {
|
||||
// let mut inner_toks = TokenStream2::new();
|
||||
// self.recurse(child).to_tokens(&mut inner_toks);
|
||||
// while let Some(child) = children.next() {
|
||||
// quote!(,).to_tokens(&mut inner_toks);
|
||||
// self.recurse(child).to_tokens(&mut inner_toks);
|
||||
// }
|
||||
// tokens.append_all(quote! {
|
||||
// .children([#inner_toks])
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// tokens.append_all(quote! {
|
||||
// .finish()
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Component {
|
||||
fn parse(s: ParseStream) -> Result<Self> {
|
||||
// TODO: reject anything weird/nonstandard
|
||||
// we want names *only*
|
||||
let name = Ident::parse_any(s)?;
|
||||
|
||||
if crate::util::is_valid_tag(&name.to_string()) {
|
||||
return Err(Error::new(
|
||||
name.span(),
|
||||
"Components cannot share names with valid HTML tags",
|
||||
));
|
||||
}
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in s);
|
||||
let mut fields: Vec<Field> = Vec::new();
|
||||
|
||||
'parsing: loop {
|
||||
// [1] Break if empty
|
||||
if content.is_empty() {
|
||||
break 'parsing;
|
||||
}
|
||||
}
|
||||
|
||||
// let body = content.parse()?;
|
||||
|
||||
// eventually we'll parse the attrs
|
||||
// let mut attrs: Vec<Attr> = vec![];
|
||||
let mut children: Vec<Node> = vec![];
|
||||
// parse_element_content(content, &mut attrs, &mut children);
|
||||
|
||||
let children = MaybeExpr::Literal(children);
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
body,
|
||||
// attrs,
|
||||
children,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// the struct's fields info
|
||||
pub struct Field {
|
||||
name: Ident,
|
||||
content: Ident,
|
||||
}
|
||||
|
||||
impl Parse for Field {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ToToksCtx<&Field> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,12 +257,12 @@ struct Element {
|
||||
|
||||
impl ToTokens for ToToksCtx<&Element> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
// todo!()
|
||||
// // let ctx = self.ctx;
|
||||
let name = &self.inner.name.to_string();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
dioxus::builder::ElementBuilder::new(ctx, #name)
|
||||
});
|
||||
|
||||
for attr in self.inner.attrs.iter() {
|
||||
self.recurse(attr).to_tokens(tokens);
|
||||
}
|
||||
@@ -182,84 +297,20 @@ impl Parse for Element {
|
||||
// we want names *only*
|
||||
let name = Ident::parse_any(s)?;
|
||||
|
||||
if !crate::util::is_valid_tag(&name.to_string()) {
|
||||
return Err(Error::new(name.span(), "Not a valid Html tag"));
|
||||
}
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in s);
|
||||
|
||||
let mut attrs = vec![];
|
||||
let mut attrs: Vec<Attr> = vec![];
|
||||
let mut children: Vec<Node> = vec![];
|
||||
|
||||
'parsing: loop {
|
||||
// todo move this around into a more functional style
|
||||
// [1] Break if empty
|
||||
// [2] Try to consume an attr (with comma)
|
||||
// [3] Try to consume a child node (with comma)
|
||||
// [4] Try to consume brackets as anything thats Into<Node>
|
||||
// [last] Fail if none worked
|
||||
|
||||
// [1] Break if empty
|
||||
if content.is_empty() {
|
||||
break 'parsing;
|
||||
}
|
||||
|
||||
// [2] Try to consume an attr
|
||||
let fork = content.fork();
|
||||
if let Ok(attr) = fork.parse::<Attr>() {
|
||||
// make sure to advance or your computer will become a spaceheater :)
|
||||
content.advance_to(&fork);
|
||||
attrs.push(attr);
|
||||
continue 'parsing;
|
||||
}
|
||||
|
||||
// [3] Try to consume a child node
|
||||
let fork = content.fork();
|
||||
if let Ok(node) = fork.parse::<Node>() {
|
||||
// make sure to advance or your computer will become a spaceheater :)
|
||||
content.advance_to(&fork);
|
||||
children.push(node);
|
||||
continue 'parsing;
|
||||
}
|
||||
|
||||
// [4] TODO: Parsing brackets
|
||||
// let fork = content.fork();
|
||||
// if let Ok(el) = fork.parse() {
|
||||
// children.push(el);
|
||||
// continue 'parsing;
|
||||
// }
|
||||
|
||||
// todo: pass a descriptive error onto the offending tokens
|
||||
panic!("Entry is not an attr or element\n {}", content)
|
||||
}
|
||||
parse_element_content(content, &mut attrs, &mut children);
|
||||
|
||||
let children = MaybeExpr::Literal(children);
|
||||
// let children = MaybeExpr::Literal(Vec::new());
|
||||
// // Contents of an element can either be a brace (in which case we just copy verbatim), or a
|
||||
// // sequence of nodes.
|
||||
// let children = if s.peek(token::Brace) {
|
||||
// // expr
|
||||
// let content;
|
||||
// syn::braced!(content in s);
|
||||
// MaybeExpr::Expr(content.parse()?)
|
||||
// } else {
|
||||
// // nodes
|
||||
// let mut children = vec![];
|
||||
// while !(s.peek(Token![<]) && s.peek2(Token![/])) {
|
||||
// children.push(s.parse()?);
|
||||
// }
|
||||
// MaybeExpr::Literal(children)
|
||||
// };
|
||||
|
||||
// // closing element
|
||||
// s.parse::<Token![<]>()?;
|
||||
// s.parse::<Token![/]>()?;
|
||||
// let close = Ident::parse_any(s)?;
|
||||
// if close.to_string() != name.to_string() {
|
||||
// return Err(Error::new_spanned(
|
||||
// close,
|
||||
// "closing element does not match opening",
|
||||
// ));
|
||||
// }
|
||||
// s.parse::<Token![>]>()?;
|
||||
Ok(Self {
|
||||
name,
|
||||
attrs,
|
||||
@@ -268,6 +319,51 @@ impl Parse for Element {
|
||||
}
|
||||
}
|
||||
|
||||
// used by both vcomponet and velement to parse the contents of the elements into attras and children
|
||||
fn parse_element_content(content: ParseBuffer, attrs: &mut Vec<Attr>, children: &mut Vec<Node>) {
|
||||
'parsing: loop {
|
||||
// todo move this around into a more functional style
|
||||
// [1] Break if empty
|
||||
// [2] Try to consume an attr (with comma)
|
||||
// [3] Try to consume a child node (with comma)
|
||||
// [4] Try to consume brackets as anything thats Into<Node>
|
||||
// [last] Fail if none worked
|
||||
|
||||
// [1] Break if empty
|
||||
if content.is_empty() {
|
||||
break 'parsing;
|
||||
}
|
||||
|
||||
// [2] Try to consume an attr
|
||||
let fork = content.fork();
|
||||
if let Ok(attr) = fork.parse::<Attr>() {
|
||||
// make sure to advance or your computer will become a spaceheater :)
|
||||
content.advance_to(&fork);
|
||||
attrs.push(attr);
|
||||
continue 'parsing;
|
||||
}
|
||||
|
||||
// [3] Try to consume a child node
|
||||
let fork = content.fork();
|
||||
if let Ok(node) = fork.parse::<Node>() {
|
||||
// make sure to advance or your computer will become a spaceheater :)
|
||||
content.advance_to(&fork);
|
||||
children.push(node);
|
||||
continue 'parsing;
|
||||
}
|
||||
|
||||
// [4] TODO: Parsing brackets
|
||||
// let fork = content.fork();
|
||||
// if let Ok(el) = fork.parse() {
|
||||
// children.push(el);
|
||||
// continue 'parsing;
|
||||
// }
|
||||
|
||||
// todo: pass a descriptive error onto the offending tokens
|
||||
panic!("Entry is not an attr or element\n {}", content)
|
||||
}
|
||||
}
|
||||
|
||||
/// =======================================
|
||||
/// Parse a VElement's Attributes
|
||||
/// =======================================
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// use lazy_static::lazy_static;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::hash_set::HashSet;
|
||||
|
||||
static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||
[
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"command",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"keygen",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
});
|
||||
|
||||
/// Whether or not this tag is valid
|
||||
///
|
||||
/// ```
|
||||
/// use html_validation::is_valid_tag;
|
||||
///
|
||||
/// assert_eq!(is_valid_tag("br"), true);
|
||||
///
|
||||
/// assert_eq!(is_valid_tag("random"), false);
|
||||
/// ```
|
||||
pub fn is_valid_tag(tag: &str) -> bool {
|
||||
VALID_TAGS.contains(tag)
|
||||
}
|
||||
Reference in new issue
Block a user