1
0
Evan Almloff 2 жил өмнө
parent
commit
87794b5039

+ 2 - 1
packages/router-macro/src/lib.rs

@@ -13,13 +13,14 @@ use proc_macro2::TokenStream as TokenStream2;
 use crate::{layout::LayoutId, route_tree::RouteTree};
 
 mod layout;
+mod macro2;
 mod nest;
 mod query;
 mod route;
 mod route_tree;
 mod segment;
 
-#[proc_macro_derive(Routable, attributes(route, nest, end_nest, layout, end_layout))]
+#[proc_macro_derive(Routable)]
 pub fn routable(input: TokenStream) -> TokenStream {
     let routes_enum = parse_macro_input!(input as syn::ItemEnum);
 

+ 286 - 0
packages/router-macro/src/macro2.rs

@@ -0,0 +1,286 @@
+use syn::{braced, parenthesized, parse::Parse, Expr, Ident, LitStr, Path, Token};
+
+#[test]
+fn parses() {
+    use quote::quote;
+
+    let tokens = quote! {
+        // The name of the enum
+        Route,
+        // All nests that have dynamic segments must have a name used to generate the enum
+        route(User, "user" / user_id: usize) {
+            route(Product, "product" / product_id: usize / dynamic: usize ) {
+                // Render creates a new route (that will be included in the enum) and is rendered with the given component
+                // The component uses the struct of the parent route as a prop (in this case, Product)
+                render(Other)
+            }
+
+            // You can nest routes inside a layout to wrap them in a component that accepts the struct of the parent route as a prop (in this case, User)
+            layout(UserFrame) {
+                route(Route1Props, "hello_world" / dynamic: usize ) {
+                    // (Accepts Route1Props as a prop)
+                    render(Route1)
+                }
+
+                // You can opt out of the layout by using !layout
+                !layout(UserFrame) {
+                    route(Route2Props, "hello_world" / dynamic: usize ) {
+                        // (Accepts Route2Props as a prop)
+                        render(Route2)
+                    }
+                }
+            }
+        }
+
+        route(Route3Props, "hello_world" / dynamic: usize ) {
+            // (Accepts Route3Props as a prop)
+            render(Route3)
+        }
+
+        route(RedirectData, dynamic: usize / extra: String) {
+            // Redirects accept a function that receives the struct of the parent route and returns the new route
+            redirect(|data: RedirectData| todo!() )
+        }
+    };
+
+    let _ = syn::parse2::<RouteTree>(tokens).unwrap();
+}
+
+struct RouteTree {
+    name: Ident,
+    roots: Vec<RouteSegment>,
+}
+
+impl Parse for RouteTree {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let name = input.parse()?;
+        let _ = input.parse::<Token![,]>();
+
+        let mut roots = Vec::new();
+        while !input.is_empty() {
+            roots.push(input.parse()?);
+        }
+        Ok(Self { name, roots })
+    }
+}
+
+enum RouteSegment {
+    Route(Route),
+    Layout(Layout),
+    Render(Render),
+    Redirect(Redirect),
+}
+
+impl Parse for RouteSegment {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let lookahead = input.lookahead1();
+        if lookahead.peek(syn::Token![!]) {
+            input.parse::<Token![!]>()?;
+            let ident: Ident = input.parse()?;
+            if ident == "layout" {
+                let mut layout: Layout = input.parse()?;
+                layout.opt_out = true;
+                Ok(RouteSegment::Layout(layout))
+            } else {
+                Err(lookahead.error())
+            }
+        } else if lookahead.peek(syn::Ident) {
+            let ident: Ident = input.parse()?;
+            if ident == "route" {
+                let route = input.parse()?;
+                Ok(RouteSegment::Route(route))
+            } else if ident == "layout" {
+                let layout = input.parse()?;
+                Ok(RouteSegment::Layout(layout))
+            } else if ident == "render" {
+                let render = input.parse()?;
+                Ok(RouteSegment::Render(render))
+            } else if ident == "redirect" {
+                let redirect = input.parse()?;
+                Ok(RouteSegment::Redirect(redirect))
+            } else {
+                Err(lookahead.error())
+            }
+        } else {
+            Err(lookahead.error())
+        }
+    }
+}
+
+struct Render {
+    component: Path,
+}
+
+impl Parse for Render {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let inner;
+        parenthesized!(inner in input);
+        let component = inner.parse()?;
+
+        Ok(Self { component })
+    }
+}
+
+struct Layout {
+    opt_out: bool,
+    component: Path,
+    children: Vec<RouteSegment>,
+}
+
+impl Parse for Layout {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let inner;
+        parenthesized!(inner in input);
+        let component = inner.parse()?;
+
+        let content;
+        braced!(content in input);
+        let mut children = Vec::new();
+        while !content.is_empty() {
+            children.push(content.parse()?);
+        }
+
+        Ok(Self {
+            opt_out: false,
+            component,
+            children,
+        })
+    }
+}
+
+struct Route {
+    name: Ident,
+    path: RoutePath,
+    children: Vec<RouteSegment>,
+}
+
+impl Parse for Route {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let inner;
+        parenthesized!(inner in input);
+        let name = inner.parse()?;
+        inner.parse::<Token![,]>()?;
+        let path = inner.parse()?;
+
+        let content;
+        braced!(content in input);
+        let mut children = Vec::new();
+        while !content.is_empty() {
+            children.push(content.parse()?);
+        }
+
+        Ok(Self {
+            name,
+            path,
+            children,
+        })
+    }
+}
+
+struct RoutePath {
+    segments: Vec<RoutePathSegment>,
+    query: Option<QuerySegment>,
+}
+
+impl Parse for RoutePath {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        // parse all segments first
+        let mut segments = Vec::new();
+        // remove any leading slash
+        if input.peek(syn::Token![/]) {
+            input.parse::<syn::Token![/]>()?;
+        }
+
+        while !input.is_empty() {
+            let peak = input.lookahead1();
+            // check if the next segment is a query
+            if peak.peek(syn::Token![?]) {
+                break;
+            } else if peak.peek(syn::Token![/]) {
+                input.parse::<syn::Token![/]>()?;
+            } else if peak.peek(syn::Ident) || peak.peek(syn::Token![...]) || peak.peek(syn::LitStr)
+            {
+                // parse the segment
+                segments.push(input.parse()?);
+            } else {
+                return Err(peak.error());
+            }
+        }
+        // then parse the query
+        let query = if input.peek(syn::Token![?]) {
+            Some(input.parse()?)
+        } else {
+            None
+        };
+        Ok(Self { segments, query })
+    }
+}
+
+enum RoutePathSegment {
+    Static(String),
+    Dynamic(Ident, Path),
+    CatchAll(Ident, Path),
+}
+
+impl Parse for RoutePathSegment {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let lookahead = input.lookahead1();
+        if lookahead.peek(Token![...]) {
+            input.parse::<Token![...]>()?;
+            let name: Ident = input.parse()?;
+            input.parse::<Token![:]>()?;
+            let type_: Path = input.parse()?;
+
+            // parse the /
+            let _ = input.parse::<Token![/]>();
+
+            Ok(RoutePathSegment::CatchAll(name, type_))
+        } else if lookahead.peek(LitStr) {
+            let lit: LitStr = input.parse()?;
+
+            // parse the /
+            let _ = input.parse::<Token![/]>();
+
+            Ok(RoutePathSegment::Static(lit.value()))
+        } else if lookahead.peek(Ident) {
+            let ident: Ident = input.parse()?;
+            input.parse::<Token![:]>()?;
+            let type_: Path = input.parse()?;
+
+            // parse the /
+            let _ = input.parse::<Token![/]>();
+
+            Ok(RoutePathSegment::Dynamic(ident, type_))
+        } else {
+            Err(lookahead.error())
+        }
+    }
+}
+
+struct QuerySegment {
+    name: Ident,
+    type_: Path,
+}
+
+impl Parse for QuerySegment {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        input.parse::<syn::Token![?]>()?;
+        let name = input.parse()?;
+        input.parse::<syn::Token![:]>()?;
+        let type_ = input.parse()?;
+        Ok(Self { name, type_ })
+    }
+}
+
+struct Redirect {
+    function: Expr,
+}
+
+impl Parse for Redirect {
+    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+        let inner;
+        parenthesized!(inner in input);
+        let function = inner.parse()?;
+        Ok(Self { function })
+    }
+}