darklua_core/rules/
remove_continue.rs

1use std::fmt::Debug;
2
3use crate::nodes::{
4    AssignStatement, Block, Expression, IfStatement, LastStatement, LocalAssignStatement,
5    RepeatStatement, Statement, TypedIdentifier, UnaryExpression, UnaryOperator, Variable,
6};
7use crate::process::{DefaultVisitor, NodeProcessor, NodeVisitor};
8use crate::rules::{Context, RuleConfiguration, RuleConfigurationError, RuleProperties};
9
10use super::runtime_identifier::RuntimeIdentifierBuilder;
11use super::{Rule, RuleProcessResult};
12
13#[derive(Default)]
14struct Processor {
15    break_identifier: String,
16    continue_identifier: String,
17}
18
19fn count_continue_break(block: &Block) -> (usize, usize) {
20    let (mut continue_count, mut break_count) = if let Some(last_stmt) = block.get_last_statement()
21    {
22        (
23            if let LastStatement::Continue(_) = last_stmt {
24                1
25            } else {
26                0
27            },
28            if let LastStatement::Break(_) = last_stmt {
29                1
30            } else {
31                0
32            },
33        )
34    } else {
35        (0, 0)
36    };
37    for stmt in block.iter_statements() {
38        match stmt {
39            Statement::If(if_stmt) => {
40                for branch in if_stmt.iter_branches() {
41                    let (c, b) = count_continue_break(branch.get_block());
42                    continue_count += c;
43                    break_count += b;
44                }
45            }
46            Statement::Do(do_stmt) => {
47                let (c, b) = count_continue_break(do_stmt.get_block());
48                continue_count += c;
49                break_count += b;
50            }
51            _ => {}
52        }
53    }
54    (continue_count, break_count)
55}
56
57fn continues_to_breaks(block: &mut Block) {
58    if let Some(last_stmt) = block.mutate_last_statement() {
59        if matches!(last_stmt, LastStatement::Continue(_)) {
60            *last_stmt = LastStatement::new_break();
61        }
62    }
63    for stmt in block.iter_mut_statements() {
64        match stmt {
65            Statement::If(if_stmt) => {
66                for branch in if_stmt.mutate_branches().iter_mut() {
67                    continues_to_breaks(branch.mutate_block());
68                }
69            }
70            Statement::Do(do_stmt) => {
71                continues_to_breaks(do_stmt.mutate_block());
72            }
73            _ => {}
74        }
75    }
76}
77
78impl Processor {
79    fn process(&self, block: &mut Block) {
80        let (continue_count, break_count) = count_continue_break(block);
81
82        if continue_count > 0 {
83            let (mut stmts, break_variable_handler) = if break_count > 0 {
84                let with_continue_statement = continue_count < break_count;
85                let break_block = Block::new(vec![], Some(LastStatement::new_break()));
86                let (break_variable_handler, var) = if with_continue_statement {
87                    let var = TypedIdentifier::new(self.continue_identifier.as_str());
88                    (
89                        IfStatement::create(
90                            UnaryExpression::new(UnaryOperator::Not, var.get_identifier().clone()),
91                            break_block,
92                        ),
93                        var,
94                    )
95                } else {
96                    let var = TypedIdentifier::new(self.break_identifier.as_str());
97                    (
98                        IfStatement::create(var.get_identifier().clone(), break_block),
99                        var,
100                    )
101                };
102
103                self.continues_with_breaks_to_breaks(block, with_continue_statement);
104
105                let initial_value = Expression::False(None);
106                let local_assign_stmt = LocalAssignStatement::new(vec![var], vec![initial_value]);
107
108                (vec![local_assign_stmt.into()], Some(break_variable_handler))
109            } else {
110                continues_to_breaks(block);
111                (Vec::new(), None)
112            };
113            let repeat_stmt = RepeatStatement::new(block.clone(), true);
114            stmts.push(repeat_stmt.into());
115            if let Some(break_variable_handler) = break_variable_handler {
116                stmts.push(break_variable_handler.into());
117            }
118            *block = Block::new(stmts, None);
119        }
120    }
121
122    fn continues_with_breaks_to_breaks(&self, block: &mut Block, with_continue_statement: bool) {
123        if let Some(last_stmt) = block.mutate_last_statement() {
124            let (continue_statement, break_statement) = if with_continue_statement {
125                let var = Variable::new(self.continue_identifier.as_str());
126                (Some(AssignStatement::from_variable(var, true)), None)
127            } else {
128                let var = Variable::new(self.break_identifier.as_str());
129                (None, Some(AssignStatement::from_variable(var, true)))
130            };
131            match last_stmt {
132                LastStatement::Continue(_) => {
133                    if let Some(stmt) = continue_statement {
134                        block.push_statement(stmt);
135                    }
136                    block.set_last_statement(LastStatement::new_break());
137                }
138                LastStatement::Break(_) => {
139                    if let Some(stmt) = break_statement {
140                        block.push_statement(stmt);
141                    }
142                    block.set_last_statement(LastStatement::new_break());
143                }
144                _ => {}
145            }
146        }
147        for stmt in block.iter_mut_statements() {
148            match stmt {
149                Statement::If(if_stmt) => {
150                    for branch in if_stmt.mutate_branches().iter_mut() {
151                        self.continues_with_breaks_to_breaks(
152                            branch.mutate_block(),
153                            with_continue_statement,
154                        );
155                    }
156                }
157                Statement::Do(do_stmt) => {
158                    self.continues_with_breaks_to_breaks(
159                        do_stmt.mutate_block(),
160                        with_continue_statement,
161                    );
162                }
163                _ => {}
164            }
165        }
166    }
167}
168
169impl NodeProcessor for Processor {
170    fn process_statement(&mut self, statement: &mut Statement) {
171        match statement {
172            Statement::NumericFor(numeric_for) => self.process(numeric_for.mutate_block()),
173            Statement::GenericFor(generic_for) => self.process(generic_for.mutate_block()),
174            Statement::Repeat(repeat_stmt) => self.process(repeat_stmt.mutate_block()),
175            Statement::While(while_stmt) => self.process(while_stmt.mutate_block()),
176            _ => (),
177        }
178    }
179}
180
181pub const REMOVE_CONTINUE_RULE_NAME: &str = "remove_continue";
182
183/// A rule that removes continue statements and convert into breaks.
184#[derive(Debug, PartialEq, Eq)]
185pub struct RemoveContinue {
186    runtime_identifier_format: String,
187}
188
189impl Default for RemoveContinue {
190    fn default() -> Self {
191        Self {
192            runtime_identifier_format: "__DARKLUA_REMOVE_CONTINUE_{name}{hash}".to_string(),
193        }
194    }
195}
196
197impl Rule for RemoveContinue {
198    fn process(&self, block: &mut Block, _: &Context) -> RuleProcessResult {
199        let var_builder = RuntimeIdentifierBuilder::new(
200            self.runtime_identifier_format.as_str(),
201            format!("{block:?}").as_bytes(),
202            None,
203        )?;
204        let mut processor = Processor {
205            break_identifier: var_builder.build("break")?,
206            continue_identifier: var_builder.build("continue")?,
207        };
208        DefaultVisitor::visit_block(block, &mut processor);
209        Ok(())
210    }
211}
212
213impl RuleConfiguration for RemoveContinue {
214    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
215        for (key, value) in properties {
216            match key.as_str() {
217                "runtime_identifier_format" => {
218                    self.runtime_identifier_format = value.expect_string(&key)?;
219                }
220                _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
221            }
222        }
223
224        Ok(())
225    }
226
227    fn get_name(&self) -> &'static str {
228        REMOVE_CONTINUE_RULE_NAME
229    }
230
231    fn serialize_to_properties(&self) -> RuleProperties {
232        RuleProperties::new()
233    }
234}
235
236#[cfg(test)]
237mod test {
238    use super::*;
239    use crate::rules::Rule;
240
241    use insta::assert_json_snapshot;
242
243    fn new_rule() -> RemoveContinue {
244        RemoveContinue::default()
245    }
246
247    #[test]
248    fn serialize_default_rule() {
249        let rule: Box<dyn Rule> = Box::new(new_rule());
250
251        assert_json_snapshot!("default_remove_continue", rule);
252    }
253
254    #[test]
255    fn configure_with_extra_field_error() {
256        let result = json5::from_str::<Box<dyn Rule>>(
257            r#"{
258            rule: 'remove_continue',
259            runtime_identifier_format: '{name}',
260            prop: "something",
261        }"#,
262        );
263        pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
264    }
265}