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    LocalAssignStatement, NumericForStatement, RepeatStatement, UnaryExpression, UnaryOperator,
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                    LocalAssignStatement::from_variable(loop_data.get_identifier())
68                        .with_value(false),
69                )
70                .with_statement(RepeatStatement::new(current_loop_block, true))
71                .with_statement(IfStatement::create(
72                    UnaryExpression::new(UnaryOperator::Not, loop_data.get_identifier()),
73                    LastStatement::Break(None),
74                ));
75
76            *block = new_block;
77        }
78    }
79}
80
81impl NodeProcessor for Processor {
82    fn process_generic_for_statement(&mut self, _: &mut GenericForStatement) {
83        self.push_loop();
84    }
85
86    fn process_numeric_for_statement(&mut self, _: &mut NumericForStatement) {
87        self.push_loop();
88    }
89
90    fn process_repeat_statement(&mut self, _: &mut RepeatStatement) {
91        self.push_loop();
92    }
93
94    fn process_while_statement(&mut self, _: &mut WhileStatement) {
95        self.push_loop();
96    }
97
98    fn process_function_statement(&mut self, _: &mut crate::nodes::FunctionStatement) {
99        self.push_no_loop();
100    }
101
102    fn process_function_expression(&mut self, _: &mut crate::nodes::FunctionExpression) {
103        self.push_no_loop();
104    }
105
106    fn process_block(&mut self, block: &mut Block) {
107        let new_statement =
108            block
109                .mutate_last_statement()
110                .and_then(|last_statement| match last_statement {
111                    LastStatement::Continue(continue_token) => {
112                        if let Some(Some(loop_data)) = self.loop_stack.last_mut() {
113                            if !loop_data.has_continue_statement {
114                                loop_data.has_continue_statement = true;
115                            }
116
117                            *last_statement = LastStatement::Break(continue_token.take().map(
118                                |mut continue_token| {
119                                    continue_token.replace_with_content("break");
120                                    continue_token
121                                },
122                            ));
123
124                            Some(AssignStatement::from_variable(
125                                loop_data.get_identifier(),
126                                true,
127                            ))
128                        } else {
129                            None
130                        }
131                    }
132                    _ => None,
133                });
134
135        if let Some(statement) = new_statement {
136            block.push_statement(statement);
137        }
138    }
139}
140
141impl NodePostProcessor for Processor {
142    fn process_after_generic_for_statement(&mut self, statement: &mut GenericForStatement) {
143        self.wrap_loop_block_if_needed(statement.mutate_block());
144    }
145
146    fn process_after_numeric_for_statement(&mut self, statement: &mut NumericForStatement) {
147        self.wrap_loop_block_if_needed(statement.mutate_block());
148    }
149
150    fn process_after_repeat_statement(&mut self, statement: &mut RepeatStatement) {
151        self.wrap_loop_block_if_needed(statement.mutate_block());
152    }
153
154    fn process_after_while_statement(&mut self, statement: &mut WhileStatement) {
155        self.wrap_loop_block_if_needed(statement.mutate_block());
156    }
157
158    fn process_after_function_statement(&mut self, _: &mut crate::nodes::FunctionStatement) {
159        self.loop_stack.pop();
160    }
161
162    fn process_after_function_expression(&mut self, _: &mut crate::nodes::FunctionExpression) {
163        self.loop_stack.pop();
164    }
165}
166
167pub const REMOVE_CONTINUE_RULE_NAME: &str = "remove_continue";
168
169/// A rule that removes continue statements and converts them into break statements.
170#[derive(Debug, Default, PartialEq, Eq)]
171pub struct RemoveContinue {
172    metadata: RuleMetadata,
173}
174
175impl FlawlessRule for RemoveContinue {
176    fn flawless_process(&self, block: &mut Block, _: &Context) {
177        let mut processor = Processor::default();
178        DefaultPostVisitor::visit_block(block, &mut processor);
179    }
180}
181
182impl RuleConfiguration for RemoveContinue {
183    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
184        verify_no_rule_properties(&properties)?;
185
186        Ok(())
187    }
188
189    fn get_name(&self) -> &'static str {
190        REMOVE_CONTINUE_RULE_NAME
191    }
192
193    fn serialize_to_properties(&self) -> RuleProperties {
194        RuleProperties::new()
195    }
196
197    fn set_metadata(&mut self, metadata: RuleMetadata) {
198        self.metadata = metadata;
199    }
200
201    fn metadata(&self) -> &RuleMetadata {
202        &self.metadata
203    }
204}
205
206#[cfg(test)]
207mod test {
208    use super::*;
209    use crate::rules::Rule;
210
211    use insta::assert_json_snapshot;
212
213    fn new_rule() -> RemoveContinue {
214        RemoveContinue::default()
215    }
216
217    #[test]
218    fn serialize_default_rule() {
219        let rule: Box<dyn Rule> = Box::new(new_rule());
220
221        assert_json_snapshot!(rule, @r###""remove_continue""###);
222    }
223
224    #[test]
225    fn configure_with_extra_field_error() {
226        let result = json5::from_str::<Box<dyn Rule>>(
227            r#"{
228            rule: 'remove_continue',
229            prop: "something",
230        }"#,
231        );
232        insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
233    }
234}