Skip to main content

darklua_core/rules/
remove_continue.rs

1use std::fmt::Debug;
2use std::mem;
3
4use crate::nodes::{
5    AssignStatement, Block, GenericForStatement, Identifier, IfStatement, LastStatement,
6    NumericForStatement, RepeatStatement, UnaryExpression, UnaryOperator, VariableAssignment,
7    WhileStatement,
8};
9use crate::process::{DefaultPostVisitor, NodePostProcessor, NodePostVisitor, NodeProcessor};
10use crate::rules::{
11    Context, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProperties,
12};
13
14use super::{verify_no_rule_properties, FlawlessRule};
15
16#[derive(Default)]
17struct Processor {
18    loop_stack: Vec<Option<LoopData>>,
19    loop_identifier_count: u16,
20}
21
22struct LoopData {
23    has_continue_statement: bool,
24    loop_break_id: u16,
25}
26
27impl LoopData {
28    fn new(loop_break_id: u16) -> Self {
29        Self {
30            has_continue_statement: false,
31            loop_break_id,
32        }
33    }
34
35    fn get_identifier(&self) -> Identifier {
36        Identifier::new(format!("__DARKLUA_CONTINUE_{}", self.loop_break_id))
37    }
38}
39
40impl Processor {
41    fn push_loop(&mut self) {
42        self.loop_identifier_count += 1;
43        self.loop_stack
44            .push(Some(LoopData::new(self.loop_identifier_count)));
45    }
46
47    fn push_no_loop(&mut self) {
48        self.loop_stack.push(None);
49    }
50
51    fn wrap_loop_block_if_needed(&mut self, block: &mut Block) {
52        if let Some(loop_data) = self.loop_stack.pop().flatten() {
53            if !loop_data.has_continue_statement {
54                return;
55            }
56            let mut current_loop_block = mem::take(block);
57
58            if current_loop_block.get_last_statement().is_none() {
59                current_loop_block.push_statement(AssignStatement::from_variable(
60                    loop_data.get_identifier(),
61                    true,
62                ));
63            }
64
65            let new_block = Block::default()
66                .with_statement(
67                    VariableAssignment::from_variable(loop_data.get_identifier()).with_value(false),
68                )
69                .with_statement(RepeatStatement::new(current_loop_block, true))
70                .with_statement(IfStatement::create(
71                    UnaryExpression::new(UnaryOperator::Not, loop_data.get_identifier()),
72                    LastStatement::Break(None),
73                ));
74
75            *block = new_block;
76        }
77    }
78}
79
80impl NodeProcessor for Processor {
81    fn process_generic_for_statement(&mut self, _: &mut GenericForStatement) {
82        self.push_loop();
83    }
84
85    fn process_numeric_for_statement(&mut self, _: &mut NumericForStatement) {
86        self.push_loop();
87    }
88
89    fn process_repeat_statement(&mut self, _: &mut RepeatStatement) {
90        self.push_loop();
91    }
92
93    fn process_while_statement(&mut self, _: &mut WhileStatement) {
94        self.push_loop();
95    }
96
97    fn process_function_statement(&mut self, _: &mut crate::nodes::FunctionStatement) {
98        self.push_no_loop();
99    }
100
101    fn process_function_expression(&mut self, _: &mut crate::nodes::FunctionExpression) {
102        self.push_no_loop();
103    }
104
105    fn process_block(&mut self, block: &mut Block) {
106        let new_statement =
107            block
108                .mutate_last_statement()
109                .and_then(|last_statement| match last_statement {
110                    LastStatement::Continue(continue_token) => {
111                        if let Some(Some(loop_data)) = self.loop_stack.last_mut() {
112                            if !loop_data.has_continue_statement {
113                                loop_data.has_continue_statement = true;
114                            }
115
116                            *last_statement = LastStatement::Break(continue_token.take().map(
117                                |mut continue_token| {
118                                    continue_token.replace_with_content("break");
119                                    continue_token
120                                },
121                            ));
122
123                            Some(AssignStatement::from_variable(
124                                loop_data.get_identifier(),
125                                true,
126                            ))
127                        } else {
128                            None
129                        }
130                    }
131                    _ => None,
132                });
133
134        if let Some(statement) = new_statement {
135            block.push_statement(statement);
136        }
137    }
138}
139
140impl NodePostProcessor for Processor {
141    fn process_after_generic_for_statement(&mut self, statement: &mut GenericForStatement) {
142        self.wrap_loop_block_if_needed(statement.mutate_block());
143    }
144
145    fn process_after_numeric_for_statement(&mut self, statement: &mut NumericForStatement) {
146        self.wrap_loop_block_if_needed(statement.mutate_block());
147    }
148
149    fn process_after_repeat_statement(&mut self, statement: &mut RepeatStatement) {
150        self.wrap_loop_block_if_needed(statement.mutate_block());
151    }
152
153    fn process_after_while_statement(&mut self, statement: &mut WhileStatement) {
154        self.wrap_loop_block_if_needed(statement.mutate_block());
155    }
156
157    fn process_after_function_statement(&mut self, _: &mut crate::nodes::FunctionStatement) {
158        self.loop_stack.pop();
159    }
160
161    fn process_after_function_expression(&mut self, _: &mut crate::nodes::FunctionExpression) {
162        self.loop_stack.pop();
163    }
164}
165
166pub const REMOVE_CONTINUE_RULE_NAME: &str = "remove_continue";
167
168/// A rule that removes continue statements and converts them into break statements.
169#[derive(Debug, Default, PartialEq, Eq)]
170pub struct RemoveContinue {
171    metadata: RuleMetadata,
172}
173
174impl FlawlessRule for RemoveContinue {
175    fn flawless_process(&self, block: &mut Block, _: &Context) {
176        let mut processor = Processor::default();
177        DefaultPostVisitor::visit_block(block, &mut processor);
178    }
179}
180
181impl RuleConfiguration for RemoveContinue {
182    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
183        verify_no_rule_properties(&properties)?;
184
185        Ok(())
186    }
187
188    fn get_name(&self) -> &'static str {
189        REMOVE_CONTINUE_RULE_NAME
190    }
191
192    fn serialize_to_properties(&self) -> RuleProperties {
193        RuleProperties::new()
194    }
195
196    fn set_metadata(&mut self, metadata: RuleMetadata) {
197        self.metadata = metadata;
198    }
199
200    fn metadata(&self) -> &RuleMetadata {
201        &self.metadata
202    }
203}
204
205#[cfg(test)]
206mod test {
207    use super::*;
208    use crate::rules::Rule;
209
210    use insta::assert_json_snapshot;
211
212    fn new_rule() -> RemoveContinue {
213        RemoveContinue::default()
214    }
215
216    #[test]
217    fn serialize_default_rule() {
218        let rule: Box<dyn Rule> = Box::new(new_rule());
219
220        assert_json_snapshot!(rule, @r###""remove_continue""###);
221    }
222
223    #[test]
224    fn configure_with_extra_field_error() {
225        let result = json5::from_str::<Box<dyn Rule>>(
226            r#"{
227            rule: 'remove_continue',
228            prop: "something",
229        }"#,
230        );
231        insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
232    }
233}