darklua_core/rules/
remove_continue.rs1use 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#[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}