Skip to main content

meow_meow_script/
block_effect_analyzer.rs

1use crate::ast::{BinOpKind, BlockStatement, Expression, Statement};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum EffectKind {
5    None,
6    Audio,
7    Visual,
8    Mixed,
9    Unknown,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct StatementEffectSummary {
14    pub effect_kind: EffectKind,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct BlockEffectAnalysis {
19    pub contains_audio_effects: bool,
20    pub contains_visual_effects: bool,
21    pub contains_unknown_effects: bool,
22    pub statements: Vec<StatementEffectSummary>,
23}
24
25pub struct BlockEffectAnalyzer;
26
27impl BlockEffectAnalyzer {
28    pub fn analyze_keyframe_block(body: &BlockStatement) -> BlockEffectAnalysis {
29        let statements: Vec<StatementEffectSummary> = body
30            .statements
31            .iter()
32            .map(|stmt| StatementEffectSummary {
33                effect_kind: classify_statement(stmt),
34            })
35            .collect();
36
37        BlockEffectAnalysis {
38            contains_audio_effects: statements
39                .iter()
40                .any(|s| matches!(s.effect_kind, EffectKind::Audio | EffectKind::Mixed)),
41            contains_visual_effects: statements
42                .iter()
43                .any(|s| matches!(s.effect_kind, EffectKind::Visual | EffectKind::Mixed)),
44            contains_unknown_effects: statements
45                .iter()
46                .any(|s| matches!(s.effect_kind, EffectKind::Unknown)),
47            statements,
48        }
49    }
50}
51
52fn classify_statement(stmt: &Statement) -> EffectKind {
53    match stmt {
54        Statement::Expression(expr) => classify_expr(expr),
55        Statement::Block(block) => summarize(block.statements.iter().map(classify_statement)),
56        Statement::If(if_stmt) => {
57            let mut effects = vec![summarize(
58                if_stmt
59                    .then_branch
60                    .statements
61                    .iter()
62                    .map(classify_statement),
63            )];
64            if let Some(else_branch) = &if_stmt.else_branch {
65                let effect = match else_branch {
66                    crate::ast::ElseBranch::Block(block) => {
67                        summarize(block.statements.iter().map(classify_statement))
68                    }
69                    crate::ast::ElseBranch::If(next_if) => {
70                        classify_statement(&Statement::If((**next_if).clone()))
71                    }
72                };
73                effects.push(effect);
74            }
75            summarize(effects)
76        }
77        Statement::ForIn { body, .. } | Statement::While { body, .. } => {
78            summarize(body.statements.iter().map(classify_statement))
79        }
80        Statement::Assignment(_)
81        | Statement::Reassign { .. }
82        | Statement::Return(_)
83        | Statement::Break
84        | Statement::Continue
85        | Statement::Import { .. } => EffectKind::None,
86    }
87}
88
89fn classify_expr(expr: &Expression) -> EffectKind {
90    match expr {
91        Expression::Call(call) => match call.callee.as_ref() {
92            Expression::BinaryOp {
93                op: BinOpKind::Dot,
94                lhs,
95                rhs,
96            } => {
97                if matches!(lhs.as_ref(), Expression::Identifier(id) if id.0 == "MusicNote")
98                    && matches!(rhs.as_ref(), Expression::Identifier(id) if matches!(id.0.as_str(), "a" | "b" | "c" | "d" | "e" | "f" | "g"))
99                {
100                    EffectKind::Audio
101                } else {
102                    EffectKind::Unknown
103                }
104            }
105            _ => EffectKind::Unknown,
106        },
107        _ => EffectKind::None,
108    }
109}
110
111fn summarize(effects: impl IntoIterator<Item = EffectKind>) -> EffectKind {
112    let mut saw_audio = false;
113    let mut saw_visual = false;
114    let mut saw_unknown = false;
115
116    for effect in effects {
117        match effect {
118            EffectKind::None => {}
119            EffectKind::Audio => saw_audio = true,
120            EffectKind::Visual => saw_visual = true,
121            EffectKind::Mixed => {
122                saw_audio = true;
123                saw_visual = true;
124            }
125            EffectKind::Unknown => saw_unknown = true,
126        }
127    }
128
129    if saw_unknown {
130        EffectKind::Unknown
131    } else if saw_audio && saw_visual {
132        EffectKind::Mixed
133    } else if saw_audio {
134        EffectKind::Audio
135    } else if saw_visual {
136        EffectKind::Visual
137    } else {
138        EffectKind::None
139    }
140}