1use proc_macro::TokenStream;
45use proc_macro_crate::{FoundCrate, crate_name};
46use quote::{format_ident, quote};
47use syn::{
48 Expr, LitStr, Token,
49 parse::{Parse, ParseStream},
50 parse_macro_input,
51};
52
53use wazabin_qcode_parser::ast::{
54 Atom, ExprNode, FnDecl, Label, Program, ProgramKind, Statement, TupleField, TypedAtom,
55};
56
57struct QCodeInput {
58 expr: Expr,
59 program: LitStr,
60}
61
62impl Parse for QCodeInput {
63 fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
64 let expr: Expr = input.parse()?;
65 let _comma: Token![,] = input.parse()?;
66 let program: LitStr = input.parse()?;
67 Ok(Self { expr, program })
68 }
69}
70
71#[proc_macro]
72pub fn qcode(input: TokenStream) -> TokenStream {
73 let input = parse_macro_input!(input as QCodeInput);
74 match compile(&input.expr, &input.program.value()) {
75 Ok(tokens) => tokens.into(),
76 Err(err) => err.to_compile_error().into(),
77 }
78}
79
80#[derive(Default)]
82struct Names {
83 functions: Vec<String>,
84 blocks: Vec<String>,
85 ssa: Vec<String>,
86 varnodes: Vec<String>,
87 temps: Vec<String>,
88 block_params: Vec<String>,
89 externals: Vec<String>,
90}
91
92fn push_unique(set: &mut Vec<String>, name: &str) {
93 if !set.iter().any(|n| n == name) {
94 set.push(name.to_owned());
95 }
96}
97
98fn compile(expr: &Expr, source: &str) -> syn::Result<proc_macro2::TokenStream> {
99 let err = |e: String| syn::Error::new(proc_macro2::Span::call_site(), e);
100 let program = wazabin_qcode_parser::qcode_from_str(source).map_err(|e| err(e.to_string()))?;
101
102 let mut names = Names::default();
103 collect_program(&program, &mut names);
104
105 let defined: std::collections::HashSet<&str> = names
108 .functions
109 .iter()
110 .chain(&names.blocks)
111 .chain(&names.ssa)
112 .chain(&names.varnodes)
113 .chain(&names.temps)
114 .chain(&names.block_params)
115 .map(String::as_str)
116 .collect();
117 let captures: Vec<&String> = names
118 .externals
119 .iter()
120 .filter(|n| !defined.contains(n.as_str()))
121 .collect();
122
123 let qcode = resolve_crate("qcode")?;
124
125 let capture_inserts = captures.iter().map(|name| {
126 let ident = format_ident!("{}", name);
127 quote! {
128 __qcode_ext.insert(
129 ::std::string::String::from(#name),
130 #qcode::value::ValueId::from(#ident),
131 );
132 }
133 });
134
135 let bind = |ty: proc_macro2::TokenStream,
136 getter: proc_macro2::TokenStream,
137 items: &[String]|
138 -> Vec<proc_macro2::TokenStream> {
139 items
140 .iter()
141 .map(|name| {
142 let ident = format_ident!("{}", name);
143 quote! {
144 #[allow(unused_variables)]
145 let #ident: #qcode::value::#ty = __qcode_syms.#getter(#name);
146 }
147 })
148 .collect()
149 };
150
151 let fn_binds = bind(quote!(FunctionId), quote!(function), &names.functions);
152 let block_binds = bind(quote!(BlockId), quote!(block), &names.blocks);
153 let ssa_binds = bind(quote!(InstructionId), quote!(ssa), &names.ssa);
154 let varnode_binds = bind(quote!(VarnodeId), quote!(varnode), &names.varnodes);
155 let temp_binds = bind(quote!(TempId), quote!(temp), &names.temps);
156 let param_binds = bind(
157 quote!(BlockParamId),
158 quote!(block_param),
159 &names.block_params,
160 );
161
162 Ok(quote! {
165 let mut __qcode_ext: ::std::collections::HashMap<
166 ::std::string::String,
167 #qcode::value::ValueId,
168 > = ::std::collections::HashMap::new();
169 #(#capture_inserts)*
170 let __qcode_syms = #qcode::lower::lower_str_with_externals(&mut (#expr), #source, __qcode_ext)
171 .expect("qcode! lowering failed");
172 #(#fn_binds)*
173 #(#block_binds)*
174 #(#ssa_binds)*
175 #(#varnode_binds)*
176 #(#temp_binds)*
177 #(#param_binds)*
178 let _ = &__qcode_syms;
179 })
180}
181
182fn resolve_crate(name: &str) -> syn::Result<proc_macro2::TokenStream> {
183 match crate_name(name) {
184 Ok(FoundCrate::Itself) => Ok(quote!(crate)),
185 Ok(FoundCrate::Name(found)) => {
186 let ident = syn::Ident::new(&found, proc_macro2::Span::call_site());
187 Ok(quote!(::#ident))
188 }
189 Err(e) => Err(syn::Error::new(
190 proc_macro2::Span::call_site(),
191 format!("could not resolve `{name}` crate for qcode! macro: {e}"),
192 )),
193 }
194}
195
196fn collect_program(program: &Program, names: &mut Names) {
197 match &program.kind {
198 ProgramKind::Statements(stmts) => {
199 let split = stmts
200 .iter()
201 .position(|stmt| !matches!(stmt.inner(), Statement::LocalDecl { .. }))
202 .unwrap_or(stmts.len());
203 collect_statements(&stmts[..split], names, false);
204 collect_statements(&stmts[split..], names, true);
205 }
206 ProgramKind::Functions { varnodes, fns } => {
207 collect_statements(varnodes, names, false);
208 for fn_decl in fns {
209 collect_fn(fn_decl, names);
210 }
211 }
212 }
213}
214
215fn collect_fn(fn_decl: &FnDecl, names: &mut Names) {
216 push_unique(&mut names.functions, &fn_decl.name);
217 collect_statements(&fn_decl.statements, names, true);
218}
219
220fn collect_statements(stmts: &[Statement], names: &mut Names, local_temps: bool) {
221 for stmt in stmts {
222 collect_statement(stmt.inner(), names, local_temps);
223 }
224}
225
226fn collect_statement(stmt: &Statement, names: &mut Names, local_temps: bool) {
227 match stmt {
228 Statement::LocalDecl { name, .. } => {
229 if local_temps {
230 push_unique(&mut names.temps, name);
231 } else {
232 push_unique(&mut names.varnodes, name);
233 }
234 }
235 Statement::Assign { name, expr, .. } => {
236 push_unique(&mut names.ssa, name);
237 collect_expr(expr, names);
238 }
239 Statement::Expr(expr) => collect_expr(expr, names),
240 Statement::LabelDecl {
241 label: Label::Named { name, params, .. },
242 ..
243 } => {
244 push_unique(&mut names.blocks, name);
245 for p in params {
246 push_unique(&mut names.block_params, &p.name);
247 }
248 }
249 Statement::LabelDecl { .. } => {}
250 Statement::Branch { args, .. } => {
251 for (_, atom) in args {
252 collect_atom(atom, names);
253 }
254 }
255 Statement::BranchInd { ptr, .. } => collect_atom(ptr, names),
256 Statement::Switch {
257 scrutinee,
258 cases,
259 default,
260 ..
261 } => {
262 collect_atom(scrutinee, names);
263 let case_args = cases.iter().flat_map(|(_, _, args)| args);
264 let default_args = default.iter().flat_map(|(_, args)| args);
265 for (_, atom) in case_args.chain(default_args) {
266 collect_atom(atom, names);
267 }
268 }
269 Statement::CBranch {
270 condition,
271 target_args,
272 fallthrough_args,
273 ..
274 } => {
275 collect_atom(condition, names);
276 for (_, atom) in target_args.iter().chain(fallthrough_args) {
277 collect_atom(atom, names);
278 }
279 }
280 Statement::Call { args, .. } => {
281 for (_, atom) in args {
282 collect_atom(atom, names);
283 }
284 }
285 Statement::CallInd { ptr, args, .. } => {
286 collect_atom(ptr, names);
287 for atom in args {
288 collect_atom(atom, names);
289 }
290 }
291 Statement::Return { ptr, value, .. } => {
292 collect_atom(ptr, names);
293 if let Some(v) = value {
294 collect_atom(v, names);
295 }
296 }
297 Statement::ReturnValue { value, .. } => collect_atom(value, names),
298 Statement::BadInsn { .. } => {}
300 Statement::Assert { condition, .. } => collect_atom(condition, names),
301 Statement::Commented { inner, .. } => collect_statement(inner, names, local_temps),
302 }
303}
304
305fn collect_expr(expr: &ExprNode, names: &mut Names) {
306 match expr {
307 ExprNode::Atom(a) => collect_atom(a, names),
308 ExprNode::Unop { src, .. } => collect_atom(src, names),
309 ExprNode::Binary { lhs, rhs, .. } => {
310 collect_atom(lhs, names);
311 collect_atom(rhs, names);
312 }
313 ExprNode::Cast { src, .. } => collect_atom(src, names),
314 ExprNode::Load { ptr, .. } => collect_atom(ptr, names),
315 ExprNode::Store { ptr, src, .. } => {
316 collect_atom(ptr, names);
317 collect_atom(src, names);
318 }
319 ExprNode::FuncCall { args, .. }
320 | ExprNode::Intrinsic { args, .. }
321 | ExprNode::Apply { args, .. } => {
322 for a in args {
323 collect_atom(a, names);
324 }
325 }
326 ExprNode::Map { src, captures, .. } => {
327 collect_atom(src, names);
328 for a in captures {
329 collect_atom(a, names);
330 }
331 }
332 ExprNode::Scan {
333 init,
334 src,
335 captures,
336 ..
337 } => {
338 collect_atom(init, names);
339 collect_atom(src, names);
340 for a in captures {
341 collect_atom(a, names);
342 }
343 }
344 ExprNode::Tuple { fields } => {
345 for TupleField { value, .. } in fields {
346 collect_atom(value, names);
347 }
348 }
349 ExprNode::Extract { agg, .. } => collect_atom(agg, names),
350 ExprNode::Gep { base, .. } => collect_atom(base, names),
351 ExprNode::Range { src, .. } => collect_atom(src, names),
352 }
353}
354
355fn collect_atom(atom: &TypedAtom, names: &mut Names) {
356 if let Atom::External(name) = &atom.atom {
357 push_unique(&mut names.externals, name);
358 }
359}