//! Writing a request's values into a residual. //! //! The third thing a `#[staged]` shape gets. The first is the ordinary //! function, the second is the twin that renders with sentinels, and this is //! what a request actually calls: a walk over the residual that pushes the //! renderer's own literals and writes the request's values into the gaps. //! //! No `Node` is built. That is the whole point of the task this belongs to, and //! it is why this is generated rather than interpreted: the values come from //! the same expressions the declaration wrote, evaluated in place. //! //! # Why holes are filled by number and not by position //! //! Render order and declaration order are not the same. A cell's `activate` //! address is written after the cell's value and renders before it, inside the //! anchor that wraps it. So a filler that walked the residual writing values in //! the order it had them would put the address in the text and the text in the //! address. //! //! The residual carries each hole's number, and [`crate::symbolic`] recorded //! the expression for each number in the same traversal that assigned it. So a //! straight run of markup is filled by asking for holes by number, and only the //! things that change control flow -- a guard, a loop, another shape -- have to //! appear in the same order in both. Those do, because a container renders its //! members in the order it was told them. use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; use syn::{Ident, Result}; use crate::ast::{Declaration, Hole, Step}; use crate::symbolic::Fill; /// The filler's name, which is what an `include` calls. pub fn fill_name(name: &Ident) -> Ident { format_ident!("{}_fill", name, span = name.span()) } /// The one-call entry point, which makes a cursor and closes it. fn serve_name(name: &Ident) -> Ident { format_ident!("{}_serve", name, span = name.span()) } /// The filler for one shape, and the entry point beside it. pub fn filler(declaration: &Declaration, fill: &[Fill]) -> Result { let name = fill_name(&declaration.name); let serve = serve_name(&declaration.name); let vis = &declaration.vis; let params: Vec = declaration .params .iter() .map(|param| { let name = ¶m.name; let ty = ¶m.ty; quote!(#name: #ty) }) .collect(); let forwarded: Vec<&Ident> = declaration.params.iter().map(|param| ¶m.name).collect(); let body = self::body(fill)?; let fill_doc = format!( " Write a request's values into [`{}`]'s residual.", declaration.name ); let serve_doc = format!( " Serve [`{}`] from a residual, building no `Node`.", declaration.name ); Ok(quote! { #[doc = #fill_doc] /// /// Continues on the cursor it is given and does not close it, so a /// shape that includes this one carries on where it left off. // `out` is appended to, which `&mut str` cannot do. clippy reads the // signature of a shape that happens to push nothing itself -- one whose // every value comes from an included shape, or one with no values at // all -- and suggests the slice. Taking the suggestion would break every // other shape emitted by this same code. #[allow(clippy::ptr_arg)] #vis fn #name( cursor: &mut ::quasi_router::stage::Cursor<'_>, out: &mut ::std::string::String, #(#params),* ) { #body } #[doc = #serve_doc] #vis fn #serve( residual: &::quasi_router::stage::Residual, #(#params),* ) -> ::std::string::String { let mut out = ::std::string::String::with_capacity(residual.literal_len() + 64); let mut cursor = residual.cursor(); #name(&mut cursor, &mut out, #(#forwarded),*); ::quasi_router::stage::Cursor::finish(&mut cursor, &mut out); out } }) } /// One body: a shape's, a branch's, or a loop's. /// /// Every straight stretch is offered **every hole at this level**, not just the /// ones written since the last branch or loop. That is what makes the filler /// agree with the residual about which stretch a hole is in, and the two do not /// otherwise agree: the residual is in render order and the fill program is in /// declaration order, and control flow moves between them. /// /// MNW's `/git/{owner}` is the site. A row declares two cells, then a guarded /// third, then `activate` -- and `activate`'s address renders in the row's /// opening tag, before any cell. So the residual's first stretch holds the /// address's holes and the fill program's first run held the two cells', and /// the cursor asked for a number that run had no arm for. /// /// Offering all of them costs a wider `match` in the generated code and nothing /// at run time: an arm's expression is evaluated only when the cursor asks for /// that number, and the cursor asks only for the holes the stretch it is /// walking actually has. A stretch with no holes at all still gets the call, /// because which stretch that is is exactly what declaration order cannot say. fn body(fill: &[Fill]) -> Result { let holes: Vec<&Fill> = fill .iter() .filter(|one| matches!(one, Fill::Hole { .. })) .collect(); let mut out = TokenStream::new(); for one in fill { if matches!(one, Fill::Hole { .. }) { continue; } out.extend(run(&holes)?); out.extend(control(one, &holes)?); } out.extend(run(&holes)?); Ok(out) } /// One straight stretch of markup, with its holes answered by number. fn run(holes: &[&Fill]) -> Result { if holes.is_empty() { return Ok(TokenStream::new()); } let arms = answers(holes)?; Ok(quote! { ::quasi_router::stage::Cursor::fill(cursor, out, &mut |which, out| match which { #(#arms)* // A hole at another level, or one this shape does not have. The // residual and this code came from one declaration, so a number // outside the level's own set is a pairing bug rather than a case. _ => ::core::unreachable!("the residual has a hole the filler does not"), }); }) } /// One `match` arm per hole, answering it by number. fn answers(holes: &[&Fill]) -> Result> { holes .iter() .map(|one| { let Fill::Hole { id, hole } = one else { unreachable!("only holes are collected here"); }; let value = crate::emit::hole(&owned(hole))?; Ok(quote!(#id => ::quasi_webview::stage::Fill::fill(&(#value), out),)) }) .collect() } /// A guard, a loop, or another shape. /// /// `level` is every hole the stretch around this one has. A guard needs them /// because the residual may hold [`Op::Arms`](quasi_router::stage::Op::Arms) /// where the declaration said a guard, and an arm's holes are numbered in the /// level around it rather than in the arm -- which is what lets one arm carry a /// hole the other does not. fn control(one: &Fill, level: &[&Fill]) -> Result { Ok(match one { Fill::Hole { .. } => unreachable!("handled by the caller"), Fill::Branch { guard, body } => { let predicate = crate::emit::predicate(guard)?; let inner = self::body(body)?; // Both sets, because either shape may turn up here and the two // number their holes in the same shape-wide sequence. An arm is // evaluated only when the cursor asks for it, so offering more than // one shape can use costs a wider `match` and nothing at run time. let mut reachable: Vec<&Fill> = level.to_vec(); reachable.extend(body.iter().filter(|one| matches!(one, Fill::Hole { .. }))); let answers = answers(&reachable)?; quote! { { let taken = #predicate; ::quasi_router::stage::Cursor::choose( cursor, out, taken, &mut |which, out| match which { #(#answers)* _ => ::core::unreachable!( "the residual has a hole the filler does not" ), }, &mut |cursor, out| { #inner ::quasi_router::stage::Cursor::finish(cursor, out); }, ); } } } Fill::Repeat { dereferenced, binder, iterable, body, } => { let over = crate::emit::hole(iterable)?; let inner = self::body(body)?; let bound = if *dereferenced { quote!(&#binder) } else { quote!(#binder) }; quote! { { let repeated = ::quasi_router::stage::Cursor::repeat(cursor, out); let scope = ::quasi_router::stage::Cursor::scope(cursor); for #bound in #over { let mut cursor = &mut ::quasi_router::stage::Cursor::over(repeated, scope); #inner ::quasi_router::stage::Cursor::finish(cursor, out); } } } } // Two markups at one position, picked by a predicate rather than by a // value. `Arms`'s machinery exactly, with the scrutinee replaced: arm 0 // is the setting made, which is `Plan::swap`'s own reading. Fill::Swap { guard } => { let predicate = crate::emit::predicate(guard)?; let answers = answers(level)?; quote! { { let taken = usize::from(!(#predicate)); ::quasi_router::stage::Cursor::pick( cursor, out, taken, &mut |which, out| match which { #(#answers)* _ => ::core::unreachable!( "the residual has a hole the filler does not" ), }, ); } } } // A dispatch: the residual holds one arm per position and the request // picks the index. No body to walk, because every arm's holes were // numbered at this level -- see `symbolic::Fill::Arms`. Fill::Arms { scrutinee, patterns, } => { let value = crate::emit::hole(scrutinee)?; let which = patterns .iter() .enumerate() .map(|(at, pattern)| { let pattern = crate::emit::pattern(pattern); Ok(quote!(#pattern => #at,)) }) .collect::>>()?; let last = patterns.len(); let answers = answers(level)?; quote! { { let taken = match #value { #(#which)* _ => #last, }; ::quasi_router::stage::Cursor::pick( cursor, out, taken, &mut |which, out| match which { #(#answers)* _ => ::core::unreachable!( "the residual has a hole the filler does not" ), }, ); } } } // The callee's markup was spliced into this residual where its shape // was included, so its filler carries on with the same cursor. Fill::Include { callee, args, site } => { let mut path = callee.clone(); let last = path .segments .last_mut() .ok_or_else(|| syn::Error::new(Span::call_site(), "an empty path"))?; last.ident = fill_name(&last.ident); let args = args .iter() .map(crate::emit::arg) .collect::>>()?; quote! { { // The callee's holes are numbered in its own namespace, so // the walk is told whose they are before it reaches them. let held = ::quasi_router::stage::Cursor::enter(cursor, #site); #path(cursor, out, #(#args),*); ::quasi_router::stage::Cursor::leave(cursor, held); } } } }) } /// The hole with a trailing `.clone()` taken off. /// /// A declaration clones because the vocabulary takes the value by value. A /// filler only writes it, so the clone would be one allocation per hole per row /// bought for nothing, which at two hundred rows is the whole allocation /// budget. fn owned(hole: &Hole) -> Hole { let mut trimmed = hole.clone(); if let Some(Step::Method { name, args }) = trimmed.steps.last() && name == "clone" && args.is_empty() { trimmed.steps.pop(); } trimmed }