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