1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
use crate::ast::*;
use crate::transpiler::{GeneratedCommand, GeneratedCommandKind, Transpiler};
use std::collections::HashMap;
impl Transpiler {
pub(in crate::transpiler) fn process_for(&mut self, for_loop: &ForLoop) -> Result<(), String> {
let mut for_commands = Vec::new();
// Handle range-based for loops
if let Expression::Call(func, args) = &for_loop.iter {
if let Expression::Identifier(name) = &**func {
if name == "range" && args.len() == 1 {
// Check if the argument is a literal number
let count = match &args[0] {
Expression::Number(n) => *n as i32,
Expression::Identifier(var_name) => {
return Err(format!(
"For loops with range() only accept literal numbers, not variables.\n\
\n\
Got: for {} in range({})\n\
\n\
Solution: Use a literal number instead:\n\
- for {} in range(10): # Correct\n\
\n\
Note: Dynamic loop ranges are not supported because Minecraft data packs\n\
require the loop count to be known at compile time.",
for_loop.target, var_name, for_loop.target
));
}
_ => {
return Err(format!(
"For loops with range() only accept literal numbers.\n\
Got: range({:?})\n\
\n\
Solution: Use a literal number:\n\
- for {} in range(10): # Correct",
args[0], for_loop.target
));
}
};
// Determine step value (default 1, or from for_loop.step)
let step = if let Some(ref step_expr) = for_loop.step {
match step_expr {
Expression::Number(s) => *s as i32,
_ => return Err("Step must be a constant number".to_string()),
}
} else {
1
};
if step == 0 {
return Err("Step cannot be zero".to_string());
}
// Generate a helper function for the loop
let loop_func_name = format!("loop_temp_{}", self.temp_counter);
self.temp_counter += 1;
// Track loop_counter objective
self.data_pack.track_objective("loop_counter");
// Save previous state of loop variable (if it exists)
let saved_var_objective =
self.variable_objectives.get(&for_loop.target).cloned();
let was_scoreboard_var = self.scoreboard_variables.contains(&for_loop.target);
// Track this variable's objective AND mark as scoreboard variable
self.variable_objectives
.insert(for_loop.target.clone(), "loop_counter".to_string());
self.scoreboard_variables.insert(for_loop.target.clone());
// Initialize loop counter based on step direction
let start_value = if step > 0 {
0
} else {
// For negative step, always start at count - 1
// This ensures we iterate from (n-1) down to 0 regardless of step magnitude
count - 1
};
for_commands.push(format!(
"scoreboard players set {} loop_counter {}",
for_loop.target, start_value
));
// Create a macro function for the loop body
// This allows loop variables to be used in commands like /say
let body_func_name = format!("loop_body_{}", self.temp_counter - 1);
// Process loop body as a macro function with loop variable as parameter
let saved_context = self.current_context.clone();
let saved_variable_types = self.variable_types.clone();
// Set up function context with loop variable as a parameter
self.current_context = crate::transpiler::FunctionContext::with_params(
saved_context.with_extra_param(for_loop.target.clone()),
);
let capture = self.capture_statements(&for_loop.body)?;
self.add_captured_function(body_func_name.clone(), capture);
// Track this function as having parameters
self.function_params
.insert(body_func_name.clone(), vec![for_loop.target.clone()]);
self.current_context = saved_context;
self.variable_types = saved_variable_types;
// Restore previous state of loop variable
if let Some(obj) = saved_var_objective {
self.variable_objectives
.insert(for_loop.target.clone(), obj);
} else {
self.variable_objectives.remove(&for_loop.target);
}
if !was_scoreboard_var {
self.scoreboard_variables.remove(&for_loop.target);
}
// Create loop control function
let mut loop_commands = vec![];
// Condition depends on step direction
let condition = if step > 0 {
// For positive step: continue while i < count
format!("..{}", count - 1)
} else {
// For negative step: continue while i >= 0
"0..".to_string()
};
// Create a wrapper function that stores variable and calls body
// Use a separate counter increment to ensure unique wrapper names in nested loops
let wrapper_id = self.temp_counter;
self.temp_counter += 1;
let wrapper_func_name = format!("loop_wrapper_{}", wrapper_id);
let mut wrapper_commands = vec![];
let mut wrapper_metadata = HashMap::new();
// Store loop variable value into storage for macro function
wrapper_commands.push(format!(
"execute store result storage {}:global args.{} int 1 run scoreboard players get {} loop_counter",
self.data_pack.namespace, for_loop.target, for_loop.target
));
wrapper_metadata.insert(
0,
GeneratedCommand::new(
wrapper_commands[0].clone(),
self.current_statement_source.clone(),
GeneratedCommandKind::ControlFlow,
),
);
// Call the macro body function with the loop variable
wrapper_commands.push(format!(
"function {}:{} with storage {}:global args",
self.data_pack.namespace, body_func_name, self.data_pack.namespace
));
wrapper_metadata.insert(
1,
GeneratedCommand::new(
wrapper_commands[1].clone(),
self.current_statement_source.clone(),
GeneratedCommandKind::ControlFlow,
),
);
self.data_pack.add_function_with_metadata(
wrapper_func_name.clone(),
wrapper_commands,
wrapper_metadata,
);
// Check condition BEFORE executing body (to prevent zero-length loops from executing)
loop_commands.push(format!(
"execute if score {} loop_counter matches {} run function {}:{}",
for_loop.target, condition, self.data_pack.namespace, wrapper_func_name
));
// THEN add increment/decrement
if step > 0 {
loop_commands.push(format!(
"scoreboard players add {} loop_counter {}",
for_loop.target, step
));
} else {
// Use 'remove' for negative step (Java Edition compatibility)
loop_commands.push(format!(
"scoreboard players remove {} loop_counter {}",
for_loop.target,
step.abs()
));
}
// Recursive call (check condition again after increment)
loop_commands.push(format!(
"execute if score {} loop_counter matches {} run function {}:{}",
for_loop.target, condition, self.data_pack.namespace, loop_func_name
));
// Add the loop function to the data pack
let loop_metadata = Self::metadata_for_commands(
&loop_commands,
self.current_statement_source.clone(),
);
self.data_pack.add_function_with_metadata(
loop_func_name.clone(),
loop_commands,
loop_metadata,
);
// Start the loop in the main function
for_commands.push(format!(
"function {}:{}",
self.data_pack.namespace, loop_func_name
));
} else {
return Err(format!(
"For loops with range() must have exactly one argument.\n\
Got: range() with {} arguments\n\
\n\
Solution: Use range(N) where N is a literal number:\n\
- for {} in range(10):",
args.len(),
for_loop.target
));
}
} else {
return Err(format!(
"For loops only support range() iterator, not {}.\n\
\n\
Solution: Use range(N) where N is a literal number:\n\
- for {} in range(10):",
if let Expression::Identifier(n) = &**func {
n
} else {
"unknown"
},
for_loop.target
));
}
} else {
// Unsupported iterator type - provide clear error message
return Err(format!(
"For loops only support range() iterator.\n\
Syntax: for {} in range(N):\n\
\n\
Examples:\n\
- for i in range(10): # Loop 10 times (0..9)\n\
- for i in range(10) by 2: # Count by 2s\n\
- for i in range(10) by -1: # Count backwards\n\
\n\
Iterating over lists/arrays is not yet supported.",
for_loop.target
));
}
if let Some(ref mut commands) = self.current_function {
commands.extend(for_commands);
}
Ok(())
}
pub(in crate::transpiler) fn process_while(
&mut self,
while_loop: &WhileLoop,
) -> Result<(), String> {
let mut while_commands = Vec::new();
// WARNING: While loops execute all iterations in a single tick
// This can cause server lag with large iteration counts (>100)
// Future improvement: Add schedule command support for tick-based iteration
// Check for obvious infinite loops
if let Expression::Boolean(true) = &while_loop.condition {
eprintln!(
"⚠️ Warning: Infinite loop detected (while True). \n\
This will run forever and freeze Minecraft!\n\
Consider using a condition that can become false."
);
}
// Generate a recursive function for the while loop
let loop_func_name = format!("while_temp_{}", self.temp_counter);
self.temp_counter += 1;
// Capture condition setup so complex expressions and OR lowering are
// rerun on every recursive iteration instead of once before the loop.
let (condition_cmd, condition_capture) =
self.capture_commands_with_result(|transpiler| {
let processed_condition = transpiler.preprocess_condition(&while_loop.condition)?;
let translated = transpiler.translate_condition(&processed_condition)?;
transpiler.normalize_if_condition(translated)
})?;
let condition_execute_args = Self::condition_execute_args(&condition_cmd);
// IMPORTANT: We need to wrap the body in a conditional function call
// to prevent bugs where body statements modify condition variables.
// The condition should be evaluated ONCE per iteration, not per statement.
// Create inner body function that executes unconditionally
let body_func_name = format!("while_body_{}", self.temp_counter);
self.temp_counter += 1;
// Process loop body into the body function
let saved_context = self.current_context.clone();
let capture = self.capture_statements(&while_loop.body)?;
let body_needs_storage = capture.requires_macro_context();
self.add_captured_function(body_func_name.clone(), capture);
self.current_context = saved_context;
let body_call = self.function_call_command(&body_func_name, body_needs_storage);
let mut loop_commands = Vec::new();
let mut loop_metadata = HashMap::new();
Self::append_capture_to_buffers(&mut loop_commands, &mut loop_metadata, &condition_capture);
loop_commands.push(format!(
"execute {} run {}",
condition_execute_args, body_call
));
let body_call_index = loop_commands.len() - 1;
loop_metadata.insert(
body_call_index,
GeneratedCommand::new(
loop_commands[body_call_index].clone(),
self.current_statement_source.clone(),
GeneratedCommandKind::ControlFlow,
),
);
// Re-evaluate the condition after the body before deciding whether to
// recurse. This keeps loops with mutated or computed conditions correct.
Self::append_capture_to_buffers(&mut loop_commands, &mut loop_metadata, &condition_capture);
loop_commands.push(format!(
"execute {} run function {}:{}",
condition_execute_args, self.data_pack.namespace, loop_func_name
));
let recurse_index = loop_commands.len() - 1;
loop_metadata.insert(
recurse_index,
GeneratedCommand::new(
loop_commands[recurse_index].clone(),
self.current_statement_source.clone(),
GeneratedCommandKind::ControlFlow,
),
);
// Add the loop function to the data pack
self.data_pack.add_function_with_metadata(
loop_func_name.clone(),
loop_commands,
loop_metadata,
);
// Start the loop
while_commands.push(format!(
"function {}:{}",
self.data_pack.namespace, loop_func_name
));
if let Some(ref mut commands) = self.current_function {
commands.extend(while_commands);
}
Ok(())
}
fn metadata_for_commands(
commands: &[String],
source: Option<crate::transpiler::SourceLocation>,
) -> HashMap<usize, GeneratedCommand> {
commands
.iter()
.enumerate()
.map(|(index, command)| {
(
index,
GeneratedCommand::new(
command.clone(),
source.clone(),
GeneratedCommandKind::ControlFlow,
),
)
})
.collect()
}
fn append_capture_to_buffers(
commands: &mut Vec<String>,
metadata: &mut HashMap<usize, GeneratedCommand>,
capture: &crate::transpiler::FunctionCapture,
) {
let start = commands.len();
commands.extend(capture.commands.clone());
for (source_index, generated) in &capture.metadata {
metadata.insert(start + source_index, generated.clone());
}
}
}