sark-gen 0.28.7

Sark proc-macro generators
Documentation
use std::collections;

use crate::runtime::paths::Paths;

#[derive(Default)]
pub(super) struct Radix {
    terminal: Option<usize>,
    children: collections::BTreeMap<u8, Radix>,
}

struct Node {
    prefix: Vec<u8>,
    terminal: Option<usize>,
    children: Vec<(u8, usize)>,
}

impl Radix {
    pub(super) fn insert(&mut self, literal: &[u8], state: usize) {
        let mut node = self;
        for &byte in literal {
            node = node.children.entry(byte).or_default();
        }
        node.terminal = Some(state);
    }

    pub(super) fn emit_scan(
        &self,
        paths: &Paths,
        generics: &crate::types::Generics,
        fallback: Option<usize>,
    ) -> proc_macro2::TokenStream {
        use quote::quote;
        let [v_prefix, v_tail] = generics.locals(["__prefix", "__tail"]);
        let [v_rtail] = generics.locals(["__route_tail"]);
        let [v_node] = generics.locals(["__literal_node"]);
        let [v_rbyte, v_found] = generics.locals(["__route_byte", "__route_match"]);

        let (literal, node) = self.chain();
        if let Some(state) = node.terminal
            && node.children.is_empty()
        {
            let len = literal.len();
            let literal = proc_macro2::Literal::byte_string(&literal);
            let mismatch = match fallback {
                Some(next) => {
                    let scan = Self::scan_tail(paths, &v_rtail, &v_found);
                    quote! {
                        #scan
                        ::core::option::Option::Some(#next)
                    }
                }
                None => quote!(::core::option::Option::None),
            };
            return quote! {
                if let ::core::option::Option::Some((#v_prefix, #v_tail)) =
                    #v_rtail.split_first_chunk::<#len>()
                    && #v_prefix == #literal
                    && ::core::matches!(
                        #v_tail.first(),
                        ::core::option::Option::None
                            | ::core::option::Option::Some(b'/' | b'?')
                    )
                {
                    #v_rtail = #v_tail;
                    ::core::option::Option::Some(#state)
                } else {
                    #mismatch
                }
            };
        }

        if self.children.is_empty() && self.terminal.is_none() {
            return match fallback {
                Some(next) => {
                    let scan = Self::scan_tail(paths, &v_rtail, &v_found);
                    quote! {
                        #scan
                        ::core::option::Option::Some(#next)
                    }
                }
                None => quote!(::core::option::Option::None),
            };
        }
        let (nodes, root) = self.flatten();

        let edges = nodes.iter().enumerate().flat_map(|(node, value)| {
            value
                .children
                .iter()
                .map(move |(byte, next)| quote!((#node, #byte) => #next))
        });
        let ends = nodes.iter().enumerate().filter_map(|(node, value)| {
            value
                .terminal
                .map(|state| quote!(#node => ::core::option::Option::Some(#state)))
        });
        let miss = match fallback {
            Some(next) => quote!(::core::option::Option::Some(#next)),
            None => quote!(::core::option::Option::None),
        };
        let label = fallback.map(|_| quote!('__sark_radix:));
        let mismatch = match fallback {
            Some(next) => {
                let scan = Self::scan_tail(paths, &v_rtail, &v_found);
                quote! {
                    #scan
                    break '__sark_radix (::core::option::Option::Some(#next));
                }
            }
            None => quote!(break '__sark_route_match),
        };
        let prefixes = nodes
            .iter()
            .enumerate()
            .filter(|(_, value)| !value.prefix.is_empty())
            .map(|(node, value)| {
                let len = value.prefix.len();
                let literal = proc_macro2::Literal::byte_string(&value.prefix);
                quote! {
                    #node => {
                        if let ::core::option::Option::Some((#v_prefix, #v_tail)) =
                            #v_rtail.split_first_chunk::<#len>()
                            && #v_prefix == #literal
                        {
                            #v_rtail = #v_tail;
                        } else {
                            #mismatch
                        }
                    }
                }
            });
        quote! {
            #label {
                let mut #v_node = #root;
                loop {
                    match #v_node {
                        #( #prefixes, )*
                        _ => {}
                    }
                    let ::core::option::Option::Some((&#v_rbyte, #v_tail)) =
                        #v_rtail.split_first()
                    else {
                        break;
                    };
                    if #v_rbyte == b'/' || #v_rbyte == b'?' {
                        break;
                    }
                    #v_rtail = #v_tail;
                    #v_node = match (#v_node, #v_rbyte) {
                        #( #edges, )*
                        _ => { #mismatch }
                    };
                }
                match #v_node {
                    #( #ends, )*
                    _ => #miss,
                }
            }
        }
    }

    fn flatten(&self) -> (Vec<Node>, usize) {
        let mut nodes = Vec::new();
        let root = self.append(&mut nodes);
        (nodes, root)
    }

    fn append(&self, nodes: &mut Vec<Node>) -> usize {
        let (prefix, node) = self.chain();
        let children = node
            .children
            .iter()
            .map(|(&byte, child)| (byte, child.append(nodes)))
            .collect();
        let id = nodes.len();
        nodes.push(Node {
            prefix,
            terminal: node.terminal,
            children,
        });
        id
    }

    fn chain(&self) -> (Vec<u8>, &Self) {
        let mut literal = Vec::new();
        let mut node = self;
        while node.terminal.is_none() {
            let mut children = node.children.iter();
            let Some((byte, child)) = children.next() else {
                break;
            };
            if children.next().is_some() {
                break;
            }
            literal.push(*byte);
            node = child;
        }
        (literal, node)
    }

    fn scan_tail(paths: &Paths, tail: &syn::Ident, found: &syn::Ident) -> proc_macro2::TokenStream {
        let ns_http = &paths.http;
        use quote::quote;

        quote! {
            #tail = match #ns_http::field::scan::Match::find2(
                b'/',
                b'?',
                #tail,
            ) {
                ::core::option::Option::Some(#found) => #found.tail(),
                ::core::option::Option::None => &[],
            };
        }
    }
}