preserves_schema_macros/
lib.rs1use preserves::value::Map;
2use preserves_schema::compiler::*;
3use preserves_schema::compiler::types::Purpose;
4use preserves_schema::gen::schema::Schema;
5use proc_macro2::Span;
6use quote::ToTokens;
7use quote::quote;
8use std::fmt::Display;
9use syn::LitStr;
10use syn::Token;
11use syn::parenthesized;
12use syn::parse::Parser;
13use syn::punctuated::Punctuated;
14
15mod kw {
16 use syn::custom_keyword;
17 custom_keyword!(load);
18 custom_keyword!(cross_reference);
19 custom_keyword!(external_module);
20}
21
22#[derive(Debug)]
23enum Instruction {
24 Namespace(String),
25 Load(LitStr),
26 CrossReference {
27 namespace: String,
28 bundle_path: LitStr,
29 },
30 ExternalModule {
31 module_path: ModulePath,
32 namespace: String,
33 }
34}
35
36fn syn_path_string(p: syn::Path) -> String {
37 p.to_token_stream().to_string().replace(&[' ', '\t', '\n', '\r'], "")
38}
39
40fn syn_litstr_resolve(s: &syn::LitStr) -> String {
41 let s: String = s.value();
42 match s.chars().nth(0) {
43 Some('/') => s.into(),
44 Some('<') => match &s[1..].split_once('>') {
45 Some((envvar, remainder)) => match std::env::var(envvar) {
46 Ok(p) => p + "/" + remainder,
47 Err(_) => panic!("No such environment variable: {:?}", s),
48 }
49 None => panic!("Invalid relative path syntax: {:?}", s),
50 },
51 _ => panic!("Invalid path syntax: {:?}", s)
52 }
53}
54
55impl syn::parse::Parse for Instruction {
56 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
57 let lookahead = input.lookahead1();
58 if lookahead.peek(kw::load) {
59 let _: kw::load = input.parse()?;
60 let content;
61 let _ = parenthesized!(content in input);
62 let bundle_path: syn::LitStr = content.parse()?;
63 Ok(Instruction::Load(bundle_path))
64 } else if lookahead.peek(kw::cross_reference) {
65 let _: kw::cross_reference = input.parse()?;
66 let content;
67 let _ = parenthesized!(content in input);
68 let namespace: syn::Path = content.parse()?;
69 let _: Token![=] = content.parse()?;
70 let bundle_path: syn::LitStr = content.parse()?;
71 Ok(Instruction::CrossReference {
72 namespace: syn_path_string(namespace),
73 bundle_path,
74 })
75 } else if lookahead.peek(kw::external_module) {
76 let _: kw::external_module = input.parse()?;
77 let content;
78 let _ = parenthesized!(content in input);
79 let module_path = Punctuated::<syn::Ident, syn::Token![.]>::parse_separated_nonempty(&content)?;
80 let _: Token![=] = content.parse()?;
81 let namespace: syn::Path = content.parse()?;
82 Ok(Instruction::ExternalModule {
83 module_path: module_path.into_iter().map(|p| p.to_string()).collect(),
84 namespace: syn_path_string(namespace),
85 })
86 } else {
87 let ns: syn::Path = input.parse()?;
88 Ok(Instruction::Namespace(syn_path_string(ns)))
89 }
90 }
91}
92
93struct ModuleTree {
94 own_body: String,
95 children: Map<String, ModuleTree>,
96}
97
98impl Default for ModuleTree {
99 fn default() -> Self {
100 ModuleTree {
101 own_body: String::new(),
102 children: Map::default(),
103 }
104 }
105}
106
107impl ModuleTree {
108 fn build(outputs: Map<Option<ModulePath>, String>) -> Self {
109 let mut mt = ModuleTree::default();
110 for (p, c) in outputs.into_iter() {
111 match p {
112 None => mt.own_body = c,
113 Some(k) => {
114 let mut r = &mut mt;
115 for e in k { r = mt.children.entry(names::render_modname(&e)).or_default(); }
116 r.own_body = c;
117 }
118 }
119 }
120 mt
121 }
122}
123
124impl Display for ModuleTree {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 write!(f, "{}", self.own_body)?;
127 for (label, mt) in self.children.iter() {
128 write!(f, "\npub mod {} {{ ", label)?;
129 mt.fmt(f)?;
130 write!(f, "}}")?;
131 }
132 Ok(())
133 }
134}
135
136#[proc_macro]
137pub fn compile_preserves_schemas(src: proc_macro::TokenStream) -> proc_macro::TokenStream {
138 let instructions = Punctuated::<Instruction, syn::Token![,]>::parse_terminated
139 .parse(src)
140 .expect("valid sequence of compile_preserves_schemas instructions");
141
142 let mut namespace = None::<String>;
143 let mut bundles_to_load = Vec::<LitStr>::new();
144 let mut bundles_to_xref = Vec::<LitStr>::new();
145 let mut external_modules = Vec::<ExternalModule>::new();
146
147 for i in instructions.into_iter() {
148 match i {
149 Instruction::Namespace(n) => {
150 if namespace.is_some() {
151 panic!("Only one namespace is permitted")
152 }
153 namespace = Some(n)
154 }
155 Instruction::Load(p) => bundles_to_load.push(p),
156 Instruction::ExternalModule { module_path, namespace } => {
157 external_modules.push(ExternalModule::new(module_path, &namespace));
158 }
159 Instruction::CrossReference { namespace, bundle_path } => {
160 let mut bundle = Map::<ModulePath, Schema>::new();
161 let is_schema = load_schema_or_bundle(&mut bundle, &syn_litstr_resolve(&bundle_path).into())
162 .expect("Invalid schema/bundle binary");
163 bundles_to_xref.push(bundle_path);
164 for (k, _v) in bundle.into_iter() {
165 external_modules.push(if is_schema {
166 ExternalModule::new(k, &namespace)
167 } else {
168 let ns = namespace.clone();
169 let mut pieces = vec![ns.clone()];
170 pieces.extend(
171 k.iter().map(|p| names::render_modname(&p)).collect::<Vec<_>>());
172 ExternalModule::new(k, &pieces.join("::"))
173 .set_fallback_language_types(
174 move |v| vec![format!("{}::Language<{}>", ns, v)].into_iter().collect())
175 });
176 }
177 }
178 }
179 }
180
181 let namespace = namespace.expect("Missing namespace");
182
183 let mut dependency_paths = Vec::<syn::LitStr>::new();
184
185 let mut c = CompilerConfig::new(namespace.clone());
186 for b in bundles_to_load.into_iter() {
187 dependency_paths.push(syn::LitStr::new(&syn_litstr_resolve(&b), Span::call_site()));
188 load_schema_or_bundle_with_purpose(&mut c.bundle, &syn_litstr_resolve(&b).into(), Purpose::Codegen)
189 .expect(&b.value());
190 }
191 for b in bundles_to_xref.into_iter() {
192 dependency_paths.push(syn::LitStr::new(&syn_litstr_resolve(&b), Span::call_site()));
193 load_schema_or_bundle_with_purpose(&mut c.bundle, &syn_litstr_resolve(&b).into(), Purpose::Xref)
194 .expect(&b.value());
195 }
196 for m in external_modules.into_iter() {
197 c.add_external_module(m);
198 }
199
200 let mut outputs = Map::<Option<ModulePath>, String>::new();
201 let mut collector = CodeCollector {
202 emit_mod_declarations: false,
203 collect_module: CodeModuleCollector::Custom {
204 collect_output: &mut |p, c| {
205 outputs.insert(p.cloned(), c.to_owned());
206 Ok(())
207 },
208 },
209 };
210 compile(&c, &mut collector).expect("Compilation failed");
211
212 let top_module_source = format!(
213 "pub mod {} {{ {} }}",
214 names::render_modname(namespace.split("::").last().unwrap()),
215 ModuleTree::build(outputs));
216 let top_module: syn::Item = syn::parse_str(&top_module_source)
217 .expect("Invalid generated code");
218
219 quote!{
220 #( const _: &'static [u8] = include_bytes!(#dependency_paths); )*
224
225 #top_module
226 }.into()
227}