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 fields = vec![];
319
320    for item in stack_items.rev() {
321        let count = match item {
322            StackItem::Name(name) => {
323                let name = name.to_string();
324
325                let new_index = if index_offset.is_none() {
326                    quote! { (#index) - 1 }
327                } else {
328                    index.clone()
329                };
330
331                fields.push(quote! { StackItem { name: #name, count: 1, index: #new_index } });
332                quote! { 1 }
333            }
334            StackItem::NameCounted(name, count) => {
335                let name = name.to_string();
336
337                let new_index = if index_offset.is_none() {
338                    quote! { (#index) - (#count) }
339                } else {
340                    index.clone()
341                };
342
343                fields.push(
344                    quote! { StackItem { name: #name, count: (#count) as u32, index: #new_index } },
345                );
346                quote! { #count }
347            }
348            StackItem::Unused(count) => quote! { #count },
349        };
350
351        if index_offset.is_none() {
352            index = quote! { (#index) - (#count) };
353        } else {
354            index = quote! { (#index) + (#count) };
355        }
356    }
357
358    if index_offset.is_none() {
359        fields.reverse();
360    }
361
362    (fields, index)
363}
364
365#[proc_macro]
366pub fn define_opcodes(input: TokenStream) -> TokenStream {
367    let Opcodes { opcodes, exception } = parse_macro_input!(input as Opcodes);
368
369    let opcodes_with_stack: Vec<_> = opcodes
370        .iter()
371        .filter(|o| o.stack_effect.is_some())
372        .collect();
373
374    let names: Vec<_> = opcodes.iter().map(|o| &o.name).collect();
375    let camel_names: Vec<Ident> = names
376        .iter()
377        .map(|ident| {
378            let camel = ident.to_string().to_upper_camel_case();
379            Ident::new(&camel, ident.span())
380        })
381        .collect();
382
383    let names_with_stack: Vec<_> = opcodes_with_stack.iter().map(|o| &o.name).collect();
384
385    let numbers: Vec<_> = opcodes.iter().map(|o| &o.number).collect();
386
387    let pops: Vec<_> = opcodes_with_stack
388        .iter()
389        .map(|o| sum_items(&o.stack_effect.as_ref().unwrap().pops))
390        .collect();
391
392    let pushes: Vec<_> = opcodes_with_stack
393        .iter()
394        .map(|o| sum_items(&o.stack_effect.as_ref().unwrap().pushes))
395        .collect();
396
397    let mut expanded = quote! {
398        #[allow(non_camel_case_types)]
399        #[allow(clippy::upper_case_acronyms)]
400        #[derive(Debug, Clone, PartialEq, Eq)]
401        pub enum Opcode {
402            #( #names ),*,
403            INVALID_OPCODE(u8),
404        }
405
406        impl From<u8> for Opcode {
407            fn from(value: u8) -> Self {
408                match value {
409                    #( #numbers => Opcode::#names, )*
410                    _ => Opcode::INVALID_OPCODE(value),
411                }
412            }
413        }
414
415        impl From<Opcode> for u8 {
416            fn from(value: Opcode) -> Self {
417                match value {
418                    #( Opcode::#names => #numbers , )*
419                    Opcode::INVALID_OPCODE(value) => value,
420                }
421            }
422        }
423
424        impl From<(Opcode, u8)> for Instruction {
425            fn from(value: (Opcode, u8)) -> Self {
426                match value.0 {
427                    #(
428                        Opcode::#names => Instruction::#camel_names(value.1),
429                    )*
430                    Opcode::INVALID_OPCODE(opcode) => {
431                        if !cfg!(test) {
432                            Instruction::InvalidOpcode((opcode, value.1))
433                        } else {
434                            panic!("Testing environment should not come across invalid opcodes")
435                        }
436                    },
437                }
438            }
439        }
440
441        impl Opcode {
442            pub fn from_instruction(instruction: &Instruction) -> Self {
443                match instruction {
444                    #(
445                        Instruction::#camel_names(_) => Opcode::#names ,
446                    )*
447                    Instruction::InvalidOpcode((opcode, _)) => Opcode::INVALID_OPCODE(*opcode),
448                }
449            }
450        }
451
452        impl StackEffectTrait for Opcode {
453            fn stack_effect(&self, oparg: u32, jump: bool, calculate_max: bool) -> StackEffect {
454                match &self {
455                    #(
456                        Opcode::#names_with_stack => StackEffect { pops: #pops, pushes: #pushes },
457                    )*
458                    Opcode::INVALID_OPCODE(_) => StackEffect { pops: 0, pushes: 0 },
459
460                    _ => unimplemented!("stack_effect not implemented for {:?}", self),
461                }
462            }
463        }
464    };
465
466    let mut input_sirs = vec![];
467    let mut output_sirs = vec![];
468    let mut stack_deltas = vec![];
469
470    for (opcode, name) in opcodes.iter().zip(names) {
471        let mut input_constructor_fields = vec![];
472        let mut output_constructor_fields = vec![];
473        let mut stack_delta = quote! { 0 };
474
475        if let Some(stack_effect) = &opcode.stack_effect {
476            let input_offset;
477            (input_constructor_fields, input_offset) =
478                collect_stack_effect(stack_effect.pops.iter(), None);
479
480            (output_constructor_fields, _) =
481                collect_stack_effect(stack_effect.pushes.iter().rev(), Some(input_offset));
482
483            let pushes = sum_items(&stack_effect.pushes);
484            let pops = sum_items(&stack_effect.pops);
485
486            stack_delta = quote! { (#pushes) as isize - (#pops) as isize};
487        }
488
489        input_sirs.push(quote! { Opcode::#name => vec![
490            #(
491                #input_constructor_fields
492            ),*
493        ] });
494
495        output_sirs.push(quote! { Opcode::#name => vec![
496            #(
497                #output_constructor_fields
498            ),*
499        ] });
500
501        stack_deltas.push(quote! { Opcode::#name => #stack_delta });
502    }
503
504    let sir_exception = if let Some(exception) = exception {
505        let (input_fields, input_offset) =
506            collect_stack_effect(exception.stack_effect.pops.iter(), None);
507
508        let (output_fields, _) = collect_stack_effect(
509            exception.stack_effect.pushes.iter().rev(),
510            Some(input_offset),
511        );
512
513        let pushes = sum_items(&exception.stack_effect.pushes);
514        let pops = sum_items(&exception.stack_effect.pops);
515
516        let stack_delta = quote! { (#pushes) as isize - (#pops) as isize};
517
518        quote! {
519            #[derive(PartialEq, Debug, Clone)]
520            pub struct SIRException {
521                pub lasti: bool,
522                pub stack_depth: usize,
523                pub input: Vec<StackItem>,
524                pub output: Vec<StackItem>,
525                pub net_stack_delta: isize
526            }
527
528            impl SIRException {
529                pub fn new(lasti: bool, stack_depth: usize, jump: bool) -> Self {
530                    let input = vec![
531                        #(
532                            #input_fields
533                        ),*
534                    ];
535
536                    let output = vec![
537                        #(
538                            #output_fields
539                        ),*
540                    ];
541
542                    let net_stack_delta = #stack_delta;
543
544                    Self {
545                        lasti,
546                        stack_depth,
547                        input,
548                        output,
549                        net_stack_delta,
550                    }
551                }
552            }
553
554            impl GenericSIRException for SIRException {
555                type Opcode = Opcode;
556
557                fn new(lasti: bool, stack_depth: usize, jump: bool) -> Self {
558                    SIRException::new(lasti, stack_depth, jump)
559                }
560
561                fn get_outputs(&self) -> &[StackItem] {
562                    &self.output
563                }
564
565                fn get_inputs(&self) -> &[StackItem] {
566                    &self.input
567                }
568
569                fn get_net_stack_delta(&self) -> isize {
570                    self.net_stack_delta
571                }
572
573                fn get_stack_depth(&self) -> usize {
574                    self.stack_depth
575                }
576            }
577
578            impl std::fmt::Display for ExceptionCall<SIRNode> {
579                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580                    let mut inputs = self
581                        .stack_inputs
582                        .iter()
583                        .map(|input| format!("{}", input))
584                        .collect::<Vec<_>>();
585
586                    inputs.push(format!("{}", self.exception.lasti));
587
588                    write!(f, "EXCEPTION({})", inputs.join(", "))
589                }
590            }
591        }
592    } else {
593        quote! {#[derive(PartialEq, Debug, Clone)]
594            /// This does not exist in versions without an exception_table
595            /// So we generate an empty version of it
596            pub struct SIRException {
597            }
598
599            impl std::fmt::Display for ExceptionCall<SIRNode> {
600                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601                    unreachable!()
602                }
603            }
604
605            impl GenericSIRException for SIRException {
606                type Opcode = Opcode;
607
608                fn new(lasti: bool, stack_depth: usize, jump: bool) -> Self {
609                    SIRException {}
610                }
611
612                fn get_outputs(&self) -> &[StackItem] {
613                    &[]
614                }
615
616                fn get_inputs(&self) -> &[StackItem] {
617                    &[]
618                }
619
620                fn get_net_stack_delta(&self) -> isize {
621                    0
622                }
623
624                fn get_stack_depth(&self) -> usize {
625                    0
626                }
627            }
628        }
629    };
630
631    let sir = quote! {
632        #[cfg(feature = "sir")]
633        pub mod sir {
634            use super::{Opcode};
635            use crate::sir::{SIR, StackItem, SIRStatement, ExceptionCall, Call, SIRExpression, AuxVar};
636            use crate::traits::{GenericSIRNode, SIROwned, GenericSIRException};
637
638
639            #[derive(PartialEq, Debug, Clone)]
640            pub struct SIRNode {
641                pub opcode: Opcode,
642                pub oparg: u32,
643                pub input: Vec<StackItem>,
644                pub output: Vec<StackItem>,
645                pub net_stack_delta: isize
646            }
647
648            impl SIRNode {
649                pub fn new(opcode: Opcode, oparg: u32, jump: bool) -> Self {
650                    // This comes from the Python DSL where it is used to calculate the max stack usage possible. We intentionally disable it here.
651                    let calculate_max = false;
652                    let oparg = oparg as isize;
653
654                    let input = match opcode {
655                        #(
656                            #input_sirs
657                        ),*,
658                        Opcode::INVALID_OPCODE(_) => vec![],
659                    };
660
661                    let output = match opcode {
662                        #(
663                            #output_sirs
664                        ),*,
665                        Opcode::INVALID_OPCODE(_) => vec![],
666                    };
667
668                    let net_stack_delta = match opcode {
669                        #(
670                            #stack_deltas
671                        ),*,
672                        Opcode::INVALID_OPCODE(_) => 0,
673                    };
674
675                    Self {
676                        opcode,
677                        oparg: oparg as u32,
678                        input,
679                        output,
680                        net_stack_delta
681                    }
682                }
683            }
684
685            #sir_exception
686
687            impl GenericSIRNode for SIRNode {
688                type Opcode = Opcode;
689                type SIRException = SIRException;
690
691                fn new(opcode: Self::Opcode, oparg: u32, jump: bool) -> Self {
692                    SIRNode::new(opcode, oparg, jump)
693                }
694
695                fn get_outputs(&self) -> &[StackItem] {
696                    &self.output
697                }
698
699                fn get_inputs(&self) -> &[StackItem] {
700                    &self.input
701                }
702
703                fn get_net_stack_delta(&self) -> isize {
704                    self.net_stack_delta
705                }
706            }
707
708            impl SIROwned<SIRNode> for SIR<SIRNode> {
709                fn new(statements: Vec<SIRStatement<SIRNode>>) -> Self {
710                    SIR(statements)
711                }
712            }
713
714            impl std::fmt::Display for SIR<SIRNode> {
715                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
716                    for statement in &self.0 {
717                        match statement {
718                            SIRStatement::Assignment(aux_var, call) => {
719                                writeln!(f, "{} = {}", aux_var.name, call)?;
720                            }
721                            SIRStatement::TupleAssignment(aux_vars, call) => {
722                                let vars = aux_vars.iter().map(|v| v.name.clone()).collect::<Vec<_>>().join(", ");
723                                writeln!(f, "({}) = {}", vars, call)?;
724                            }
725                            SIRStatement::DisregardCall(call) => {
726                                writeln!(f, "{}", call)?;
727                            }
728                            SIRStatement::UseVar(aux_var) => {
729                                write!(f, "UseVar({})", aux_var.name.clone())?;
730                            },
731                        }
732                    }
733                    Ok(())
734                }
735            }
736
737            impl std::fmt::Display for Call<SIRNode> {
738                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
739                    let mut inputs = self
740                        .stack_inputs
741                        .iter()
742                        .map(|input| format!("{}", input))
743                        .collect::<Vec<_>>();
744
745                    inputs.push(format!("{}", self.node.oparg));
746
747                    write!(f, "{:#?}({})", self.node.opcode, inputs.join(", "))
748                }
749            }
750
751            impl std::fmt::Display for SIRExpression<SIRNode> {
752                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
753                    match self {
754                        SIRExpression::Call(call) => write!(f, "{}", call),
755                        SIRExpression::Exception(exception_call) => write!(f, "{}", exception_call),
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}