essential_asm_gen/
lib.rs

1//! Generate essential ASM declarations from the official specification.
2//!
3//! Provides proc macros for generating declarations and implementations for
4//! the `essential-asm` crate.
5
6use essential_asm_spec::{visit, Group, Node, Op, StackOut, Tree};
7use proc_macro::TokenStream;
8use proc_macro2::Span;
9use quote::ToTokens;
10use syn::{punctuated::Punctuated, token::Comma};
11
12const WORD_SIZE: usize = core::mem::size_of::<essential_types::Word>();
13
14/// Document the required bytecode arguments to the operation.
15fn bytecode_arg_docs(num_arg_bytes: u8) -> String {
16    if num_arg_bytes == 0 {
17        return String::new();
18    }
19    assert_eq!(
20        num_arg_bytes as usize % WORD_SIZE,
21        0,
22        "doc gen currently only supports arguments that are a multiple of the word size"
23    );
24    let arg_words = num_arg_bytes as usize / WORD_SIZE;
25    format!(
26        "## Bytecode Argument\nThis operation expects a {num_arg_bytes}-byte \
27        ({arg_words}-word) argument following its opcode within bytecode."
28    )
29}
30
31/// Generate an Op's stack-input docstring.
32fn stack_in_docs(stack_in: &[String]) -> String {
33    if stack_in.is_empty() {
34        return String::new();
35    }
36    format!("## Stack Input\n`[{}]`\n", stack_in.join(", "))
37}
38
39/// Generate an Op's stack-output docstring.
40fn stack_out_docs(stack_out: &StackOut) -> String {
41    match stack_out {
42        StackOut::Fixed(words) if words.is_empty() => String::new(),
43        StackOut::Fixed(words) => {
44            format!("## Stack Output\n`[{}]`\n", words.join(", "))
45        }
46        StackOut::Dynamic(out) => {
47            format!(
48                "## Stack Output\n`[{}, ...]`\nThe stack output length depends on the \
49                value of the `{}` stack input word.\n",
50                out.elem, out.len
51            )
52        }
53    }
54}
55
56/// Generate an Op's panic reason docstring.
57fn panic_docs(panic_reasons: &[String]) -> String {
58    if panic_reasons.is_empty() {
59        return String::new();
60    }
61    let mut docs = "## Panics\n".to_string();
62    panic_reasons
63        .iter()
64        .for_each(|reason| docs.push_str(&format!("- {reason}\n")));
65    docs
66}
67
68/// Generate the docstring for an `Op` variant.
69fn op_docs(op: &Op) -> String {
70    let arg_docs = bytecode_arg_docs(op.num_arg_bytes);
71    let short_docs = if op.short.is_empty() {
72        String::new()
73    } else {
74        format!(": `{}`", op.short)
75    };
76    let opcode_docs = format!("`0x{:02X}`{}\n\n", op.opcode, short_docs);
77    let desc = &op.description;
78    let stack_in_docs = stack_in_docs(&op.stack_in);
79    let stack_out_docs = stack_out_docs(&op.stack_out);
80    let panic_docs = panic_docs(&op.panics);
81    format!("{opcode_docs}\n{desc}\n{arg_docs}\n{stack_in_docs}\n{stack_out_docs}\n{panic_docs}")
82}
83
84/// Generate the docstring for an `Opcode` variant.
85fn opcode_docs(enum_name: &str, name: &str, op: &Op) -> String {
86    let opcode_docs = format!("`0x{:02X}`\n\n", op.opcode);
87    let docs = format!(
88        "Opcode associated with the \
89        [{enum_name}::{name}][super::op::{enum_name}::{name}] operation."
90    );
91    format!("{opcode_docs}\n{docs}")
92}
93
94/// Generate a single variant for an op group's enum decl.
95fn op_enum_decl_variant(name: &str, node: &Node) -> syn::Variant {
96    let ident = syn::Ident::new(name, Span::call_site());
97    match node {
98        Node::Group(group) => {
99            let docs = &group.description;
100            syn::parse_quote! {
101                #[doc = #docs]
102                #ident(#ident)
103            }
104        }
105        Node::Op(op) => {
106            let docs = op_docs(op);
107            match op.num_arg_bytes {
108                0 => syn::parse_quote! {
109                    #[doc = #docs]
110                    #ident
111                },
112                8 => syn::parse_quote! {
113                    #[doc = #docs]
114                    #ident(essential_types::Word)
115                },
116                _ => panic!(
117                    "Unexpected num_arg_bytes {}: requires more thoughtful asm-gen",
118                    op.num_arg_bytes
119                ),
120            }
121        }
122    }
123}
124
125/// Generate a single variant for an op group's opcode enum decl
126fn opcode_enum_decl_variant(parent_name: &str, name: &str, node: &Node) -> syn::Variant {
127    let ident = syn::Ident::new(name, Span::call_site());
128    match node {
129        Node::Group(group) => {
130            let docs = &group.description;
131            syn::parse_quote! {
132                #[doc = #docs]
133                #ident(#ident)
134            }
135        }
136        Node::Op(op) => {
137            let docs = opcode_docs(parent_name, name, op);
138            let opcode = op.opcode;
139            syn::parse_quote! {
140                #[doc = #docs]
141                #ident = #opcode
142            }
143        }
144    }
145}
146
147/// Generate the variants for an op group's enum decl.
148fn op_enum_decl_variants(group: &Group) -> Punctuated<syn::Variant, Comma> {
149    group
150        .tree
151        .iter()
152        .map(|(name, node)| op_enum_decl_variant(name, node))
153        .collect()
154}
155
156/// Generate the variants for an op group's opcode enum decl.
157fn opcode_enum_decl_variants(enum_name: &str, group: &Group) -> Punctuated<syn::Variant, Comma> {
158    group
159        .tree
160        .iter()
161        .map(|(name, node)| opcode_enum_decl_variant(enum_name, name, node))
162        .collect()
163}
164
165/// Generate a single enum declaration from the given op group.
166fn op_enum_decl(name: &str, group: &Group) -> syn::ItemEnum {
167    let variants = op_enum_decl_variants(group);
168    let ident = syn::Ident::new(name, Span::call_site());
169    let docs = &group.description;
170    let item_enum = syn::parse_quote! {
171        #[doc = #docs]
172        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
173        pub enum #ident {
174            #variants
175        }
176    };
177    item_enum
178}
179
180/// Generate an opcode enum declaration for the given op group.
181fn opcode_enum_decl(name: &str, group: &Group) -> syn::ItemEnum {
182    let variants = opcode_enum_decl_variants(name, group);
183    let ident = syn::Ident::new(name, Span::call_site());
184    let docs = &group.description;
185    let item_enum = syn::parse_quote! {
186        #[doc = #docs]
187        #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
188        #[repr(u8)]
189        pub enum #ident {
190            #variants
191        }
192    };
193    item_enum
194}
195
196/// Generate the arms of the `opcode` method's match expression.
197fn op_enum_impl_opcode_arms(enum_ident: &syn::Ident, group: &Group) -> Vec<syn::Arm> {
198    group
199        .tree
200        .iter()
201        .map(|(name, node)| {
202            let name = syn::Ident::new(name, Span::call_site());
203            match node {
204                Node::Group(_) => syn::parse_quote! {
205                    #enum_ident::#name(group) => crate::opcode::#enum_ident::#name(group.to_opcode()),
206                },
207                Node::Op(op) if op.num_arg_bytes > 0 => {
208                    syn::parse_quote! {
209                        #enum_ident::#name(_) => crate::opcode::#enum_ident::#name,
210                    }
211                }
212                Node::Op(_) => {
213                    syn::parse_quote! {
214                        #enum_ident::#name => crate::opcode::#enum_ident::#name,
215                    }
216                }
217            }
218        })
219        .collect()
220}
221
222/// Generate the `ToOpcode` implementation for an operation type.
223fn op_enum_impl_opcode(name: &str, group: &Group) -> syn::ItemImpl {
224    let name = syn::Ident::new(name, Span::call_site());
225    let arms = op_enum_impl_opcode_arms(&name, group);
226    syn::parse_quote! {
227        impl ToOpcode for #name {
228            type Opcode = crate::opcode::#name;
229            fn to_opcode(&self) -> Self::Opcode {
230                match *self {
231                    #(
232                        #arms
233                    )*
234                }
235            }
236        }
237    }
238}
239
240/// Generate the `TryFromBytes` implementation for parsing the Op from an
241/// iterator yielding bytes.
242fn op_enum_impl_try_from_bytes(name: &str) -> syn::ItemImpl {
243    let name = syn::Ident::new(name, Span::call_site());
244    syn::parse_quote! {
245        impl TryFromBytes for #name {
246            type Error = crate::FromBytesError;
247            fn try_from_bytes(
248                bytes: &mut impl Iterator<Item = u8>,
249            ) -> Option<Result<Self, Self::Error>> {
250                use crate::opcode::ParseOp;
251                let opcode_byte = bytes.next()?;
252                let op_res = crate::opcode::#name::try_from(opcode_byte)
253                    .map_err(From::from)
254                    .and_then(|opcode| opcode.parse_op(bytes).map_err(From::from));
255                Some(op_res)
256            }
257        }
258    }
259}
260
261/// Generate a single variant for an op enum's bytes iterator.
262fn op_enum_bytes_iter_decl_variant(name: &str, node: &Node) -> syn::Variant {
263    let ident = syn::Ident::new(name, Span::call_site());
264    match node {
265        Node::Group(_group) => {
266            syn::parse_quote! {
267                #ident(#ident)
268            }
269        }
270        Node::Op(op) => {
271            // The opcode + the number of bytes in the associated data.
272            let n_bytes: usize = 1 + op.num_arg_bytes as usize;
273            let n_bytes: syn::LitInt = syn::parse_quote!(#n_bytes);
274            syn::parse_quote! {
275                #ident {
276                    /// The current index within the bytes array.
277                    index: usize,
278                    /// The operation's associated data as an array of bytes.
279                    bytes: [u8; #n_bytes],
280                }
281            }
282        }
283    }
284}
285
286/// Generate an op enum's bytes iterator variants.
287fn op_enum_bytes_iter_decl_variants(group: &Group) -> Vec<syn::Variant> {
288    group
289        .tree
290        .iter()
291        .map(|(name, node)| op_enum_bytes_iter_decl_variant(name, node))
292        .collect()
293}
294
295/// Create the declaration for an Op enum's associated bytes iterator type.
296fn op_enum_bytes_iter_decl(name: &str, group: &Group) -> syn::ItemEnum {
297    let name = syn::Ident::new(name, Span::call_site());
298    let variants = op_enum_bytes_iter_decl_variants(group);
299    let docs = format!(
300        "The bytes iterator produced by the \
301        [{name}::to_bytes][super::ToBytes::to_bytes] method."
302    );
303    syn::parse_quote! {
304        #[doc = #docs]
305        #[derive(Clone, Debug)]
306        pub enum #name {
307            #(
308                /// Bytes iterator for the op variant.
309                #variants
310            ),*
311        }
312    }
313}
314
315/// An arm of the match expr within a bytes iterator implementation.
316fn op_enum_bytes_iter_impl_arm(name: &str, node: &Node) -> syn::Arm {
317    let name = syn::Ident::new(name, Span::call_site());
318    match node {
319        Node::Group(_group) => syn::parse_quote! {
320            Self::#name(ref mut iter) => iter.next(),
321        },
322        Node::Op(_op) => {
323            syn::parse_quote! {
324                Self::#name { ref mut index, ref bytes } => {
325                    let byte = *bytes.get(*index)?;
326                    *index += 1;
327                    Some(byte)
328                },
329            }
330        }
331    }
332}
333
334/// The arms of the match expr within a bytes iterator implementation.
335fn op_enum_bytes_iter_impl_arms(group: &Group) -> Vec<syn::Arm> {
336    group
337        .tree
338        .iter()
339        .map(|(name, node)| op_enum_bytes_iter_impl_arm(name, node))
340        .collect()
341}
342
343/// An implementation of Iterator for the op's associated bytes iterator type.
344fn op_enum_bytes_iter_impl(name: &str, group: &Group) -> syn::ItemImpl {
345    let bytes_iter = syn::Ident::new(name, Span::call_site());
346    let arms = op_enum_bytes_iter_impl_arms(group);
347    syn::parse_quote! {
348        impl Iterator for #bytes_iter {
349            type Item = u8;
350            fn next(&mut self) -> Option<Self::Item> {
351                match *self {
352                    #(
353                        #arms
354                    )*
355                }
356            }
357        }
358    }
359}
360
361/// Generate an arm of the match expr with an op type's `to_bytes` method.
362fn op_enum_impl_to_bytes_arm(enum_name: &syn::Ident, name: &str, node: &Node) -> syn::Arm {
363    let name = syn::Ident::new(name, Span::call_site());
364    match node {
365        Node::Group(_group) => {
366            syn::parse_quote! {
367                Self::#name(group) => bytes_iter::#enum_name::#name(group.to_bytes()),
368            }
369        }
370        Node::Op(op) => {
371            let opcode = op.opcode;
372            if op.num_arg_bytes == 0 {
373                syn::parse_quote! {
374                    Self::#name => bytes_iter::#enum_name::#name {
375                        index: 0usize,
376                        bytes: [#opcode],
377                    },
378                }
379            } else if op.num_arg_bytes == 8 {
380                syn::parse_quote! {
381                    Self::#name(data) => {
382                        use essential_types::convert::bytes_from_word;
383                        let [b0, b1, b2, b3, b4, b5, b6, b7] = bytes_from_word(data.clone());
384                        bytes_iter::#enum_name::#name {
385                            index: 0usize,
386                            bytes: [#opcode, b0, b1, b2, b3, b4, b5, b6, b7],
387                        }
388                    },
389                }
390            } else {
391                panic!(
392                    "Currently only support operations with a single word \
393                    argument. This must be updated to properly support variable \
394                    size arguments.",
395                )
396            }
397        }
398    }
399}
400
401/// Generate the arms of the match expr with an op type's `to_bytes` method.
402fn op_enum_impl_to_bytes_arms(enum_name: &syn::Ident, group: &Group) -> Vec<syn::Arm> {
403    group
404        .tree
405        .iter()
406        .map(|(name, node)| op_enum_impl_to_bytes_arm(enum_name, name, node))
407        .collect()
408}
409
410/// Generate the `to_bytes` method for an operation type.
411fn op_enum_impl_to_bytes(name: &str, group: &Group) -> syn::ItemImpl {
412    let name = syn::Ident::new(name, Span::call_site());
413    let arms = op_enum_impl_to_bytes_arms(&name, group);
414    syn::parse_quote! {
415        impl ToBytes for #name {
416            type Bytes = bytes_iter::#name;
417            fn to_bytes(&self) -> Self::Bytes {
418                match self {
419                    #(
420                        #arms
421                    )*
422                }
423            }
424        }
425    }
426}
427
428/// Generate a `From` implementation for converting the subgroup (last name) to
429/// the higher-level group (first name).
430fn impl_from_subgroup(names: &[String]) -> syn::ItemImpl {
431    let ident = syn::Ident::new(names.first().unwrap(), Span::call_site());
432    let subident = syn::Ident::new(names.last().unwrap(), Span::call_site());
433    let inner_expr: syn::Expr = syn::parse_quote!(subgroup);
434    let expr = enum_variant_tuple1_expr(names, inner_expr);
435    syn::parse_quote! {
436        impl From<#subident> for #ident {
437            fn from(subgroup: #subident) -> Self {
438                #expr
439            }
440        }
441    }
442}
443
444/// Generate the `From` implementations for converting subgroups to higher-level groups.
445fn impl_from_subgroups(name: &str, group: &Group) -> Vec<syn::ItemImpl> {
446    let mut impls = vec![];
447    let mut names = vec![name.to_string()];
448    visit::groups_filtered_recurse(&group.tree, &|_| true, &mut names, &mut |names, _group| {
449        impls.push(impl_from_subgroup(names));
450    });
451    impls
452}
453
454/// Wrap an expression with the given nested op group naming.
455/// E.g. the args [Op, Stack]` and `my_expr` becomes
456/// `Op::Stack(my_expr)`.
457fn enum_variant_tuple1_expr(names: &[String], mut expr: syn::Expr) -> syn::Expr {
458    assert!(!names.is_empty(), "Expecting at least one variant name");
459    let mut idents: Vec<_> = names
460        .iter()
461        .map(|n| syn::Ident::new(n, Span::call_site()))
462        .collect();
463    let mut variant_name = idents.pop().unwrap();
464    while let Some(enum_name) = idents.pop() {
465        expr = syn::parse_quote!(#enum_name::#variant_name(#expr));
466        variant_name = enum_name;
467    }
468    expr
469}
470
471/// Generate an opcode expression from the given nested op group naming.
472/// E.g. `[Op, Stack, Push]` becomes `Op::Stack(Stack::Push)`.
473fn opcode_expr_from_names(names: &[String]) -> syn::Expr {
474    assert!(
475        names.len() >= 2,
476        "Expecting at least the enum and variant names"
477    );
478    let enum_name = syn::Ident::new(&names[names.len() - 2], Span::call_site());
479    let variant_name = syn::Ident::new(&names[names.len() - 1], Span::call_site());
480    let expr = syn::parse_quote!(#enum_name::#variant_name);
481    enum_variant_tuple1_expr(&names[..names.len() - 1], expr)
482}
483
484/// Generates an arm of the match expr used within the opcode's `parse_op` implementation.
485fn opcode_enum_impl_parse_op_arm(
486    enum_name: &syn::Ident,
487    name: &syn::Ident,
488    node: &Node,
489) -> syn::Arm {
490    match node {
491        Node::Group(_group) => {
492            syn::parse_quote! {
493                Self::#name(group) => group.parse_op(bytes).map(Into::into),
494            }
495        }
496        Node::Op(op) if op.num_arg_bytes == 0 => {
497            syn::parse_quote! {
498                Self::#name => Ok(crate::op::#enum_name::#name),
499            }
500        }
501        // TODO: Update this to handle variable size arguments if we add more
502        // sophisticated options than `Push`.
503        Node::Op(op) => {
504            assert_eq!(
505                op.num_arg_bytes as usize % WORD_SIZE,
506                0,
507                "Currently only support operations with an `num_arg_bytes` that is \
508                a multiple of the word size",
509            );
510            let words = op.num_arg_bytes as usize / WORD_SIZE;
511            assert_eq!(
512                words, 1,
513                "Currently only support operations with a single word \
514                argument. This must be updated to properly support variable \
515                size arguments.",
516            );
517            syn::parse_quote! {
518                Self::#name => {
519                    use essential_types::convert::word_from_bytes;
520                    fn parse_word_bytes(bytes: &mut impl Iterator<Item = u8>) -> Option<[u8; 8]> {
521                        Some([
522                            bytes.next()?, bytes.next()?, bytes.next()?, bytes.next()?,
523                            bytes.next()?, bytes.next()?, bytes.next()?, bytes.next()?,
524                        ])
525                    }
526                    let word_bytes: [u8; 8] = parse_word_bytes(bytes).ok_or(NotEnoughBytesError)?;
527                    let word: essential_types::Word = word_from_bytes(word_bytes);
528                    Ok(crate::op::#enum_name::#name(word))
529                },
530            }
531        }
532    }
533}
534
535/// Generates the arms of the match expr used within the opcode's `parse_op` implementation.
536fn opcode_enum_impl_parse_op_arms(enum_name: &syn::Ident, group: &Group) -> Vec<syn::Arm> {
537    group
538        .tree
539        .iter()
540        .map(|(name, node)| {
541            let name = syn::Ident::new(name, Span::call_site());
542            opcode_enum_impl_parse_op_arm(enum_name, &name, node)
543        })
544        .collect()
545}
546
547/// Generate a method that parses the operation associated with the opcode.
548fn opcode_enum_impl_parse_op(name: &str, group: &Group) -> syn::ItemImpl {
549    let ident = syn::Ident::new(name, Span::call_site());
550    let arms = opcode_enum_impl_parse_op_arms(&ident, group);
551    syn::parse_quote! {
552        impl ParseOp for #ident {
553            type Op = crate::op::#ident;
554            type Error = NotEnoughBytesError;
555
556            /// Attempt to parse the operation associated with the opcode from the given bytes.
557            ///
558            /// Only consumes the bytes necessary to construct any associated data.
559            ///
560            /// Returns an error in the case that the given `bytes` iterator
561            /// contains insufficient bytes to parse the op.
562            fn parse_op(
563                &self,
564                bytes: &mut impl Iterator<Item = u8>,
565            ) -> Result<Self::Op, Self::Error> {
566                match *self {
567                    #(
568                        #arms
569                    )*
570                }
571            }
572        }
573    }
574}
575
576/// Generate the arms from the opcode's `TryFrom<u8>` conversion match expr.
577fn opcode_enum_impl_tryfrom_u8_arms(group: &Group) -> Vec<syn::Arm> {
578    let mut arms = vec![];
579    let mut names = vec!["Self".to_string()];
580    visit::ops_filtered_recurse(&group.tree, &|_| true, &mut names, &mut |names, op| {
581        let opcode = op.opcode;
582        let opcode_expr = opcode_expr_from_names(names);
583        let arm = syn::parse_quote! {
584            #opcode => #opcode_expr,
585        };
586        arms.push(arm);
587    });
588    arms
589}
590
591/// Generate the arms for the conversion from opcode to u8.
592fn opcode_enum_impl_from_opcode_for_u8_arms(
593    enum_ident: &syn::Ident,
594    group: &Group,
595) -> Vec<syn::Arm> {
596    group
597        .tree
598        .iter()
599        .map(|(name, node)| {
600            let name = syn::Ident::new(name, Span::call_site());
601            match node {
602                Node::Group(_) => syn::parse_quote! {
603                    #enum_ident::#name(group) => u8::from(group),
604                },
605                Node::Op(op) => {
606                    let opcode = op.opcode;
607                    syn::parse_quote! {
608                        #enum_ident::#name => #opcode,
609                    }
610                }
611            }
612        })
613        .collect()
614}
615
616/// Generate the conversion from the opcode type to `u8`.
617fn opcode_enum_impl_from_opcode_for_u8(name: &str, group: &Group) -> syn::ItemImpl {
618    let name = syn::Ident::new(name, Span::call_site());
619    let arms = opcode_enum_impl_from_opcode_for_u8_arms(&name, group);
620    syn::parse_quote! {
621        impl From<#name> for u8 {
622            fn from(opcode: #name) -> Self {
623                match opcode {
624                    #(
625                        #arms
626                    )*
627                }
628            }
629        }
630    }
631}
632
633/// Generate the fallible conversion from `u8` to the opcode.
634fn opcode_enum_impl_tryfrom_u8(name: &str, group: &Group) -> syn::ItemImpl {
635    let name = syn::Ident::new(name, Span::call_site());
636    let arms = opcode_enum_impl_tryfrom_u8_arms(group);
637    syn::parse_quote! {
638        impl TryFrom<u8> for #name {
639            type Error = InvalidOpcodeError;
640            fn try_from(u: u8) -> Result<Self, Self::Error> {
641                let opcode = match u {
642                    #(
643                        #arms
644                    )*
645                    _ => return Err(InvalidOpcodeError(u)),
646                };
647                Ok(opcode)
648            }
649        }
650    }
651}
652
653/// Generate the implementations for the given op group enum.
654fn op_enum_impls(names: &[String], group: &Group) -> Vec<syn::ItemImpl> {
655    let name = names.last().unwrap();
656    let mut impls = vec![
657        op_enum_impl_opcode(name, group),
658        op_enum_impl_to_bytes(name, group),
659        op_enum_impl_try_from_bytes(name),
660    ];
661    impls.extend(impl_from_subgroups(name, group));
662    impls
663}
664
665/// Build const expression for an op variant.
666fn op_const_expr(names: &[String], insert_word: bool) -> syn::Expr {
667    let last = syn::Ident::new(names.last().unwrap(), Span::call_site());
668    let last: syn::Expr = if insert_word {
669        syn::parse_quote!(#last(word))
670    } else {
671        syn::parse_quote!(#last)
672    };
673    names[1..(names.len() - 1)].iter().rev().fold(
674        syn::parse_quote!(#last),
675        |acc: syn::Expr, name| {
676            let name = syn::Ident::new(name, Span::call_site());
677            syn::parse_quote!(#name(#name::#acc))
678        },
679    )
680}
681
682/// Generate the const declarations for the given op.
683fn op_consts(names: &[String], op: &Op) -> Vec<syn::Item> {
684    let const_name = if op.short.is_empty() {
685        syn::Ident::new(&names.last().unwrap().to_uppercase(), Span::call_site())
686    } else {
687        syn::Ident::new(&op.short, Span::call_site())
688    };
689    let docs = format!("## {}\n\n{}", names.last().unwrap(), op_docs(op));
690
691    if op.num_arg_bytes == 0 {
692        let full_name = op_const_expr(names, false);
693        let s = syn::parse_quote! {
694            #[doc = #docs]
695            pub const #const_name: Op = Op::#full_name;
696        };
697        vec![syn::Item::Const(s)]
698    } else {
699        let full_name = op_const_expr(names, true);
700        let mod_name = syn::Ident::new(&names.last().unwrap().to_lowercase(), Span::call_site());
701        let mut v = vec![];
702        let s = syn::parse_quote! {
703            #[doc = #docs]
704            pub const #const_name: fn(i64) -> Op = #mod_name::#mod_name;
705        };
706        v.push(syn::Item::Const(s));
707        let s = syn::parse_quote! {
708            mod #mod_name {
709                use super::*;
710                pub(super) fn #mod_name(word: i64) -> Op {
711                    Op::#full_name
712                }
713
714            }
715        };
716        v.push(syn::Item::Mod(s));
717        v
718    }
719}
720
721/// Generate the implementation for the opcode enum.
722fn opcode_enum_impls(names: &[String], group: &Group) -> Vec<syn::ItemImpl> {
723    let name = names.last().unwrap();
724    let mut impls = vec![
725        opcode_enum_impl_from_opcode_for_u8(name, group),
726        opcode_enum_impl_tryfrom_u8(name, group),
727        opcode_enum_impl_parse_op(name, group),
728    ];
729    impls.extend(impl_from_subgroups(name, group));
730    impls
731}
732
733/// Generate all op const declarations.
734fn all_op_consts(tree: &Tree) -> Vec<syn::Item> {
735    let mut items = vec![];
736    visit::ops(tree, &mut |names, op| {
737        items.extend(op_consts(names, op));
738    });
739    items
740}
741
742/// Generate items for each op group and collect them into a `Vec`.
743fn collect_items(tree: &Tree, new_item: impl Fn(&[String], &Group) -> syn::Item) -> Vec<syn::Item> {
744    let mut items = vec![];
745    visit::groups(tree, &mut |str, group| items.push(new_item(str, group)));
746    items
747}
748
749/// Generate all op enum declarations.
750fn all_op_enum_decls(tree: &Tree) -> Vec<syn::Item> {
751    collect_items(tree, |names, group| {
752        let name = names.last().unwrap();
753        syn::Item::Enum(op_enum_decl(name, group))
754    })
755}
756
757/// Generate all opcode enum declarations.
758fn all_opcode_enum_decls(tree: &Tree) -> Vec<syn::Item> {
759    collect_items(tree, |names, group| {
760        let name = names.last().unwrap();
761        syn::Item::Enum(opcode_enum_decl(name, group))
762    })
763}
764
765/// Generate the bytes iterator declaration and implementation for all op groups.
766fn all_op_enum_bytes_iter(tree: &Tree) -> Vec<syn::Item> {
767    let mut items = vec![];
768    visit::groups(tree, &mut |names, group| {
769        let name = names.last().unwrap();
770        items.push(syn::Item::Enum(op_enum_bytes_iter_decl(name, group)));
771        items.push(syn::Item::Impl(op_enum_bytes_iter_impl(name, group)));
772    });
773    items
774}
775
776/// Generate all op enum implementations.
777fn all_op_enum_impls(tree: &Tree) -> Vec<syn::Item> {
778    let mut items = vec![];
779    visit::groups(tree, &mut |name, group| {
780        items.extend(op_enum_impls(name, group).into_iter().map(syn::Item::Impl));
781    });
782    items
783}
784
785/// Generate all opcode enum implementations.
786fn all_opcode_enum_impls(tree: &Tree) -> Vec<syn::Item> {
787    let mut items = vec![];
788    visit::groups(tree, &mut |name, group| {
789        items.extend(
790            opcode_enum_impls(name, group)
791                .into_iter()
792                .map(syn::Item::Impl),
793        );
794    });
795    items
796}
797
798const DOCS_TABLE_HEADER: &str = "\n\n\
799    | Opcode | Op | Short Description |\n\
800    | --- | --- | --- |\n";
801
802/// Generates a row for a single op within an ASM table docs.
803fn docs_table_row(names: &[String], op: &Op) -> String {
804    assert!(
805        names.len() >= 2,
806        "`names` should contain at least the group and op names"
807    );
808    let enum_ix = names.len() - 2;
809    let enum_variant = &names[enum_ix..];
810    let enum_name = enum_variant.first().unwrap();
811    let variant_name = enum_variant.last().unwrap();
812    let opcode_link = format!("opcode::{enum_name}::{variant_name}");
813    let op_link = format!("op::{enum_name}::{variant_name}");
814    let short_desc = op.description.lines().next().unwrap();
815    format!(
816        "| [`0x{:02X}`][{opcode_link}] | [{}][{op_link}] | {short_desc} |\n",
817        op.opcode,
818        enum_variant.join(" ")
819    )
820}
821
822/// Generates a markdown table containing all operations.
823fn ops_docs_table(tree: &Tree) -> syn::LitStr {
824    let mut docs = DOCS_TABLE_HEADER.to_string();
825    visit::ops(tree, &mut |names, op| {
826        docs.push_str(&docs_table_row(names, op));
827    });
828    syn::parse_quote! { #docs }
829}
830
831fn token_stream_from_items(items: impl IntoIterator<Item = syn::Item>) -> TokenStream {
832    items
833        .into_iter()
834        .flat_map(|item| item.into_token_stream())
835        .collect::<proc_macro2::TokenStream>()
836        .into()
837}
838
839#[proc_macro]
840pub fn gen_all_op_decls(_input: TokenStream) -> TokenStream {
841    let tree = essential_asm_spec::tree();
842    let items = all_op_enum_decls(&tree);
843    token_stream_from_items(items)
844}
845
846#[proc_macro]
847pub fn gen_all_opcode_decls(_input: TokenStream) -> TokenStream {
848    let tree = essential_asm_spec::tree();
849    let items = all_opcode_enum_decls(&tree);
850    token_stream_from_items(items)
851}
852
853#[proc_macro]
854pub fn gen_all_op_bytes_iter(_input: TokenStream) -> TokenStream {
855    let tree = essential_asm_spec::tree();
856    let items = all_op_enum_bytes_iter(&tree);
857    token_stream_from_items(items)
858}
859
860#[proc_macro]
861pub fn gen_all_op_impls(_input: TokenStream) -> TokenStream {
862    let tree = essential_asm_spec::tree();
863    let items = all_op_enum_impls(&tree);
864    token_stream_from_items(items)
865}
866
867#[proc_macro]
868pub fn gen_all_opcode_impls(_input: TokenStream) -> TokenStream {
869    let tree = essential_asm_spec::tree();
870    let items = all_opcode_enum_impls(&tree);
871    token_stream_from_items(items)
872}
873
874#[proc_macro]
875pub fn gen_ops_docs_table(_input: TokenStream) -> TokenStream {
876    let tree = essential_asm_spec::tree();
877    let lit_str = ops_docs_table(&tree);
878    lit_str.into_token_stream().into()
879}
880
881#[proc_macro]
882pub fn gen_all_op_consts(_input: TokenStream) -> TokenStream {
883    let tree = essential_asm_spec::tree();
884    let items = all_op_consts(&tree);
885
886    token_stream_from_items(items)
887}