1use std::fmt::Debug;
2
3use crate::nodes::{
4 AssignStatement, Block, Expression, IfStatement, LastStatement, LocalAssignStatement,
5 RepeatStatement, Statement, TypedIdentifier, UnaryExpression, UnaryOperator, Variable,
6};
7use crate::process::{DefaultVisitor, NodeProcessor, NodeVisitor};
8use crate::rules::{Context, RuleConfiguration, RuleConfigurationError, RuleProperties};
9
10use super::runtime_identifier::RuntimeIdentifierBuilder;
11use super::{Rule, RuleProcessResult};
12
13#[derive(Default)]
14struct Processor {
15 break_identifier: String,
16 continue_identifier: String,
17}
18
19fn count_continue_break(block: &Block) -> (usize, usize) {
20 let (mut continue_count, mut break_count) = if let Some(last_stmt) = block.get_last_statement()
21 {
22 (
23 if let LastStatement::Continue(_) = last_stmt {
24 1
25 } else {
26 0
27 },
28 if let LastStatement::Break(_) = last_stmt {
29 1
30 } else {
31 0
32 },
33 )
34 } else {
35 (0, 0)
36 };
37 for stmt in block.iter_statements() {
38 match stmt {
39 Statement::If(if_stmt) => {
40 for branch in if_stmt.iter_branches() {
41 let (c, b) = count_continue_break(branch.get_block());
42 continue_count += c;
43 break_count += b;
44 }
45 }
46 Statement::Do(do_stmt) => {
47 let (c, b) = count_continue_break(do_stmt.get_block());
48 continue_count += c;
49 break_count += b;
50 }
51 _ => {}
52 }
53 }
54 (continue_count, break_count)
55}
56
57fn continues_to_breaks(block: &mut Block) {
58 if let Some(last_stmt) = block.mutate_last_statement() {
59 if matches!(last_stmt, LastStatement::Continue(_)) {
60 *last_stmt = LastStatement::new_break();
61 }
62 }
63 for stmt in block.iter_mut_statements() {
64 match stmt {
65 Statement::If(if_stmt) => {
66 for branch in if_stmt.mutate_branches().iter_mut() {
67 continues_to_breaks(branch.mutate_block());
68 }
69 }
70 Statement::Do(do_stmt) => {
71 continues_to_breaks(do_stmt.mutate_block());
72 }
73 _ => {}
74 }
75 }
76}
77
78impl Processor {
79 fn process(&self, block: &mut Block) {
80 let (continue_count, break_count) = count_continue_break(block);
81
82 if continue_count > 0 {
83 let (mut stmts, break_variable_handler) = if break_count > 0 {
84 let with_continue_statement = continue_count < break_count;
85 let break_block = Block::new(vec![], Some(LastStatement::new_break()));
86 let (break_variable_handler, var) = if with_continue_statement {
87 let var = TypedIdentifier::new(self.continue_identifier.as_str());
88 (
89 IfStatement::create(
90 UnaryExpression::new(UnaryOperator::Not, var.get_identifier().clone()),
91 break_block,
92 ),
93 var,
94 )
95 } else {
96 let var = TypedIdentifier::new(self.break_identifier.as_str());
97 (
98 IfStatement::create(var.get_identifier().clone(), break_block),
99 var,
100 )
101 };
102
103 self.continues_with_breaks_to_breaks(block, with_continue_statement);
104
105 let initial_value = Expression::False(None);
106 let local_assign_stmt = LocalAssignStatement::new(vec![var], vec![initial_value]);
107
108 (vec![local_assign_stmt.into()], Some(break_variable_handler))
109 } else {
110 continues_to_breaks(block);
111 (Vec::new(), None)
112 };
113 let repeat_stmt = RepeatStatement::new(block.clone(), true);
114 stmts.push(repeat_stmt.into());
115 if let Some(break_variable_handler) = break_variable_handler {
116 stmts.push(break_variable_handler.into());
117 }
118 *block = Block::new(stmts, None);
119 }
120 }
121
122 fn continues_with_breaks_to_breaks(&self, block: &mut Block, with_continue_statement: bool) {
123 if let Some(last_stmt) = block.mutate_last_statement() {
124 let (continue_statement, break_statement) = if with_continue_statement {
125 let var = Variable::new(self.continue_identifier.as_str());
126 (Some(AssignStatement::from_variable(var, true)), None)
127 } else {
128 let var = Variable::new(self.break_identifier.as_str());
129 (None, Some(AssignStatement::from_variable(var, true)))
130 };
131 match last_stmt {
132 LastStatement::Continue(_) => {
133 if let Some(stmt) = continue_statement {
134 block.push_statement(stmt);
135 }
136 block.set_last_statement(LastStatement::new_break());
137 }
138 LastStatement::Break(_) => {
139 if let Some(stmt) = break_statement {
140 block.push_statement(stmt);
141 }
142 block.set_last_statement(LastStatement::new_break());
143 }
144 _ => {}
145 }
146 }
147 for stmt in block.iter_mut_statements() {
148 match stmt {
149 Statement::If(if_stmt) => {
150 for branch in if_stmt.mutate_branches().iter_mut() {
151 self.continues_with_breaks_to_breaks(
152 branch.mutate_block(),
153 with_continue_statement,
154 );
155 }
156 }
157 Statement::Do(do_stmt) => {
158 self.continues_with_breaks_to_breaks(
159 do_stmt.mutate_block(),
160 with_continue_statement,
161 );
162 }
163 _ => {}
164 }
165 }
166 }
167}
168
169impl NodeProcessor for Processor {
170 fn process_statement(&mut self, statement: &mut Statement) {
171 match statement {
172 Statement::NumericFor(numeric_for) => self.process(numeric_for.mutate_block()),
173 Statement::GenericFor(generic_for) => self.process(generic_for.mutate_block()),
174 Statement::Repeat(repeat_stmt) => self.process(repeat_stmt.mutate_block()),
175 Statement::While(while_stmt) => self.process(while_stmt.mutate_block()),
176 _ => (),
177 }
178 }
179}
180
181pub const REMOVE_CONTINUE_RULE_NAME: &str = "remove_continue";
182
183#[derive(Debug, PartialEq, Eq)]
185pub struct RemoveContinue {
186 runtime_identifier_format: String,
187}
188
189impl Default for RemoveContinue {
190 fn default() -> Self {
191 Self {
192 runtime_identifier_format: "__DARKLUA_REMOVE_CONTINUE_{name}{hash}".to_string(),
193 }
194 }
195}
196
197impl Rule for RemoveContinue {
198 fn process(&self, block: &mut Block, _: &Context) -> RuleProcessResult {
199 let var_builder = RuntimeIdentifierBuilder::new(
200 self.runtime_identifier_format.as_str(),
201 format!("{block:?}").as_bytes(),
202 None,
203 )?;
204 let mut processor = Processor {
205 break_identifier: var_builder.build("break")?,
206 continue_identifier: var_builder.build("continue")?,
207 };
208 DefaultVisitor::visit_block(block, &mut processor);
209 Ok(())
210 }
211}
212
213impl RuleConfiguration for RemoveContinue {
214 fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
215 for (key, value) in properties {
216 match key.as_str() {
217 "runtime_identifier_format" => {
218 self.runtime_identifier_format = value.expect_string(&key)?;
219 }
220 _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
221 }
222 }
223
224 Ok(())
225 }
226
227 fn get_name(&self) -> &'static str {
228 REMOVE_CONTINUE_RULE_NAME
229 }
230
231 fn serialize_to_properties(&self) -> RuleProperties {
232 RuleProperties::new()
233 }
234}
235
236#[cfg(test)]
237mod test {
238 use super::*;
239 use crate::rules::Rule;
240
241 use insta::assert_json_snapshot;
242
243 fn new_rule() -> RemoveContinue {
244 RemoveContinue::default()
245 }
246
247 #[test]
248 fn serialize_default_rule() {
249 let rule: Box<dyn Rule> = Box::new(new_rule());
250
251 assert_json_snapshot!("default_remove_continue", rule);
252 }
253
254 #[test]
255 fn configure_with_extra_field_error() {
256 let result = json5::from_str::<Box<dyn Rule>>(
257 r#"{
258 rule: 'remove_continue',
259 runtime_identifier_format: '{name}',
260 prop: "something",
261 }"#,
262 );
263 pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
264 }
265}