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