meow_meow_script/
block_effect_analyzer.rs1use 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}