wip: serious refactor with const generics

This commit is contained in:
Jonathan Kelley committed 2021-07-21 22:55:04 -04:00
1 parent 308414f5ef
commit 160d86abbe
16 files changed
+492 -286

No files matched your search

+167 -69
View File
@@ -19,18 +19,18 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
token, Expr, ExprClosure, Ident, Result, Token,
token, Error, Expr, ExprClosure, Ident, Result, Token,
};
pub struct Component {
pub struct Component<const AS: HTML_OR_RSX> {
// accept any path-like argument
name: syn::Path,
body: Vec<ComponentField>,
children: Vec<BodyNode>,
body: Vec<ComponentField<AS>>,
children: Vec<BodyNode<AS>>,
manual_props: Option<Expr>,
}
impl Parse for Component {
impl Parse for Component<AS_RSX> {
fn parse(stream: ParseStream) -> Result<Self> {
// let name = s.parse::<syn::ExprPath>()?;
// todo: look into somehow getting the crate/super/etc
@@ -41,21 +41,45 @@ impl Parse for Component {
let content: ParseBuffer;
syn::braced!(content in stream);
let mut body: Vec<ComponentField> = Vec::new();
let mut children: Vec<BodyNode> = Vec::new();
let mut body: Vec<ComponentField<AS_RSX>> = Vec::new();
let mut children: Vec<BodyNode<AS_RSX>> = Vec::new();
let mut manual_props = None;
parse_component_body(
&content,
&BodyParseConfig {
allow_children: true,
allow_fields: true,
allow_manual_props: true,
},
&mut body,
&mut children,
&mut manual_props,
)?;
let cfg: BodyParseConfig<AS_RSX> = BodyParseConfig {
allow_children: true,
allow_fields: true,
allow_manual_props: true,
};
cfg.parse_component_body(&content, &mut body, &mut children, &mut manual_props)?;
Ok(Self {
name,
body,
children,
manual_props,
})
}
}
impl Parse for Component<AS_HTML> {
fn parse(stream: ParseStream) -> Result<Self> {
let name = syn::Path::parse_mod_style(stream)?;
// parse the guts
let content: ParseBuffer;
syn::braced!(content in stream);
let mut body: Vec<ComponentField<AS_HTML>> = Vec::new();
let mut children: Vec<BodyNode<AS_HTML>> = Vec::new();
let mut manual_props = None;
let cfg: BodyParseConfig<AS_HTML> = BodyParseConfig {
allow_children: true,
allow_fields: true,
allow_manual_props: true,
};
cfg.parse_component_body(&content, &mut body, &mut children, &mut manual_props)?;
Ok(Self {
name,
@@ -66,64 +90,117 @@ impl Parse for Component {
}
}
pub struct BodyParseConfig {
pub struct BodyParseConfig<const AS: HTML_OR_RSX> {
pub allow_fields: bool,
pub allow_children: bool,
pub allow_manual_props: bool,
}
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
content: &ParseBuffer,
cfg: &BodyParseConfig,
body: &mut Vec<ComponentField>,
children: &mut Vec<BodyNode>,
manual_props: &mut Option<Expr>,
) -> Result<()> {
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
if content.peek(Token![..]) {
if !cfg.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
impl BodyParseConfig<AS_RSX> {
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
&self,
content: &ParseBuffer,
body: &mut Vec<ComponentField<AS_RSX>>,
children: &mut Vec<BodyNode<AS_RSX>>,
manual_props: &mut Option<Expr>,
) -> Result<()> {
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
content.parse::<Token![..]>()?;
*manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !cfg.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField>()?);
} else {
if !cfg.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
if content.peek(Token![..]) {
if !self.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
}
content.parse::<Token![..]>()?;
*manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !self.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField<AS_RSX>>()?);
} else {
if !self.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode<AS_RSX>>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(())
}
}
impl BodyParseConfig<AS_HTML> {
// todo: unify this body parsing for both elements and components
// both are style rather ad-hoc, though components are currently more configured
pub fn parse_component_body(
&self,
content: &ParseBuffer,
body: &mut Vec<ComponentField<AS_HTML>>,
children: &mut Vec<BodyNode<AS_HTML>>,
manual_props: &mut Option<Expr>,
) -> Result<()> {
'parsing: loop {
// [1] Break if empty
if content.is_empty() {
break 'parsing;
}
if content.peek(Token![..]) {
if !self.allow_manual_props {
return Err(Error::new(
content.span(),
"Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
));
}
content.parse::<Token![..]>()?;
*manual_props = Some(content.parse::<Expr>()?);
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
if !self.allow_fields {
return Err(Error::new(
content.span(),
"Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
));
}
body.push(content.parse::<ComponentField<AS_HTML>>()?);
} else {
if !self.allow_children {
return Err(Error::new(
content.span(),
"This item is not allowed to accept children.",
));
}
children.push(content.parse::<BodyNode<AS_HTML>>()?);
}
// consume comma if it exists
// we don't actually care if there *are* commas between attrs
if content.peek(Token![,]) {
let _ = content.parse::<Token![,]>();
}
}
Ok(())
}
Ok(())
}
impl ToTokens for Component {
impl<const AS: HTML_OR_RSX> ToTokens for Component<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
@@ -195,7 +272,7 @@ impl ToTokens for Component {
}
// the struct's fields info
pub struct ComponentField {
pub struct ComponentField<const AS: HTML_OR_RSX> {
name: Ident,
content: ContentField,
}
@@ -222,7 +299,28 @@ impl ToTokens for ContentField {
}
}
impl Parse for ComponentField {
impl Parse for ComponentField<AS_RSX> {
fn parse(input: ParseStream) -> Result<Self> {
let name = Ident::parse_any(input)?;
input.parse::<Token![:]>()?;
let name_str = name.to_string();
let content = if name_str.starts_with("on") {
if input.peek(token::Brace) {
let content;
syn::braced!(content in input);
ContentField::OnHandlerRaw(content.parse()?)
} else {
ContentField::OnHandler(input.parse()?)
}
} else {
ContentField::ManExpr(input.parse::<Expr>()?)
};
Ok(Self { name, content })
}
}
impl Parse for ComponentField<AS_HTML> {
fn parse(input: ParseStream) -> Result<Self> {
let name = Ident::parse_any(input)?;
input.parse::<Token![:]>()?;
@@ -244,7 +342,7 @@ impl Parse for ComponentField {
}
}
impl ToTokens for ComponentField {
impl<const AS: HTML_OR_RSX> ToTokens for ComponentField<AS> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ComponentField { name, content, .. } = self;
tokens.append_all(quote! {