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        #[cfg(feature = "sir")]
636        pub mod sir {
637            use super::{Opcode};
638            use crate::sir::{SIR, StackItem, SIRStatement, ExceptionCall, Call, SIRExpression, AuxVar};
639            use crate::traits::{GenericSIRNode, SIROwned, GenericSIRException};
640
641
642            #[derive(PartialEq, Debug, Clone)]
643            pub struct SIRNode {
644                pub opcode: Opcode,
645                pub oparg: u32,
646                pub input: Vec<StackItem>,
647                pub output: Vec<StackItem>,
648                pub net_stack_delta: isize
649            }
650
651            impl SIRNode {
652                pub fn new(opcode: Opcode, oparg: u32, jump: bool) -> Self {
653                    // This comes from the Python DSL where it is used to calculate the max stack usage possible. We intentionally disable it here.
654                    let calculate_max = false;
655                    let oparg = oparg as isize;
656
657                    let input = match opcode {
658                        #(
659                            #input_sirs
660                        ),*,
661                        Opcode::INVALID_OPCODE(_) => vec![],
662                    };
663
664                    let output = match opcode {
665                        #(
666                            #output_sirs
667                        ),*,
668                        Opcode::INVALID_OPCODE(_) => vec![],
669                    };
670
671                    let net_stack_delta = match opcode {
672                        #(
673                            #stack_deltas
674                        ),*,
675                        Opcode::INVALID_OPCODE(_) => 0,
676                    };
677
678                    Self {
679                        opcode,
680                        oparg: oparg as u32,
681                        input,
682                        output,
683                        net_stack_delta
684                    }
685                }
686            }
687
688            #sir_exception
689
690            impl GenericSIRNode for SIRNode {
691                type Opcode = Opcode;
692                type SIRException = SIRException;
693
694                fn new(opcode: Self::Opcode, oparg: u32, jump: bool) -> Self {
695                    SIRNode::new(opcode, oparg, jump)
696                }
697
698                fn get_outputs(&self) -> &[StackItem] {
699                    &self.output
700                }
701
702                fn get_inputs(&self) -> &[StackItem] {
703                    &self.input
704                }
705
706                fn get_net_stack_delta(&self) -> isize {
707                    self.net_stack_delta
708                }
709            }
710
711            impl SIROwned<SIRNode> for SIR<SIRNode> {
712                fn new(statements: Vec<SIRStatement<SIRNode>>) -> Self {
713                    SIR(statements)
714                }
715            }
716
717            impl std::fmt::Display for SIR<SIRNode> {
718                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
719                    for statement in &self.0 {
720                        match statement {
721                            SIRStatement::Assignment(aux_var, call) => {
722                                writeln!(f, "{} = {}", aux_var.name, call)?;
723                            }
724                            SIRStatement::TupleAssignment(aux_vars, call) => {
725                                let vars = aux_vars.iter().map(|v| v.name.clone()).collect::<Vec<_>>().join(", ");
726                                writeln!(f, "({}) = {}", vars, call)?;
727                            }
728                            SIRStatement::DisregardCall(call) => {
729                                writeln!(f, "{}", call)?;
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::AuxVar(aux_var) => write!(f, "{}", aux_var.name.clone()),
757                        SIRExpression::PhiNode(phi) => write!(f, "phi({})", phi.iter().map(|v| &v.name).cloned().collect::<Vec<_>>().join(", ")),
758                        SIRExpression::GeneratorStart => write!(f, "GenStart()"),
759                    }
760                }
761            }
762        }
763    };
764
765    expanded.extend(sir);
766
767    expanded.into()
768}
769
770#[cfg(test)]
771mod tests {
772    use proc_macro2::Span;
773    use syn::Ident;
774
775    use crate::StackItem;
776
777    #[test]
778    fn test_stack_effect() {
779        let inputs = [
780            StackItem::Name(Ident::new("first", Span::call_site())),
781            StackItem::Name(Ident::new("second", Span::call_site())),
782        ];
783
784        let outputs = [
785            StackItem::Name(Ident::new("out", Span::call_site())),
786            StackItem::Name(Ident::new("out2", Span::call_site())),
787        ];
788
789        let (input_fields, input_offset) = crate::collect_stack_effect(inputs.iter(), None);
790
791        let (output_fields, _) =
792            crate::collect_stack_effect(outputs.iter().rev(), Some(input_offset));
793
794        for input_field in input_fields {
795            println!("{}", input_field);
796        }
797
798        assert_eq!(
799            format!("{}", output_fields[0]),
800            "StackItem { name : \"out\" , count : 1 , index : ((0) - (1)) - (1) }"
801        );
802
803        assert_eq!(
804            format!("{}", output_fields[1]),
805            "StackItem { name : \"out2\" , count : 1 , index : (((0) - (1)) - (1)) + (1) }"
806        );
807
808        for output_field in output_fields {
809            println!("{}", output_field);
810        }
811    }
812
813    #[test]
814    fn test_stack_effect2() {
815        let inputs = [
816            StackItem::NameCounted(
817                Ident::new("first", Span::call_site()),
818                syn::Expr::Lit(syn::ExprLit {
819                    attrs: vec![],
820                    lit: syn::Lit::Int(syn::LitInt::new("5", Span::call_site().into())),
821                }),
822            ),
823            StackItem::Name(Ident::new("second", Span::call_site())),
824        ];
825
826        let outputs = [
827            StackItem::NameCounted(
828                Ident::new("out", Span::call_site()),
829                syn::Expr::Lit(syn::ExprLit {
830                    attrs: vec![],
831                    lit: syn::Lit::Int(syn::LitInt::new("5", Span::call_site().into())),
832                }),
833            ),
834            StackItem::Name(Ident::new("out2", Span::call_site())),
835        ];
836
837        let (input_fields, input_offset) = crate::collect_stack_effect(inputs.iter(), None);
838
839        let (output_fields, _) =
840            crate::collect_stack_effect(outputs.iter().rev(), Some(input_offset));
841
842        for input_field in input_fields {
843            println!("{}", input_field);
844        }
845
846        assert_eq!(
847            format!("{}", output_fields[0]),
848            "StackItem { name : \"out\" , count : (5) as u32 , index : ((0) - (1)) - (5) }"
849        );
850
851        assert_eq!(
852            format!("{}", output_fields[1]),
853            "StackItem { name : \"out2\" , count : 1 , index : (((0) - (1)) - (5)) + (5) }"
854        );
855
856        for output_field in output_fields {
857            println!("{}", output_field);
858        }
859    }
860
861    #[test]
862    fn test_stack_effect_copy() {
863        // Emulates the `COPY 3`` instruction
864        let inputs = [
865            StackItem::Name(Ident::new("bottom", Span::call_site())),
866            StackItem::Unused(syn::Expr::Lit(syn::ExprLit {
867                attrs: vec![],
868                lit: syn::Lit::Int(syn::LitInt::new("2", Span::call_site().into())),
869            })),
870        ];
871
872        let outputs = [
873            StackItem::Name(Ident::new("bottom", Span::call_site())),
874            StackItem::Unused(syn::Expr::Lit(syn::ExprLit {
875                attrs: vec![],
876                lit: syn::Lit::Int(syn::LitInt::new("2", Span::call_site().into())),
877            })),
878            StackItem::Name(Ident::new("top", Span::call_site())),
879        ];
880
881        let (input_fields, input_offset) = crate::collect_stack_effect(inputs.iter(), None);
882
883        let (output_fields, _) =
884            crate::collect_stack_effect(outputs.iter().rev(), Some(input_offset));
885
886        for input_field in input_fields {
887            println!("{}", input_field);
888        }
889
890        assert_eq!(
891            format!("{}", output_fields[0]),
892            "StackItem { name : \"bottom\" , count : 1 , index : ((0) - (2)) - (1) }"
893        );
894
895        assert_eq!(
896            format!("{}", output_fields[1]),
897            "StackItem { name : \"top\" , count : 1 , index : ((((0) - (2)) - (1)) + (1)) + (2) }"
898        );
899
900        for output_field in output_fields {
901            println!("{}", output_field);
902        }
903    }
904}