Skip to main content

max / quasi

4.4 KB · 123 lines History Blame Raw
1 //! `declare!`: a screen description, compiled to Rust at build time.
2 //!
3 //! The description IS the declaration. What a shape function used to build at
4 //! request time, this builds once at compile time, so what is left in the
5 //! binary is literals and holes. Design: wiki `quasi-declare-form`.
6 //!
7 //! # How this crate grows
8 //!
9 //! One production per conversion. A production exists here because a real
10 //! screen demanded it, and the screen that demanded it is named in the commit
11 //! that added it. Nothing is specified ahead of a screen that wants it, because
12 //! that is what produced a form with twelve amendments and no implementation.
13 //!
14 //! When a conversion hits something the form cannot say, there are three
15 //! outcomes and no fourth: use the remedy the deferred table names, add the
16 //! production, or refuse it and rewrite the Rust. Adding the production is
17 //! ordinary and expected. What is never acceptable is an escape hatch that
18 //! admits an expression, a block in argument position, a closure or a struct
19 //! literal, because those four are what keep `Node: Eq` intact.
20 //!
21 //! # One production reads a file
22 //!
23 //! `for <binder> in copy "<file>" as <key>` is copy the macro reads at
24 //! expansion time and writes out, one set of members per entry. It is not an
25 //! escape hatch: what a content file may hold is text, and the loop is gone
26 //! before `ast` sees it. See `copy.rs` for the split it implements and for
27 //! why the reading has to be marked with an `include_bytes!`.
28
29 mod ast;
30 mod copy;
31 mod emit;
32 mod fill;
33 mod pairs;
34 mod parse;
35 mod symbolic;
36
37 use proc_macro::TokenStream;
38 use syn::parse_macro_input;
39
40 /// Declare one shape.
41 ///
42 /// ```ignore
43 /// declare! {
44 /// /// The strip over the table.
45 /// shape header(view: &View) -> Node;
46 ///
47 /// let base = "/git/{view.owner}/{view.repo}";
48 /// region "git-file-header" as Group {
49 /// across Wrap {
50 /// beside Secondary text lines;
51 /// beside Essential link "Source" to get "{base}/tree/{view.file_path}" navigating;
52 /// }
53 /// }
54 /// }
55 /// ```
56 #[proc_macro]
57 pub fn declare(input: TokenStream) -> TokenStream {
58 // Anything a refused expansion read, before this one reads its own.
59 drop(copy::taken());
60 let declaration = parse_macro_input!(input as ast::Declaration);
61 let copied = copy::taken();
62 match expand(&declaration) {
63 Ok(expansion) => {
64 let watched = watch(&copied);
65 quote::quote! { #expansion #watched }.into()
66 }
67 Err(error) => error.into_compile_error().into(),
68 }
69 }
70
71 /// What makes rustc rebuild when a content file changes.
72 ///
73 /// A macro that reads a file reads it behind the compiler's back. Naming each
74 /// one in an `include_bytes!` puts it back in front: the path is absolute
75 /// because the emitted code lands in whatever file the invocation sits in, and
76 /// `include_bytes!` resolves a relative path against that file rather than
77 /// against the manifest the macro resolved with.
78 fn watch(copied: &[std::path::PathBuf]) -> proc_macro2::TokenStream {
79 let markers = copied.iter().map(|path| {
80 let path = path.to_string_lossy();
81 quote::quote! { const _: &[u8] = include_bytes!(#path); }
82 });
83 quote::quote! { #(#markers)* }
84 }
85
86 /// The shape's function, and its staged twin if it asked for one.
87 fn expand(declaration: &ast::Declaration) -> syn::Result<proc_macro2::TokenStream> {
88 pairs::check(&declaration.items)?;
89 let shape = emit::declaration(declaration)?;
90 let promise = symbolic::promise(declaration);
91 if !symbolic::wanted(declaration) {
92 return Ok(quote::quote! { #shape #promise });
93 }
94
95 let staged = symbolic::stage(declaration)?;
96 let twin = emit::declaration(&staged.declaration)?;
97 let filler = fill::filler(declaration, &staged.fill)?;
98 let (guards, loops, holes, sites) = staged.counts;
99 let counts = &staged.counts_ident;
100 let vis = declaration
101 .vis
102 .clone()
103 .unwrap_or(syn::Visibility::Inherited);
104 let doc = format!(
105 " What a plan has to answer for to drive [`{}`].",
106 staged.declaration.name
107 );
108
109 Ok(quote::quote! {
110 #shape
111 #promise
112 #twin
113 #filler
114 #[doc = #doc]
115 #vis const #counts: ::quasi_router::stage::Shape = ::quasi_router::stage::Shape {
116 guards: #guards,
117 loops: #loops,
118 holes: #holes,
119 sites: #sites,
120 };
121 })
122 }
123