Skip to main content

python_instruction_dsl_proc/
lib.rs

1extern crate proc_macro;
2use heck::ToUpperCamelCase;
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6    Expr, Ident, LitInt, Token, bracketed, parenthesized, parse::Parse, parse_macro_input,
7    spanned::Spanned,
8};
9
10#[derive(Clone)]
11enum StackItem {
12    Name(Ident),
13    NameCounted(Ident, Expr),
14    /// Amount of unused (and unnamed) stack items
15    Unused(Expr),
16}
17
18#[derive(Clone)]
19struct StackEffect {
20    pops: Vec<StackItem>,
21    pushes: Vec<StackItem>,
22}
23
24#[derive(Clone)]
25struct Opcode {
26    name: Ident,
27    number: LitInt,
28    stack_effect: Option<StackEffect>,
29}
30
31struct Exception {
32    stack_effect: StackEffect,
33}
34
35impl Parse for Opcode {
36    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
37        // Example: LOAD_CONST = 100 ( -- constant)
38        // You may also use ( / ) to indicate this opcode has no stack description.
39        let name: Ident = input.parse()?;
40        input.parse::<Token![=]>()?;
41        let number: LitInt = input.parse()?;
42
43        let inner_stack_effect;
44
45        parenthesized!(inner_stack_effect in input);
46
47        let mut stack_effect = StackEffect {
48            pops: vec![],
49            pushes: vec![],
50        };
51
52        if inner_stack_effect.parse::<Token![/]>().is_ok() {
53            // This opcode does not have a stack description
54            return Ok(Opcode {
55                name,
56                number,
57                stack_effect: None,
58            });
59        }
60
61        // Pops
62        while inner_stack_effect.peek(Ident) {
63            let name: Ident = inner_stack_effect.parse()?;
64
65            stack_effect.pops.push(
66                // This name is special, see the reference document on GitHub.
67                if name == "unused" {
68                    if inner_stack_effect.peek(syn::token::Bracket) {
69                        let inner_bracket;
70                        bracketed!(inner_bracket in inner_stack_effect);
71                        let size: Expr = inner_bracket.parse()?;
72                        StackItem::Unused(size)
73                    } else {
74                        StackItem::Unused(Expr::Lit(syn::ExprLit {
75                            attrs: vec![],
76                            lit: syn::Lit::Int(LitInt::new(
77                                "1",
78                                proc_macro::Span::call_site().into(),
79                            )),
80                        }))
81                    }
82                } else {
83                    if inner_stack_effect.peek(syn::token::Bracket) {
84                        let inner_bracket;
85                        bracketed!(inner_bracket in inner_stack_effect);
86                        let size: Expr = inner_bracket.parse()?;
87                        StackItem::NameCounted(name, size)
88                    } else {
89                        StackItem::Name(name)
90                    }
91                },
92            );
93
94            if inner_stack_effect.parse::<Token![,]>().is_err() {
95                break;
96            }
97        }
98
99        inner_stack_effect.parse::<Token![-]>()?;
100        inner_stack_effect.parse::<Token![-]>()?;
101
102        while inner_stack_effect.peek(Ident) {
103            let name: Ident = inner_stack_effect.parse()?;
104
105            stack_effect.pushes.push(
106                // This name is special, see the reference document on GitHub.
107                if name == "unused" {
108                    if inner_stack_effect.peek(syn::token::Bracket) {
109                        let inner_bracket;
110                        bracketed!(inner_bracket in inner_stack_effect);
111                        let size: Expr = inner_bracket.parse()?;
112                        StackItem::Unused(size)
113                    } else {
114                        StackItem::Unused(Expr::Lit(syn::ExprLit {
115                            attrs: vec![],
116                            lit: syn::Lit::Int(LitInt::new(
117                                "1",
118                                proc_macro::Span::call_site().into(),
119                            )),
120                        }))
121                    }
122                } else {
123                    if inner_stack_effect.peek(syn::token::Bracket) {
124                        let inner_bracket;
125                        bracketed!(inner_bracket in inner_stack_effect);
126                        let size: Expr = inner_bracket.parse()?;
127                        StackItem::NameCounted(name, size)
128                    } else {
129                        StackItem::Name(name)
130                    }
131                },
132            );
133
134            if inner_stack_effect.parse::<Token![,]>().is_err() {
135                break;
136            }
137        }
138
139        Ok(Opcode {
140            name,
141            number,
142            stack_effect: Some(stack_effect),
143        })
144    }
145}
146
147impl Parse for Exception {
148    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
149        // Special parsing for custom exception specification
150        // Example: *EXCEPTION ( -- lasti[if lasti {1} else {0}], exception)
151
152        input.parse::<Token![*]>()?;
153        syn::custom_keyword!(EXCEPTION);
154        input.parse::<EXCEPTION>()?;
155
156        let inner_stack_effect;
157
158        parenthesized!(inner_stack_effect in input);
159
160        let mut stack_effect = StackEffect {
161            pops: vec![],
162            pushes: vec![],
163        };
164
165        // Pops
166        while inner_stack_effect.peek(Ident) {
167            let name: Ident = inner_stack_effect.parse()?;
168
169            stack_effect.pops.push(
170                // This name is special, see the reference document on GitHub.
171                if name == "unused" {
172                    if inner_stack_effect.peek(syn::token::Bracket) {
173                        let inner_bracket;
174                        bracketed!(inner_bracket in inner_stack_effect);
175                        let size: Expr = inner_bracket.parse()?;
176                        StackItem::Unused(size)
177                    } else {
178                        StackItem::Unused(Expr::Lit(syn::ExprLit {
179                            attrs: vec![],
180                            lit: syn::Lit::Int(LitInt::new(
181                                "1",
182                                proc_macro::Span::call_site().into(),
183                            )),
184                        }))
185                    }
186                } else {
187                    if inner_stack_effect.peek(syn::token::Bracket) {
188                        let inner_bracket;
189                        bracketed!(inner_bracket in inner_stack_effect);
190                        let size: Expr = inner_bracket.parse()?;
191                        StackItem::NameCounted(name, size)
192                    } else {
193                        StackItem::Name(name)
194                    }
195                },
196            );
197
198            if inner_stack_effect.parse::<Token![,]>().is_err() {
199                break;
200            }
201        }
202
203        inner_stack_effect.parse::<Token![-]>()?;
204        inner_stack_effect.parse::<Token![-]>()?;
205
206        while inner_stack_effect.peek(Ident) {
207            let name: Ident = inner_stack_effect.parse()?;
208
209            stack_effect.pushes.push(
210                // This name is special, see the reference document on GitHub.
211                if name == "unused" {
212                    if inner_stack_effect.peek(syn::token::Bracket) {
213                        let inner_bracket;
214                        bracketed!(inner_bracket in inner_stack_effect);
215                        let size: Expr = inner_bracket.parse()?;
216                        StackItem::Unused(size)
217                    } else {
218                        StackItem::Unused(Expr::Lit(syn::ExprLit {
219                            attrs: vec![],
220                            lit: syn::Lit::Int(LitInt::new(
221                                "1",
222                                proc_macro::Span::call_site().into(),
223                            )),
224                        }))
225                    }
226                } else {
227                    if inner_stack_effect.peek(syn::token::Bracket) {
228                        let inner_bracket;
229                        bracketed!(inner_bracket in inner_stack_effect);
230                        let size: Expr = inner_bracket.parse()?;
231                        StackItem::NameCounted(name, size)
232                    } else {
233                        StackItem::Name(name)
234                    }
235                },
236            );
237
238            if inner_stack_effect.parse::<Token![,]>().is_err() {
239                break;
240            }
241        }
242
243        Ok(Exception { stack_effect })
244    }
245}
246
247struct Opcodes {
248    opcodes: Vec<Opcode>,
249    exception: Option<Exception>,
250}
251
252impl Parse for Opcodes {
253    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
254        let mut opcodes = vec![];
255        let mut exception = None;
256
257        loop {
258            if input.peek(Token![*]) {
259                exception = Some(Exception::parse(input)?);
260            } else {
261                opcodes.push(Opcode::parse(input)?);
262            }
263
264            if input.parse::<Token![,]>().is_err() || input.is_empty() {
265                break;
266            }
267        }
268
269        Ok(Opcodes { opcodes, exception })
270    }
271}
272
273fn sum_items(items: &[StackItem]) -> Expr {
274    if items.is_empty() {
275        // 0 if empty
276        Expr::Lit(syn::ExprLit {
277            attrs: vec![],
278            lit: syn::Lit::Int(LitInt::new("0", proc_macro::Span::call_site().into())),
279        })
280    } else {
281        items
282            .iter()
283            .map(|p| match p {
284                StackItem::Name(_) => Expr::Lit(syn::ExprLit {
285                    attrs: vec![],
286                    lit: syn::Lit::Int(LitInt::new("1", proc_macro::Span::call_site().into())),
287                }),
288                StackItem::NameCounted(_, size) => size.clone(),
289                StackItem::Unused(size) => size.clone(),
290            })
291            .reduce(|left, right| {
292                syn::Expr::Binary(syn::ExprBinary {
293                    attrs: vec![],
294                    left: Box::new(left),
295                    op: syn::BinOp::Add(syn::token::Plus {
296                        spans: [proc_macro::Span::call_site().into()],
297                    }),
298                    right: Box::new(right),
299                })
300            })
301            .expect("Something is wrong with the format")
302    }
303}
304
305fn collect_stack_effect<'a, T>(
306    stack_items: T,
307    index_offset: Option<proc_macro2::TokenStream>,
308) -> (Vec<proc_macro2::TokenStream>, proc_macro2::TokenStream)
309where
310    T: DoubleEndedIterator<Item = &'a StackItem>,
311{
312    let mut index = if let Some(ref index_offset) = index_offset {
313        index_offset.clone()
314    } else {
315        quote! { 0 }
316    };
317
318    let mut prev_index = index.clone();
319
320    let mut fields = vec![];
321
322    for item in stack_items.rev() {
323        prev_index = index.clone();
324        let count = match item {
325            StackItem::Name(name) => {
326                let name = name.to_string();
327
328                let new_index = if index_offset.is_none() {
329                    quote! { (#index) - 1 }
330                } else {
331                    index.clone()
332                };
333
334                fields.push(quote! { StackItem { name: #name, count: 1, index: #new_index } });
335                quote! { 1 }
336            }
337            StackItem::NameCounted(name, count) => {
338                let name = name.to_string();
339
340                let new_index = if index_offset.is_none() {
341                    quote! { (#index) - (#count) }
342                } else {
343                    index.clone()
344                };
345
346                fields.push(
347                    quote! { StackItem { name: #name, count: (#count) as u32, index: #new_index } },
348                );
349                quote! { #count }
350            }
351            StackItem::Unused(count) => quote! { #count },
352        };
353
354        if index_offset.is_none() {
355            index = quote! { (#index) - (#count) };
356        } else {
357            index = quote! { (#index) + (#count) };
358        }
359    }
360
361    if index_offset.is_none() {
362        fields.reverse();
363    }
364
365    (fields, index)
366}
367
368#[proc_macro]
369pub fn define_opcodes(input: TokenStream) -> TokenStream {
370    let Opcodes { opcodes, exception } = parse_macro_input!(input as Opcodes);
371
372    let opcodes_with_stack: Vec<_> = opcodes
373        .iter()
374        .filter(|o| o.stack_effect.is_some())
375        .collect();
376
377    let names: Vec<_> = opcodes.iter().map(|o| &o.name).collect();
378    let camel_names: Vec<Ident> = names
379        .iter()
380        .map(|ident| {
381            let camel = ident.to_string().to_upper_camel_case();
382            Ident::new(&camel, ident.span())
383        })
384        .collect();
385
386    let names_with_stack: Vec<_> = opcodes_with_stack.iter().map(|o| &o.name).collect();
387
388    let numbers: Vec<_> = opcodes.iter().map(|o| &o.number).collect();
389
390    let pops: Vec<_> = opcodes_with_stack
391        .iter()
392        .map(|o| sum_items(&o.stack_effect.as_ref().unwrap().pops))
393        .collect();
394
395    let pushes: Vec<_> = opcodes_with_stack
396        .iter()
397        .map(|o| sum_items(&o.stack_effect.as_ref().unwrap().pushes))
398        .collect();
399
400    let mut expanded = quote! {
401        #[allow(non_camel_case_types)]
402        #[allow(clippy::upper_case_acronyms)]
403        #[derive(Debug, Clone, PartialEq, Eq)]
404        pub enum Opcode {
405            #( #names ),*,
406            INVALID_OPCODE(u8),
407        }
408
409        impl From<u8> for Opcode {
410            fn from(value: u8) -> Self {
411                match value {
412                    #( #numbers => Opcode::#names, )*
413                    _ => Opcode::INVALID_OPCODE(value),
414                }
415            }
416        }
417
418        impl From<Opcode> for u8 {
419            fn from(value: Opcode) -> Self {
420                match value {
421                    #( Opcode::#names => #numbers , )*
422                    Opcode::INVALID_OPCODE(value) => value,
423                }
424            }
425        }
426
427        impl From<(Opcode, u8)> for Instruction {
428            fn from(value: (Opcode, u8)) -> Self {
429                match value.0 {
430                    #(
431                        Opcode::#names => Instruction::#camel_names(value.1),
432                    )*
433                    Opcode::INVALID_OPCODE(opcode) => {
434                        if !cfg!(test) {
435                            Instruction::InvalidOpcode((opcode, value.1))
436                        } else {
437                            panic!("Testing environment should not come across invalid opcodes")
438                        }
439                    },
440                }
441            }
442        }
443
444        impl Opcode {
445            pub fn from_instruction(instruction: &Instruction) -> Self {
446                match instruction {
447                    #(
448                        Instruction::#camel_names(_) => Opcode::#names ,
449                    )*
450                    Instruction::InvalidOpcode((opcode, _)) => Opcode::INVALID_OPCODE(*opcode),
451                }
452            }
453        }
454
455        impl StackEffectTrait for Opcode {
456            fn stack_effect(&self, oparg: u32, jump: bool, calculate_max: bool) -> StackEffect {
457                match &self {
458                    #(
459                        Opcode::#names_with_stack => StackEffect { pops: #pops, pushes: #pushes },
460                    )*
461                    Opcode::INVALID_OPCODE(_) => StackEffect { pops: 0, pushes: 0 },
462
463                    _ => unimplemented!("stack_effect not implemented for {:?}", self),
464                }
465            }
466        }
467    };
468
469    let mut input_sirs = vec![];
470    let mut output_sirs = vec![];
471    let mut stack_deltas = vec![];
472
473    for (opcode, name) in opcodes.iter().zip(names) {
474        let mut input_constructor_fields = vec![];
475        let mut output_constructor_fields = vec![];
476        let mut stack_delta = quote! { 0 };
477
478        if let Some(stack_effect) = &opcode.stack_effect {
479            let input_offset;
480            (input_constructor_fields, input_offset) =
481                collect_stack_effect(stack_effect.pops.iter(), None);
482
483            (output_constructor_fields, _) =
484                collect_stack_effect(stack_effect.pushes.iter().rev(), Some(input_offset));
485
486            let pushes = sum_items(&stack_effect.pushes);
487            let pops = sum_items(&stack_effect.pops);
488
489            stack_delta = quote! { (#pushes) as isize - (#pops) as isize};
490        }
491
492        input_sirs.push(quote! { Opcode::#name => vec![
493            #(
494                #input_constructor_fields
495            ),*
496        ] });
497
498        output_sirs.push(quote! { Opcode::#name => vec![
499            #(
500                #output_constructor_fields
501            ),*
502        ] });
503
504        stack_deltas.push(quote! { Opcode::#name => #stack_delta });
505    }
506
507    let sir_exception = if let Some(exception) = exception {
508        let (input_fields, input_offset) =
509            collect_stack_effect(exception.stack_effect.pops.iter(), None);
510
511        let (output_fields, _) = collect_stack_effect(
512            exception.stack_effect.pushes.iter().rev(),
513            Some(input_offset),
514        );
515
516        let pushes = sum_items(&exception.stack_effect.pushes);
517        let pops = sum_items(&exception.stack_effect.pops);
518
519        let stack_delta = quote! { (#pushes) as isize - (#pops) as isize};
520
521        quote! {
522            #[derive(PartialEq, Debug, Clone)]
523            pub struct SIRException {
524                pub lasti: bool,
525                pub stack_depth: usize,
526                pub input: Vec<StackItem>,
527                pub output: Vec<StackItem>,
528                pub net_stack_delta: isize
529            }
530
531            impl SIRException {
532                pub fn new(lasti: bool, stack_depth: usize, jump: bool) -> Self {
533                    let input = vec![
534                        #(
535                            #input_fields
536                        ),*
537                    ];
538
539                    let output = vec![
540                        #(
541                            #output_fields
542                        ),*
543                    ];
544
545                    let net_stack_delta = #stack_delta;
546
547                    Self {
548                        lasti,
549                        stack_depth,
550                        input,
551                        output,
552                        net_stack_delta,
553                    }
554                }
555            }
556
557            impl GenericSIRException for SIRException {
558                type Opcode = Opcode;
559
560                fn new(lasti: bool, stack_depth: usize, jump: bool) -> Self {
561                    SIRException::new(lasti, stack_depth, jump)
562                }
563
564                fn get_outputs(&self) -> &[StackItem] {
565                    &self.output
566                }
567
568                fn get_inputs(&self) -> &[StackItem] {
569                    &self.input
570                }
571
572                fn get_net_stack_delta(&self) -> isize {
573                    self.net_stack_delta
574                }
575
576                fn get_stack_depth(&self) -> usize {
577                    self.stack_depth
578                }
579            }
580
581            impl std::fmt::Display for ExceptionCall<SIRNode> {
582                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
583                    let mut inputs = self
584                        .stack_inputs
585                        .iter()
586                        .map(|input| format!("{}", input))
587                        .collect::<Vec<_>>();
588
589                    inputs.push(format!("{}", self.exception.lasti));
590
591                    write!(f, "EXCEPTION({})", inputs.join(", "))
592                }
593            }
594        }
595    } else {
596        quote! {#[derive(PartialEq, Debug, Clone)]
597            /// This does not exist in versions without an exception_table
598            /// So we generate an empty version of it
599            pub struct SIRException {
600            }
601
602            impl std::fmt::Display for ExceptionCall<SIRNode> {
603                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
604                    unreachable!()
605                }
606            }
607
608            impl GenericSIRException for SIRException {
609                type Opcode = Opcode;
610
611                fn new(lasti: bool, stack_depth: usize, jump: bool) -> Self {
612                    SIRException {}
613                }
614
615                fn get_outputs(&self) -> &[StackItem] {
616                    &[]
617                }
618
619                fn get_inputs(&self) -> &[StackItem] {
620                    &[]
621                }
622
623                fn get_net_stack_delta(&self) -> isize {
624                    0
625                }
626
627                fn get_stack_depth(&self) -> usize {
628                    0
629                }
630            }
631        }
632    };
633
634    let sir = quote! {
635        pub mod sir {
636            use super::{Opcode};
637            use crate::sir::{SIR, StackItem, SIRStatement, ExceptionCall, Call, SIRExpression, AuxVar};
638            use crate::traits::{GenericSIRNode, SIROwned, GenericSIRException};
639
640
641            #[derive(PartialEq, Debug, Clone)]
642            pub struct SIRNode {
643                pub opcode: Opcode,
644                pub oparg: u32,
645                pub input: Vec<StackItem>,
646                pub output: Vec<StackItem>,
647                pub net_stack_delta: isize
648            }
649
650            impl SIRNode {
651                pub fn new(opcode: Opcode, oparg: u32, jump: bool) -> Self {
652                    // This comes from the Python DSL where it is used to calculate the max stack usage possible. We intentionally disable it here.
653                    let calculate_max = false;
654                    let oparg = oparg as isize;
655
656                    let input = match opcode {
657                        #(
658                            #input_sirs
659                        ),*,
660                        Opcode::INVALID_OPCODE(_) => vec![],
661                    };
662
663                    let output = match opcode {
664                        #(
665                            #output_sirs
666                        ),*,
667                        Opcode::INVALID_OPCODE(_) => vec![],
668                    };
669
670                    let net_stack_delta = match opcode {
671                        #(
672                            #stack_deltas
673                        ),*,
674                        Opcode::INVALID_OPCODE(_) => 0,
675                    };
676
677                    Self {
678                        opcode,
679                        oparg: oparg as u32,
680                        input,
681                        output,
682                        net_stack_delta
683                    }
684                }
685            }
686
687            #sir_exception
688
689            impl GenericSIRNode for SIRNode {
690                type Opcode = Opcode;
691                type SIRException = SIRException;
692
693                fn new(opcode: Self::Opcode, oparg: u32, jump: bool) -> Self {
694                    SIRNode::new(opcode, oparg, jump)
695                }
696
697                fn get_outputs(&self) -> &[StackItem] {
698                    &self.output
699                }
700
701                fn get_inputs(&self) -> &[StackItem] {
702                    &self.input
703                }
704
705                fn get_net_stack_delta(&self) -> isize {
706                    self.net_stack_delta
707                }
708            }
709
710            impl SIROwned<SIRNode> for SIR<SIRNode> {
711                fn new(statements: Vec<SIRStatement<SIRNode>>) -> Self {
712                    SIR(statements)
713                }
714            }
715
716            impl std::fmt::Display for SIR<SIRNode> {
717                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718                    for statement in &self.0 {
719                        match statement {
720                            SIRStatement::Assignment(aux_var, call) => {
721                                writeln!(f, "{} = {}", aux_var.name, call)?;
722                            }
723                            SIRStatement::TupleAssignment(aux_vars, call) => {
724                                let vars = aux_vars.iter().map(|v| v.name.clone()).collect::<Vec<_>>().join(", ");
725                                writeln!(f, "({}) = {}", vars, call)?;
726                            }
727                            SIRStatement::DisregardCall(call) => {
728                                writeln!(f, "{}", call)?;
729                            }
730                        }
731                    }
732                    Ok(())
733                }
734            }
735
736            impl std::fmt::Display for Call<SIRNode> {
737                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
738                    let mut inputs = self
739                        .stack_inputs
740                        .iter()
741                        .map(|input| format!("{}", input))
742                        .collect::<Vec<_>>();
743
744                    inputs.push(format!("{}", self.node.oparg));
745
746                    write!(f, "{:#?}({})", self.node.opcode, inputs.join(", "))
747                }
748            }
749
750            impl std::fmt::Display for SIRExpression<SIRNode> {
751                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
752                    match self {
753                        SIRExpression::Call(call) => write!(f, "{}", call),
754                        SIRExpression::Exception(exception_call) => write!(f, "{}", exception_call),
755                        SIRExpression::AuxVar(aux_var) => write!(f, "{}", aux_var.name.clone()),
756                        SIRExpression::PhiNode(phi) => write!(f, "phi({})", phi.iter().map(|v| &v.name).cloned().collect::<Vec<_>>().join(", ")),
757                        SIRExpression::GeneratorStart => write!(f, "GenStart()"),
758                    }
759                }
760            }
761        }
762    };
763
764    expanded.extend(sir);
765
766    expanded.into()
767}
768
769#[cfg(test)]
770mod tests {
771    use proc_macro2::Span;
772    use syn::Ident;
773
774    use crate::StackItem;
775
776    #[test]
777    fn test_stack_effect() {
778        let inputs = [
779            StackItem::Name(Ident::new("first", Span::call_site())),
780            StackItem::Name(Ident::new("second", Span::call_site())),
781        ];
782
783        let outputs = [
784            StackItem::Name(Ident::new("out", Span::call_site())),
785            StackItem::Name(Ident::new("out2", Span::call_site())),
786        ];
787
788        let (input_fields, input_offset) = crate::collect_stack_effect(inputs.iter(), None);
789
790        let (output_fields, _) =
791            crate::collect_stack_effect(outputs.iter().rev(), Some(input_offset));
792
793        for input_field in input_fields {
794            println!("{}", input_field);
795        }
796
797        assert_eq!(
798            format!("{}", output_fields[0]),
799            "StackItem { name : \"out\" , count : 1 , index : ((0) - (1)) - (1) }"
800        );
801
802        assert_eq!(
803            format!("{}", output_fields[1]),
804            "StackItem { name : \"out2\" , count : 1 , index : (((0) - (1)) - (1)) + (1) }"
805        );
806
807        for output_field in output_fields {
808            println!("{}", output_field);
809        }
810    }
811
812    #[test]
813    fn test_stack_effect2() {
814        let inputs = [
815            StackItem::NameCounted(
816                Ident::new("first", Span::call_site()),
817                syn::Expr::Lit(syn::ExprLit {
818                    attrs: vec![],
819                    lit: syn::Lit::Int(syn::LitInt::new("5", Span::call_site().into())),
820                }),
821            ),
822            StackItem::Name(Ident::new("second", Span::call_site())),
823        ];
824
825        let outputs = [
826            StackItem::NameCounted(
827                Ident::new("out", Span::call_site()),
828                syn::Expr::Lit(syn::ExprLit {
829                    attrs: vec![],
830                    lit: syn::Lit::Int(syn::LitInt::new("5", Span::call_site().into())),
831                }),
832            ),
833            StackItem::Name(Ident::new("out2", Span::call_site())),
834        ];
835
836        let (input_fields, input_offset) = crate::collect_stack_effect(inputs.iter(), None);
837
838        let (output_fields, _) =
839            crate::collect_stack_effect(outputs.iter().rev(), Some(input_offset));
840
841        for input_field in input_fields {
842            println!("{}", input_field);
843        }
844
845        assert_eq!(
846            format!("{}", output_fields[0]),
847            "StackItem { name : \"out\" , count : (5) as u32 , index : ((0) - (1)) - (5) }"
848        );
849
850        assert_eq!(
851            format!("{}", output_fields[1]),
852            "StackItem { name : \"out2\" , count : 1 , index : (((0) - (1)) - (5)) + (5) }"
853        );
854
855        for output_field in output_fields {
856            println!("{}", output_field);
857        }
858    }
859
860    #[test]
861    fn test_stack_effect_copy() {
862        // Emulates the `COPY 3`` instruction
863        let inputs = [
864            StackItem::Name(Ident::new("bottom", Span::call_site())),
865            StackItem::Unused(syn::Expr::Lit(syn::ExprLit {
866                attrs: vec![],
867                lit: syn::Lit::Int(syn::LitInt::new("2", Span::call_site().into())),
868            })),
869        ];
870
871        let outputs = [
872            StackItem::Name(Ident::new("bottom", Span::call_site())),
873            StackItem::Unused(syn::Expr::Lit(syn::ExprLit {
874                attrs: vec![],
875                lit: syn::Lit::Int(syn::LitInt::new("2", Span::call_site().into())),
876            })),
877            StackItem::Name(Ident::new("top", Span::call_site())),
878        ];
879
880        let (input_fields, input_offset) = crate::collect_stack_effect(inputs.iter(), None);
881
882        let (output_fields, _) =
883            crate::collect_stack_effect(outputs.iter().rev(), Some(input_offset));
884
885        for input_field in input_fields {
886            println!("{}", input_field);
887        }
888
889        assert_eq!(
890            format!("{}", output_fields[0]),
891            "StackItem { name : \"bottom\" , count : 1 , index : ((0) - (2)) - (1) }"
892        );
893
894        assert_eq!(
895            format!("{}", output_fields[1]),
896            "StackItem { name : \"top\" , count : 1 , index : ((((0) - (2)) - (1)) + (1)) + (2) }"
897        );
898
899        for output_field in output_fields {
900            println!("{}", output_field);
901        }
902    }
903}