typhoon_metadata_extractor/
parsing.rs

1use syn::{Expr, File, Ident, Item, ItemFn, Stmt, Type};
2
3#[derive(Default, Debug)]
4pub struct ParsingContext<'a> {
5    pub contexts: Vec<&'a Ident>,
6    pub instructions: Vec<&'a Ident>,
7    pub accounts: Vec<&'a Ident>,
8}
9
10impl<'a> From<&'a File> for ParsingContext<'a> {
11    fn from(value: &'a File) -> Self {
12        let mut context = ParsingContext::default();
13        value.items.iter().for_each(|item| match item {
14            Item::Impl(item_impl) => {
15                if let Some(ident) = extract_ident(item_impl, "HandlerContext") {
16                    context.contexts.push(ident);
17                }
18
19                if let Some(ident) = extract_ident(item_impl, "Owner") {
20                    context.accounts.push(ident);
21                }
22            }
23            Item::Fn(item_fn) => {
24                if let Some(instructions) = extract_instruction_idents(item_fn) {
25                    context.instructions = instructions;
26                }
27            }
28            _ => (),
29        });
30        context
31    }
32}
33
34fn extract_ident<'a>(item_impl: &'a syn::ItemImpl, trait_name: &str) -> Option<&'a Ident> {
35    let trait_ = item_impl.trait_.as_ref()?;
36    let segment = trait_.1.segments.last()?;
37
38    if segment.ident != trait_name {
39        return None;
40    }
41
42    match *item_impl.self_ty {
43        Type::Path(ref type_path) => Some(&type_path.path.segments.last()?.ident),
44        _ => None,
45    }
46}
47
48fn extract_instruction_idents(item_fn: &ItemFn) -> Option<Vec<&Ident>> {
49    // Check if it's the process_instruction function
50    if item_fn.sig.ident != "process_instruction" {
51        return None;
52    }
53
54    // Find match expression in function body
55    let match_expr = item_fn.block.stmts.iter().find_map(|stmt| {
56        if let Stmt::Expr(Expr::Match(m), ..) = stmt {
57            Some(m)
58        } else {
59            None
60        }
61    })?;
62
63    // Extract instruction identifiers from match arms
64    let instructions = match_expr
65        .arms
66        .iter()
67        .filter_map(|arm| {
68            // Look for try expressions containing handle calls
69            let Expr::Try(try_expr) = arm.body.as_ref() else {
70                return None;
71            };
72
73            // Extract handle call
74            let Expr::Call(call) = try_expr.expr.as_ref() else {
75                return None;
76            };
77
78            // Verify it's a handle function
79            let Expr::Path(p) = call.func.as_ref() else {
80                return None;
81            };
82            if p.path.segments.last()?.ident != "handle" {
83                return None;
84            };
85
86            // Get instruction identifier from last argument
87            call.args.last().and_then(|arg| {
88                if let Expr::Path(p) = arg {
89                    p.path.get_ident()
90                } else {
91                    None
92                }
93            })
94        })
95        .collect();
96
97    Some(instructions)
98}