1use std::fmt::Write;
2
3use crate::lua51::ast::*;
4
5pub fn emit_function(func: &Function) -> String {
7 let mut out = String::new();
8 let body = normalize_block(&func.body);
9 let body = fold_elseif_chain(&body);
10 emit_block(&body, &mut out, 0);
11 out
12}
13
14pub fn emit_chunk(func: &Function) -> String {
16 let mut out = String::new();
17 let body = normalize_block(&func.body);
18 let body = fold_elseif_chain(&body);
19 emit_block(&body, &mut out, 0);
20 while out.ends_with("\n\n") {
22 out.pop();
23 }
24 if !out.ends_with('\n') {
25 out.push('\n');
26 }
27 out
28}
29
30fn normalize_block(block: &Block) -> Block {
31 let mut out = Vec::new();
32 for stat in block {
33 out.extend(normalize_stat(stat));
34 }
35 let out = cache_shared_negated_getter_calls(&out);
36 collapse_nested_table_assignments(&out)
37}
38
39fn collapse_nested_table_assignments(block: &Block) -> Block {
40 let mut out = Vec::new();
41 let mut index = 0;
42
43 while index < block.len() {
44 let Some((seed, base_expr, mut base_fields)) = extract_table_assignment_seed(&block[index]) else {
45 out.push(block[index].clone());
46 index += 1;
47 continue;
48 };
49
50 let mut next = index + 1;
51 while next < block.len() {
52 let Some((field_name, value)) = extract_nested_table_assignment(&block[next], &base_expr) else {
53 break;
54 };
55 base_fields.push(TableField::NameField(field_name, value));
56 next += 1;
57 }
58
59 if next == index + 1 {
60 out.push(block[index].clone());
61 index += 1;
62 continue;
63 }
64
65 out.push(rebuild_table_assignment_seed(seed, base_fields));
66 index = next;
67 }
68
69 out
70}
71
72enum TableAssignmentSeed {
73 Assign(Expr),
74 LocalAssign(String),
75}
76
77fn extract_table_assignment_seed(
78 stat: &Stat,
79) -> Option<(TableAssignmentSeed, Expr, Vec<TableField>)> {
80 match stat {
81 Stat::Assign { targets, values } if targets.len() == 1 && values.len() == 1 => {
82 let Expr::Table(fields) = &values[0] else {
83 return None;
84 };
85 let base = targets[0].clone();
86 let fields = fields.clone();
87 Some((TableAssignmentSeed::Assign(base.clone()), base, fields))
88 }
89 Stat::LocalAssign { names, exprs } if names.len() == 1 && exprs.len() == 1 => {
90 let Expr::Table(fields) = &exprs[0] else {
91 return None;
92 };
93 let local_name = names[0].clone();
94 let fields = fields.clone();
95 Some((
96 TableAssignmentSeed::LocalAssign(local_name.clone()),
97 Expr::Name(local_name),
98 fields,
99 ))
100 }
101 _ => None,
102 }
103}
104
105fn rebuild_table_assignment_seed(seed: TableAssignmentSeed, fields: Vec<TableField>) -> Stat {
106 match seed {
107 TableAssignmentSeed::Assign(target) => Stat::Assign {
108 targets: vec![target],
109 values: vec![Expr::Table(fields)],
110 },
111 TableAssignmentSeed::LocalAssign(name) => Stat::LocalAssign {
112 names: vec![name],
113 exprs: vec![Expr::Table(fields)],
114 },
115 }
116}
117
118fn extract_nested_table_assignment(stat: &Stat, base_expr: &Expr) -> Option<(String, Expr)> {
119 let Stat::Assign { targets, values } = stat else {
120 return None;
121 };
122 if targets.len() != 1 || values.len() != 1 {
123 return None;
124 }
125 let Expr::Table(_) = &values[0] else {
126 return None;
127 };
128
129 let field_name = match &targets[0] {
130 Expr::Field(table, field) if table.as_ref() == base_expr => field.clone(),
131 Expr::Index(table, key) if table.as_ref() == base_expr => match key.as_ref() {
132 Expr::StringLit(bytes) => {
133 let field = std::str::from_utf8(bytes).ok()?;
134 if !is_identifier(field) {
135 return None;
136 }
137 field.to_string()
138 }
139 _ => return None,
140 },
141 _ => return None,
142 };
143
144 Some((field_name, values[0].clone()))
145}
146
147fn is_identifier(name: &str) -> bool {
148 if name.is_empty() {
149 return false;
150 }
151 let mut chars = name.chars();
152 let first = chars.next().unwrap();
153 if !first.is_ascii_alphabetic() && first != '_' {
154 return false;
155 }
156 chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_') && !is_lua_keyword(name)
157}
158
159fn is_lua_keyword(name: &str) -> bool {
160 matches!(
161 name,
162 "and"
163 | "break"
164 | "do"
165 | "else"
166 | "elseif"
167 | "end"
168 | "false"
169 | "for"
170 | "function"
171 | "if"
172 | "in"
173 | "local"
174 | "nil"
175 | "not"
176 | "or"
177 | "repeat"
178 | "return"
179 | "then"
180 | "true"
181 | "until"
182 | "while"
183 )
184}
185
186fn normalize_stat(stat: &Stat) -> Block {
187 match stat {
188 Stat::LocalAssign { names, exprs } => vec![Stat::LocalAssign {
189 names: names.clone(),
190 exprs: exprs.iter().cloned().map(simplify_expr).collect(),
191 }],
192 Stat::Assign { targets, values } => vec![Stat::Assign {
193 targets: targets.iter().cloned().map(simplify_expr).collect(),
194 values: values.iter().cloned().map(simplify_expr).collect(),
195 }],
196 Stat::Call(call) => vec![Stat::Call(simplify_call(call.clone()))],
197 Stat::DoBlock(body) => vec![Stat::DoBlock(normalize_block(body))],
198 Stat::While { cond, body } => vec![Stat::While {
199 cond: simplify_expr(cond.clone()),
200 body: normalize_block(body),
201 }],
202 Stat::Repeat { body, cond } => vec![Stat::Repeat {
203 body: normalize_block(body),
204 cond: simplify_expr(cond.clone()),
205 }],
206 Stat::If {
207 cond,
208 then_block,
209 elseif_clauses,
210 else_block,
211 } => normalize_if(cond, then_block, elseif_clauses, else_block),
212 Stat::NumericFor {
213 name,
214 start,
215 limit,
216 step,
217 body,
218 } => vec![Stat::NumericFor {
219 name: name.clone(),
220 start: simplify_expr(start.clone()),
221 limit: simplify_expr(limit.clone()),
222 step: step.clone().map(simplify_expr),
223 body: normalize_block(body),
224 }],
225 Stat::GenericFor { names, iterators, body } => vec![Stat::GenericFor {
226 names: names.clone(),
227 iterators: iterators.iter().cloned().map(simplify_expr).collect(),
228 body: normalize_block(body),
229 }],
230 Stat::Return(exprs) => vec![Stat::Return(
231 exprs.iter().cloned().map(simplify_expr).collect(),
232 )],
233 Stat::Break => vec![Stat::Break],
234 Stat::Comment(text) => vec![Stat::Comment(text.clone())],
235 }
236}
237
238fn normalize_if(
239 cond: &Expr,
240 then_block: &Block,
241 elseif_clauses: &[(Expr, Block)],
242 else_block: &Option<Block>,
243) -> Block {
244 let cond = simplify_expr(cond.clone());
245 let then_block = normalize_block(then_block);
246 let mut elseif_clauses: Vec<(Expr, Block)> = elseif_clauses
247 .iter()
248 .map(|(expr, block)| (simplify_expr(expr.clone()), normalize_block(block)))
249 .collect();
250 let mut else_block = else_block.as_ref().map(normalize_block);
251
252 loop {
253 let Some(block) = else_block.as_ref() else {
254 break;
255 };
256 if block.len() != 1 {
257 break;
258 }
259 let Stat::If {
260 cond: inner_cond,
261 then_block: inner_then,
262 elseif_clauses: inner_elseifs,
263 else_block: inner_else,
264 } = &block[0] else {
265 break;
266 };
267
268 elseif_clauses.push((simplify_expr(inner_cond.clone()), inner_then.clone()));
269 elseif_clauses.extend(inner_elseifs.iter().cloned());
270 else_block = inner_else.clone();
271 }
272
273 if then_block.is_empty() && elseif_clauses.is_empty() {
274 if let Some(else_block) = else_block.as_ref() {
275 if !else_block.is_empty() {
276 return vec![Stat::If {
277 cond: negate_condition(cond),
278 then_block: else_block.clone(),
279 elseif_clauses: Vec::new(),
280 else_block: None,
281 }];
282 }
283 }
284 }
285
286 if elseif_clauses.is_empty() && else_block.is_none() && then_block.len() == 1 {
287 if let Stat::If {
288 cond: inner_cond,
289 then_block: inner_then,
290 elseif_clauses: inner_elseifs,
291 else_block: inner_else,
292 } = &then_block[0]
293 {
294 let merged_terms = count_and_terms(&cond) + count_and_terms(inner_cond);
295 if inner_elseifs.is_empty() && inner_else.is_none() && !inner_then.is_empty() && merged_terms <= 3 {
296 return vec![Stat::If {
297 cond: simplify_expr(Expr::BinOp(
298 BinOp::And,
299 Box::new(cond),
300 Box::new(inner_cond.clone()),
301 )),
302 then_block: inner_then.clone(),
303 elseif_clauses: Vec::new(),
304 else_block: None,
305 }];
306 }
307 }
308 }
309
310 if elseif_clauses.is_empty() && else_block.is_none() && then_block.len() > 1 {
311 if let Stat::If {
312 cond: inner_cond,
313 then_block: inner_then,
314 elseif_clauses: inner_elseifs,
315 else_block: inner_else,
316 } = &then_block[0]
317 {
318 let merged_terms = count_and_terms(&cond) + count_and_terms(inner_cond);
319 if inner_elseifs.is_empty()
320 && inner_else.is_none()
321 && block_ends_with_terminator(inner_then)
322 && merged_terms <= 3
323 {
324 let mut out = vec![Stat::If {
325 cond: simplify_expr(Expr::BinOp(
326 BinOp::And,
327 Box::new(cond),
328 Box::new(inner_cond.clone()),
329 )),
330 then_block: inner_then.clone(),
331 elseif_clauses: Vec::new(),
332 else_block: None,
333 }];
334 out.extend(then_block[1..].iter().cloned());
335 return out;
336 }
337 }
338 }
339
340 if else_block.is_none()
341 && !then_block.is_empty()
342 && !elseif_clauses.is_empty()
343 && elseif_clauses.iter().all(|(_, block)| *block == then_block)
344 {
345 let merged_cond = elseif_clauses.iter().fold(cond.clone(), |acc, (expr, _)| {
346 simplify_expr(Expr::BinOp(BinOp::Or, Box::new(acc), Box::new(expr.clone())))
347 });
348 return vec![Stat::If {
349 cond: merged_cond,
350 then_block,
351 elseif_clauses: Vec::new(),
352 else_block: None,
353 }];
354 }
355
356 if elseif_clauses.is_empty() && else_block.is_none() {
357 if let Some(rewritten) = cache_repeated_trailing_getter_call(&cond, &then_block) {
358 return rewritten;
359 }
360 }
361
362 let (trimmed_then, trailing_then) = split_trailing_terminator_tail(&then_block);
363 if trailing_then.is_empty() {
364 return vec![Stat::If {
365 cond,
366 then_block,
367 elseif_clauses,
368 else_block,
369 }];
370 }
371
372 if !elseif_clauses.is_empty() || else_block.is_some() {
373 return vec![Stat::If {
374 cond,
375 then_block: trimmed_then,
376 elseif_clauses,
377 else_block,
378 }];
379 }
380
381 let mut out = vec![Stat::If {
382 cond,
383 then_block: trimmed_then,
384 elseif_clauses,
385 else_block,
386 }];
387 out.extend(trailing_then);
388 out
389}
390
391fn split_trailing_terminator_tail(block: &Block) -> (Block, Block) {
392 for (index, stat) in block.iter().enumerate() {
393 if is_terminator(stat) {
394 let prefix = block[..=index].to_vec();
395 let tail = block[index + 1..].to_vec();
396 return (prefix, tail);
397 }
398 }
399 (block.clone(), Vec::new())
400}
401
402fn block_ends_with_terminator(block: &Block) -> bool {
403 block.last().is_some_and(is_terminator)
404}
405
406fn is_terminator(stat: &Stat) -> bool {
407 matches!(stat, Stat::Return(_) | Stat::Break)
408}
409
410fn count_and_terms(expr: &Expr) -> usize {
411 match expr {
412 Expr::BinOp(BinOp::And, lhs, rhs) => count_and_terms(lhs) + count_and_terms(rhs),
413 _ => 1,
414 }
415}
416
417fn simplify_expr(expr: Expr) -> Expr {
418 match expr {
419 Expr::Index(table, key) => Expr::Index(
420 Box::new(simplify_expr(*table)),
421 Box::new(simplify_expr(*key)),
422 ),
423 Expr::Field(table, field) => Expr::Field(Box::new(simplify_expr(*table)), field),
424 Expr::MethodCall(call) => Expr::MethodCall(Box::new(simplify_call(*call))),
425 Expr::FuncCall(call) => Expr::FuncCall(Box::new(simplify_call(*call))),
426 Expr::BinOp(op, lhs, rhs) => {
427 let lhs = simplify_expr(*lhs);
428 let rhs = simplify_expr(*rhs);
429 match (op, &lhs) {
430 (BinOp::And, Expr::Bool(true)) => rhs,
431 (BinOp::And, Expr::Bool(false)) => Expr::Bool(false),
432 (BinOp::Or, Expr::Bool(false)) => rhs,
433 (BinOp::Or, Expr::Bool(true)) => Expr::Bool(true),
434 _ => Expr::BinOp(op, Box::new(lhs), Box::new(rhs)),
435 }
436 }
437 Expr::UnOp(UnOp::Not, operand) => {
438 let operand = simplify_expr(*operand);
439 match operand {
440 Expr::Bool(value) => Expr::Bool(!value),
441 Expr::UnOp(UnOp::Not, inner) => Expr::UnOp(UnOp::Not, inner),
442 other => Expr::UnOp(UnOp::Not, Box::new(other)),
443 }
444 }
445 Expr::UnOp(op, operand) => Expr::UnOp(op, Box::new(simplify_expr(*operand))),
446 Expr::FunctionDef(func) => Expr::FunctionDef(Box::new(Function {
447 params: func.params.clone(),
448 is_vararg: func.is_vararg,
449 body: normalize_block(&func.body),
450 })),
451 Expr::Table(fields) => Expr::Table(
452 fields
453 .into_iter()
454 .map(|field| match field {
455 TableField::IndexField(key, value) => {
456 TableField::IndexField(simplify_expr(key), simplify_expr(value))
457 }
458 TableField::NameField(name, value) => {
459 TableField::NameField(name, simplify_expr(value))
460 }
461 TableField::Value(value) => TableField::Value(simplify_expr(value)),
462 })
463 .collect(),
464 ),
465 other => other,
466 }
467}
468
469fn simplify_call(call: CallExpr) -> CallExpr {
470 CallExpr {
471 func: simplify_expr(call.func),
472 args: call.args.into_iter().map(simplify_expr).collect(),
473 }
474}
475
476fn negate_condition(expr: Expr) -> Expr {
477 match expr {
478 Expr::Bool(value) => Expr::Bool(!value),
479 Expr::UnOp(UnOp::Not, inner) => simplify_expr(*inner),
480 other => Expr::UnOp(UnOp::Not, Box::new(other)),
481 }
482}
483
484fn cache_repeated_trailing_getter_call(cond: &Expr, then_block: &Block) -> Option<Block> {
485 let mut terms = Vec::new();
486 collect_and_terms(cond, &mut terms);
487 let last_term = terms.last()?.clone();
488 let call_expr = match &last_term {
489 Expr::FuncCall(call) if is_cacheable_getter_call(call) => last_term.clone(),
490 _ => return None,
491 };
492
493 if !block_contains_expr(then_block, &call_expr) {
494 return None;
495 }
496
497 let cache_name = uniquify_cache_name(suggest_cached_call_name(&call_expr), cond, then_block);
498 let outer_terms = &terms[..terms.len() - 1];
499 let rewritten_then = replace_exprs_in_block(then_block, &call_expr, &Expr::Name(cache_name.clone()));
500 let inner_if = Stat::If {
501 cond: Expr::Name(cache_name.clone()),
502 then_block: rewritten_then,
503 elseif_clauses: Vec::new(),
504 else_block: None,
505 };
506 let cached_local = Stat::LocalAssign {
507 names: vec![cache_name.clone()],
508 exprs: vec![call_expr],
509 };
510
511 if outer_terms.is_empty() {
512 return Some(vec![Stat::DoBlock(vec![cached_local, inner_if])]);
513 }
514
515 Some(vec![Stat::If {
516 cond: combine_and_terms(outer_terms),
517 then_block: vec![cached_local, inner_if],
518 elseif_clauses: Vec::new(),
519 else_block: None,
520 }])
521}
522
523fn cache_shared_negated_getter_calls(block: &Block) -> Block {
524 let mut out = Vec::new();
525 let mut index = 0;
526
527 while index < block.len() {
528 let Some((shared_call, mut group)) = collect_negated_getter_group(block, index) else {
529 out.push(block[index].clone());
530 index += 1;
531 continue;
532 };
533
534 let group_len = group.len();
535 if group_len < 2 {
536 out.push(block[index].clone());
537 index += 1;
538 continue;
539 }
540
541 let cache_name = uniquify_cache_name(suggest_cached_call_name(&shared_call), &Expr::Bool(false), block);
542 let replacement = Expr::UnOp(UnOp::Not, Box::new(Expr::Name(cache_name.clone())));
543 let prefixes: Vec<Expr> = group
544 .iter()
545 .filter_map(|(prefix, _)| prefix.clone())
546 .collect();
547
548 let rewritten_ifs: Block = group
549 .drain(..)
550 .map(|(prefix, stat)| rewrite_grouped_if(prefix, stat, &replacement))
551 .collect();
552
553 let mut outer_body = Vec::with_capacity(rewritten_ifs.len() + 1);
554 outer_body.push(Stat::LocalAssign {
555 names: vec![cache_name],
556 exprs: vec![shared_call],
557 });
558 outer_body.extend(rewritten_ifs);
559
560 if prefixes.len() == group_len && !prefixes.is_empty() {
561 out.push(Stat::If {
562 cond: combine_or_terms(&prefixes),
563 then_block: outer_body,
564 elseif_clauses: Vec::new(),
565 else_block: None,
566 });
567 } else {
568 out.push(Stat::DoBlock(outer_body));
569 }
570
571 index += group_len;
572 }
573
574 out
575}
576
577fn collect_negated_getter_group(block: &Block, start: usize) -> Option<(Expr, Vec<(Option<Expr>, Stat)>)> {
578 let (shared_call, first_prefix) = extract_negated_trailing_getter_from_if(block.get(start)?)?;
579 let mut group = vec![(first_prefix, block[start].clone())];
580 let mut index = start + 1;
581
582 while let Some((other_call, other_prefix)) = block.get(index).and_then(extract_negated_trailing_getter_from_if) {
583 if other_call != shared_call {
584 break;
585 }
586 group.push((other_prefix, block[index].clone()));
587 index += 1;
588 }
589
590 Some((shared_call, group))
591}
592
593fn extract_negated_trailing_getter_from_if(stat: &Stat) -> Option<(Expr, Option<Expr>)> {
594 let Stat::If {
595 cond,
596 then_block: _,
597 elseif_clauses,
598 else_block,
599 } = stat else {
600 return None;
601 };
602 if !elseif_clauses.is_empty() || else_block.is_some() {
603 return None;
604 }
605
606 match cond {
607 Expr::BinOp(BinOp::And, prefix, tail) => match &**tail {
608 Expr::UnOp(UnOp::Not, inner) => match &**inner {
609 Expr::FuncCall(call) if is_cacheable_getter_call(call) => {
610 Some((*inner.clone(), Some(*prefix.clone())))
611 }
612 _ => None,
613 },
614 _ => None,
615 },
616 Expr::UnOp(UnOp::Not, inner) => match &**inner {
617 Expr::FuncCall(call) if is_cacheable_getter_call(call) => Some((*inner.clone(), None)),
618 _ => None,
619 },
620 _ => None,
621 }
622}
623
624fn rewrite_grouped_if(
625 prefix: Option<Expr>,
626 stat: Stat,
627 replacement: &Expr,
628) -> Stat {
629 let Stat::If {
630 cond: _,
631 then_block,
632 elseif_clauses: _,
633 else_block: _,
634 } = stat else {
635 unreachable!();
636 };
637
638 let cond = match prefix {
639 Some(prefix) => simplify_expr(Expr::BinOp(
640 BinOp::And,
641 Box::new(prefix),
642 Box::new(replacement.clone()),
643 )),
644 None => replacement.clone(),
645 };
646
647 Stat::If {
648 cond,
649 then_block,
650 elseif_clauses: Vec::new(),
651 else_block: None,
652 }
653}
654
655fn combine_or_terms(terms: &[Expr]) -> Expr {
656 terms
657 .iter()
658 .cloned()
659 .reduce(|lhs, rhs| simplify_expr(Expr::BinOp(BinOp::Or, Box::new(lhs), Box::new(rhs))))
660 .unwrap_or(Expr::Bool(true))
661}
662
663fn collect_and_terms<'a>(expr: &'a Expr, out: &mut Vec<Expr>) {
664 match expr {
665 Expr::BinOp(BinOp::And, lhs, rhs) => {
666 collect_and_terms(lhs, out);
667 collect_and_terms(rhs, out);
668 }
669 _ => out.push(expr.clone()),
670 }
671}
672
673fn combine_and_terms(terms: &[Expr]) -> Expr {
674 terms
675 .iter()
676 .cloned()
677 .reduce(|lhs, rhs| simplify_expr(Expr::BinOp(BinOp::And, Box::new(lhs), Box::new(rhs))))
678 .unwrap_or(Expr::Bool(true))
679}
680
681fn is_cacheable_getter_call(call: &CallExpr) -> bool {
682 match &call.func {
683 Expr::Field(_, method) => matches!(method.as_str(), "GetBuffByOwner" | "GetBuff"),
684 _ => false,
685 }
686}
687
688fn suggest_cached_call_name(expr: &Expr) -> String {
689 let Expr::FuncCall(call) = expr else {
690 return "cached_value".to_string();
691 };
692 let Expr::Field(_, method) = &call.func else {
693 return "cached_value".to_string();
694 };
695
696 if matches!(method.as_str(), "GetBuffByOwner" | "GetBuff") {
697 if let Some(expr) = call.args.first() {
698 match expr {
699 Expr::Number(NumLit::Int(buff_id)) => {
700 return format!("buff_{}", buff_id);
701 }
702 Expr::Number(NumLit::Float(buff_id)) if buff_id.fract() == 0.0 => {
703 return format!("buff_{}", *buff_id as i64);
704 }
705 _ => {}
706 }
707 }
708 }
709
710 camel_to_snake(method)
711}
712
713fn camel_to_snake(name: &str) -> String {
714 let mut out = String::new();
715 for (idx, ch) in name.chars().enumerate() {
716 if ch.is_ascii_uppercase() {
717 if idx != 0 {
718 out.push('_');
719 }
720 out.push(ch.to_ascii_lowercase());
721 } else {
722 out.push(ch);
723 }
724 }
725 out
726}
727
728fn uniquify_cache_name(base: String, cond: &Expr, block: &Block) -> String {
729 if !expr_contains_name(cond, &base) && !block_contains_name(block, &base) {
730 return base;
731 }
732
733 let mut suffix = 1;
734 loop {
735 let candidate = format!("{}_{}", base, suffix);
736 if !expr_contains_name(cond, &candidate) && !block_contains_name(block, &candidate) {
737 return candidate;
738 }
739 suffix += 1;
740 }
741}
742
743fn block_contains_name(block: &Block, name: &str) -> bool {
744 block.iter().any(|stat| stat_contains_name(stat, name))
745}
746
747fn stat_contains_name(stat: &Stat, name: &str) -> bool {
748 match stat {
749 Stat::LocalAssign { names, exprs } => {
750 names.iter().any(|existing| existing == name)
751 || exprs.iter().any(|expr| expr_contains_name(expr, name))
752 }
753 Stat::Assign { targets, values } => {
754 targets.iter().any(|expr| expr_contains_name(expr, name))
755 || values.iter().any(|expr| expr_contains_name(expr, name))
756 }
757 Stat::Call(call) => call_contains_name(call, name),
758 Stat::DoBlock(body) => block_contains_name(body, name),
759 Stat::While { cond, body } => expr_contains_name(cond, name) || block_contains_name(body, name),
760 Stat::Repeat { body, cond } => block_contains_name(body, name) || expr_contains_name(cond, name),
761 Stat::If { cond, then_block, elseif_clauses, else_block } => {
762 expr_contains_name(cond, name)
763 || block_contains_name(then_block, name)
764 || elseif_clauses.iter().any(|(expr, block)| expr_contains_name(expr, name) || block_contains_name(block, name))
765 || else_block.as_ref().is_some_and(|block| block_contains_name(block, name))
766 }
767 Stat::NumericFor { name: loop_name, start, limit, step, body } => {
768 loop_name == name
769 || expr_contains_name(start, name)
770 || expr_contains_name(limit, name)
771 || step.as_ref().is_some_and(|expr| expr_contains_name(expr, name))
772 || block_contains_name(body, name)
773 }
774 Stat::GenericFor { names, iterators, body } => {
775 names.iter().any(|existing| existing == name)
776 || iterators.iter().any(|expr| expr_contains_name(expr, name))
777 || block_contains_name(body, name)
778 }
779 Stat::Return(exprs) => exprs.iter().any(|expr| expr_contains_name(expr, name)),
780 Stat::Break | Stat::Comment(_) => false,
781 }
782}
783
784fn expr_contains_name(expr: &Expr, name: &str) -> bool {
785 match expr {
786 Expr::Name(existing) => existing == name,
787 Expr::Index(table, key) => expr_contains_name(table, name) || expr_contains_name(key, name),
788 Expr::Field(table, _) => expr_contains_name(table, name),
789 Expr::MethodCall(call) | Expr::FuncCall(call) => call_contains_name(call, name),
790 Expr::BinOp(_, lhs, rhs) => expr_contains_name(lhs, name) || expr_contains_name(rhs, name),
791 Expr::UnOp(_, inner) => expr_contains_name(inner, name),
792 Expr::FunctionDef(func) => block_contains_name(&func.body, name),
793 Expr::Table(fields) => fields.iter().any(|field| match field {
794 TableField::IndexField(key, value) => expr_contains_name(key, name) || expr_contains_name(value, name),
795 TableField::NameField(_, value) | TableField::Value(value) => expr_contains_name(value, name),
796 }),
797 _ => false,
798 }
799}
800
801fn call_contains_name(call: &CallExpr, name: &str) -> bool {
802 expr_contains_name(&call.func, name) || call.args.iter().any(|arg| expr_contains_name(arg, name))
803}
804
805fn block_contains_expr(block: &Block, target: &Expr) -> bool {
806 block.iter().any(|stat| stat_contains_expr(stat, target))
807}
808
809fn stat_contains_expr(stat: &Stat, target: &Expr) -> bool {
810 match stat {
811 Stat::LocalAssign { exprs, .. } => exprs.iter().any(|expr| expr_contains_expr(expr, target)),
812 Stat::Assign { targets, values } => {
813 targets.iter().any(|expr| expr_contains_expr(expr, target))
814 || values.iter().any(|expr| expr_contains_expr(expr, target))
815 }
816 Stat::Call(call) => expr_contains_expr(&Expr::FuncCall(Box::new(call.clone())), target),
817 Stat::DoBlock(body) => block_contains_expr(body, target),
818 Stat::While { cond, body } => expr_contains_expr(cond, target) || block_contains_expr(body, target),
819 Stat::Repeat { body, cond } => block_contains_expr(body, target) || expr_contains_expr(cond, target),
820 Stat::If { cond, then_block, elseif_clauses, else_block } => {
821 expr_contains_expr(cond, target)
822 || block_contains_expr(then_block, target)
823 || elseif_clauses.iter().any(|(expr, block)| expr_contains_expr(expr, target) || block_contains_expr(block, target))
824 || else_block.as_ref().is_some_and(|block| block_contains_expr(block, target))
825 }
826 Stat::NumericFor { start, limit, step, body, .. } => {
827 expr_contains_expr(start, target)
828 || expr_contains_expr(limit, target)
829 || step.as_ref().is_some_and(|expr| expr_contains_expr(expr, target))
830 || block_contains_expr(body, target)
831 }
832 Stat::GenericFor { iterators, body, .. } => {
833 iterators.iter().any(|expr| expr_contains_expr(expr, target))
834 || block_contains_expr(body, target)
835 }
836 Stat::Return(exprs) => exprs.iter().any(|expr| expr_contains_expr(expr, target)),
837 Stat::Break | Stat::Comment(_) => false,
838 }
839}
840
841fn expr_contains_expr(expr: &Expr, target: &Expr) -> bool {
842 if expr == target {
843 return true;
844 }
845
846 match expr {
847 Expr::Index(table, key) => expr_contains_expr(table, target) || expr_contains_expr(key, target),
848 Expr::Field(table, _) => expr_contains_expr(table, target),
849 Expr::MethodCall(call) | Expr::FuncCall(call) => {
850 expr_contains_expr(&call.func, target)
851 || call.args.iter().any(|arg| expr_contains_expr(arg, target))
852 }
853 Expr::BinOp(_, lhs, rhs) => expr_contains_expr(lhs, target) || expr_contains_expr(rhs, target),
854 Expr::UnOp(_, inner) => expr_contains_expr(inner, target),
855 Expr::FunctionDef(func) => block_contains_expr(&func.body, target),
856 Expr::Table(fields) => fields.iter().any(|field| match field {
857 TableField::IndexField(key, value) => expr_contains_expr(key, target) || expr_contains_expr(value, target),
858 TableField::NameField(_, value) | TableField::Value(value) => expr_contains_expr(value, target),
859 }),
860 _ => false,
861 }
862}
863
864fn replace_exprs_in_block(block: &Block, target: &Expr, replacement: &Expr) -> Block {
865 block.iter().map(|stat| replace_exprs_in_stat(stat, target, replacement)).collect()
866}
867
868fn replace_exprs_in_stat(stat: &Stat, target: &Expr, replacement: &Expr) -> Stat {
869 match stat {
870 Stat::LocalAssign { names, exprs } => Stat::LocalAssign {
871 names: names.clone(),
872 exprs: exprs.iter().map(|expr| replace_expr(expr, target, replacement)).collect(),
873 },
874 Stat::Assign { targets, values } => Stat::Assign {
875 targets: targets.iter().map(|expr| replace_expr(expr, target, replacement)).collect(),
876 values: values.iter().map(|expr| replace_expr(expr, target, replacement)).collect(),
877 },
878 Stat::Call(call) => match replace_expr(&Expr::FuncCall(Box::new(call.clone())), target, replacement) {
879 Expr::FuncCall(updated) => Stat::Call(*updated),
880 expr => Stat::Call(CallExpr { func: expr, args: Vec::new() }),
881 },
882 Stat::DoBlock(body) => Stat::DoBlock(replace_exprs_in_block(body, target, replacement)),
883 Stat::While { cond, body } => Stat::While {
884 cond: replace_expr(cond, target, replacement),
885 body: replace_exprs_in_block(body, target, replacement),
886 },
887 Stat::Repeat { body, cond } => Stat::Repeat {
888 body: replace_exprs_in_block(body, target, replacement),
889 cond: replace_expr(cond, target, replacement),
890 },
891 Stat::If { cond, then_block, elseif_clauses, else_block } => Stat::If {
892 cond: replace_expr(cond, target, replacement),
893 then_block: replace_exprs_in_block(then_block, target, replacement),
894 elseif_clauses: elseif_clauses
895 .iter()
896 .map(|(expr, block)| {
897 (
898 replace_expr(expr, target, replacement),
899 replace_exprs_in_block(block, target, replacement),
900 )
901 })
902 .collect(),
903 else_block: else_block
904 .as_ref()
905 .map(|block| replace_exprs_in_block(block, target, replacement)),
906 },
907 Stat::NumericFor { name, start, limit, step, body } => Stat::NumericFor {
908 name: name.clone(),
909 start: replace_expr(start, target, replacement),
910 limit: replace_expr(limit, target, replacement),
911 step: step.as_ref().map(|expr| replace_expr(expr, target, replacement)),
912 body: replace_exprs_in_block(body, target, replacement),
913 },
914 Stat::GenericFor { names, iterators, body } => Stat::GenericFor {
915 names: names.clone(),
916 iterators: iterators.iter().map(|expr| replace_expr(expr, target, replacement)).collect(),
917 body: replace_exprs_in_block(body, target, replacement),
918 },
919 Stat::Return(exprs) => Stat::Return(
920 exprs.iter().map(|expr| replace_expr(expr, target, replacement)).collect(),
921 ),
922 Stat::Break => Stat::Break,
923 Stat::Comment(text) => Stat::Comment(text.clone()),
924 }
925}
926
927fn replace_expr(expr: &Expr, target: &Expr, replacement: &Expr) -> Expr {
928 if expr == target {
929 return replacement.clone();
930 }
931
932 match expr {
933 Expr::Index(table, key) => Expr::Index(
934 Box::new(replace_expr(table, target, replacement)),
935 Box::new(replace_expr(key, target, replacement)),
936 ),
937 Expr::Field(table, field) => Expr::Field(
938 Box::new(replace_expr(table, target, replacement)),
939 field.clone(),
940 ),
941 Expr::MethodCall(call) => Expr::MethodCall(Box::new(replace_call(call, target, replacement))),
942 Expr::FuncCall(call) => Expr::FuncCall(Box::new(replace_call(call, target, replacement))),
943 Expr::BinOp(op, lhs, rhs) => Expr::BinOp(
944 *op,
945 Box::new(replace_expr(lhs, target, replacement)),
946 Box::new(replace_expr(rhs, target, replacement)),
947 ),
948 Expr::UnOp(op, inner) => Expr::UnOp(*op, Box::new(replace_expr(inner, target, replacement))),
949 Expr::FunctionDef(func) => Expr::FunctionDef(Box::new(Function {
950 params: func.params.clone(),
951 is_vararg: func.is_vararg,
952 body: replace_exprs_in_block(&func.body, target, replacement),
953 })),
954 Expr::Table(fields) => Expr::Table(
955 fields.iter().map(|field| match field {
956 TableField::IndexField(key, value) => TableField::IndexField(
957 replace_expr(key, target, replacement),
958 replace_expr(value, target, replacement),
959 ),
960 TableField::NameField(name, value) => {
961 TableField::NameField(name.clone(), replace_expr(value, target, replacement))
962 }
963 TableField::Value(value) => TableField::Value(replace_expr(value, target, replacement)),
964 }).collect(),
965 ),
966 other => other.clone(),
967 }
968}
969
970fn replace_call(call: &CallExpr, target: &Expr, replacement: &Expr) -> CallExpr {
971 CallExpr {
972 func: replace_expr(&call.func, target, replacement),
973 args: call.args.iter().map(|arg| replace_expr(arg, target, replacement)).collect(),
974 }
975}
976
977fn fold_elseif_chain(block: &Block) -> Block {
981 block.iter().map(|stat| fold_stat(stat)).collect()
982}
983
984fn fold_stat(stat: &Stat) -> Stat {
985 match stat {
986 Stat::If {
987 cond,
988 then_block,
989 elseif_clauses,
990 else_block,
991 } => {
992 let then_block = fold_elseif_chain(then_block);
993 let mut new_elseifs: Vec<(Expr, Block)> = elseif_clauses
994 .iter()
995 .map(|(c, b)| (c.clone(), fold_elseif_chain(b)))
996 .collect();
997
998 let new_else = if let Some(eb) = else_block {
1000 let folded = fold_elseif_chain(eb);
1001 if folded.len() == 1 {
1002 if let Stat::If {
1003 cond: inner_cond,
1004 then_block: inner_then,
1005 elseif_clauses: inner_elseifs,
1006 else_block: inner_else,
1007 } = &folded[0]
1008 {
1009 new_elseifs.push((inner_cond.clone(), inner_then.clone()));
1011 new_elseifs.extend(inner_elseifs.iter().cloned());
1012 inner_else.clone()
1013 } else {
1014 Some(folded)
1015 }
1016 } else {
1017 Some(folded)
1018 }
1019 } else {
1020 None
1021 };
1022
1023 Stat::If {
1024 cond: cond.clone(),
1025 then_block,
1026 elseif_clauses: new_elseifs,
1027 else_block: new_else,
1028 }
1029 }
1030 Stat::While { cond, body } => Stat::While {
1031 cond: cond.clone(),
1032 body: fold_elseif_chain(body),
1033 },
1034 Stat::Repeat { body, cond } => Stat::Repeat {
1035 body: fold_elseif_chain(body),
1036 cond: cond.clone(),
1037 },
1038 Stat::NumericFor { name, start, limit, step, body } => Stat::NumericFor {
1039 name: name.clone(),
1040 start: start.clone(),
1041 limit: limit.clone(),
1042 step: step.clone(),
1043 body: fold_elseif_chain(body),
1044 },
1045 Stat::GenericFor { names, iterators, body } => Stat::GenericFor {
1046 names: names.clone(),
1047 iterators: iterators.clone(),
1048 body: fold_elseif_chain(body),
1049 },
1050 Stat::DoBlock(body) => Stat::DoBlock(fold_elseif_chain(body)),
1051 other => other.clone(),
1052 }
1053}
1054
1055fn emit_block(block: &Block, out: &mut String, indent: usize) {
1056 for (i, stat) in block.iter().enumerate() {
1057 if indent == 0 && i > 0 {
1059 let prev = &block[i - 1];
1060 if should_separate(prev, stat) {
1061 out.push('\n');
1062 }
1063 }
1064 emit_stat(stat, out, indent);
1065 }
1066}
1067
1068fn should_separate(prev: &Stat, curr: &Stat) -> bool {
1070 if is_func_def(prev) {
1072 return true;
1073 }
1074 if is_func_def(curr) {
1076 return true;
1077 }
1078 false
1081}
1082
1083fn is_func_def(stat: &Stat) -> bool {
1084 match stat {
1085 Stat::Assign { values, .. } => {
1086 values.len() == 1 && matches!(&values[0], Expr::FunctionDef(_))
1087 }
1088 Stat::LocalAssign { exprs, .. } => {
1089 exprs.len() == 1 && matches!(&exprs[0], Expr::FunctionDef(_))
1090 }
1091 _ => false,
1092 }
1093}
1094
1095fn emit_stat(stat: &Stat, out: &mut String, indent: usize) {
1096 let pad = " ".repeat(indent);
1097 match stat {
1098 Stat::LocalAssign { names, exprs } => {
1099 write!(out, "{}local {}", pad, names.join(", ")).unwrap();
1100 if !exprs.is_empty() {
1101 out.push_str(" = ");
1102 emit_expr_list(exprs, out);
1103 }
1104 out.push('\n');
1105 }
1106 Stat::Assign { targets, values } => {
1107 if targets.len() == 1 && values.len() == 1 {
1109 if let Expr::FunctionDef(func) = &values[0] {
1110 let name = match &targets[0] {
1111 Expr::Global(n) => Some(n.clone()),
1112 Expr::Name(n) => Some(n.clone()),
1113 Expr::Field(table, field) => {
1114 let mut s = String::new();
1116 emit_expr(table, &mut s, 10);
1117 s.push('.');
1118 s.push_str(field);
1119 Some(s)
1120 }
1121 _ => None,
1122 };
1123 if let Some(fname) = name {
1124 write!(out, "{}function {}(", pad, fname).unwrap();
1125 let mut params = func.params.join(", ");
1126 if func.is_vararg {
1127 if !params.is_empty() {
1128 params.push_str(", ");
1129 }
1130 params.push_str("...");
1131 }
1132 out.push_str(¶ms);
1133 out.push_str(")\n");
1134 emit_block(&func.body, out, indent + 1);
1135 writeln!(out, "{}end", pad).unwrap();
1136 return;
1137 }
1138 }
1139 }
1140 write!(out, "{}", pad).unwrap();
1141 emit_expr_list(targets, out);
1142 out.push_str(" = ");
1143 emit_expr_list(values, out);
1144 out.push('\n');
1145 }
1146 Stat::Call(call) => {
1147 write!(out, "{}", pad).unwrap();
1148 emit_call(call, out);
1149 out.push('\n');
1150 }
1151 Stat::DoBlock(body) => {
1152 writeln!(out, "{}do", pad).unwrap();
1153 emit_block(body, out, indent + 1);
1154 writeln!(out, "{}end", pad).unwrap();
1155 }
1156 Stat::While { cond, body } => {
1157 write!(out, "{}while ", pad).unwrap();
1158 emit_expr(cond, out, 0);
1159 out.push_str(" do\n");
1160 emit_block(body, out, indent + 1);
1161 writeln!(out, "{}end", pad).unwrap();
1162 }
1163 Stat::Repeat { body, cond } => {
1164 writeln!(out, "{}repeat", pad).unwrap();
1165 emit_block(body, out, indent + 1);
1166 write!(out, "{}until ", pad).unwrap();
1167 emit_expr(cond, out, 0);
1168 out.push('\n');
1169 }
1170 Stat::If {
1171 cond,
1172 then_block,
1173 elseif_clauses,
1174 else_block,
1175 } => {
1176 write!(out, "{}if ", pad).unwrap();
1177 emit_expr(cond, out, 0);
1178 out.push_str(" then\n");
1179 emit_block(then_block, out, indent + 1);
1180 for (ec, eb) in elseif_clauses {
1181 write!(out, "{}elseif ", pad).unwrap();
1182 emit_expr(ec, out, 0);
1183 out.push_str(" then\n");
1184 emit_block(eb, out, indent + 1);
1185 }
1186 if let Some(eb) = else_block {
1187 writeln!(out, "{}else", pad).unwrap();
1188 emit_block(eb, out, indent + 1);
1189 }
1190 writeln!(out, "{}end", pad).unwrap();
1191 }
1192 Stat::NumericFor {
1193 name,
1194 start,
1195 limit,
1196 step,
1197 body,
1198 } => {
1199 write!(out, "{}for {} = ", pad, name).unwrap();
1200 emit_expr(start, out, 0);
1201 out.push_str(", ");
1202 emit_expr(limit, out, 0);
1203 if let Some(s) = step {
1204 out.push_str(", ");
1205 emit_expr(s, out, 0);
1206 }
1207 out.push_str(" do\n");
1208 emit_block(body, out, indent + 1);
1209 writeln!(out, "{}end", pad).unwrap();
1210 }
1211 Stat::GenericFor {
1212 names,
1213 iterators,
1214 body,
1215 } => {
1216 write!(out, "{}for {} in ", pad, names.join(", ")).unwrap();
1217 emit_expr_list(iterators, out);
1218 out.push_str(" do\n");
1219 emit_block(body, out, indent + 1);
1220 writeln!(out, "{}end", pad).unwrap();
1221 }
1222 Stat::Return(exprs) => {
1223 write!(out, "{}return", pad).unwrap();
1224 if !exprs.is_empty() {
1225 out.push(' ');
1226 emit_expr_list(exprs, out);
1227 }
1228 out.push('\n');
1229 }
1230 Stat::Break => {
1231 writeln!(out, "{}break", pad).unwrap();
1232 }
1233 Stat::Comment(text) => {
1234 writeln!(out, "{}-- {}", pad, text).unwrap();
1235 }
1236 }
1237}
1238
1239fn emit_expr_list(exprs: &[Expr], out: &mut String) {
1240 for (i, e) in exprs.iter().enumerate() {
1241 if i > 0 {
1242 out.push_str(", ");
1243 }
1244 emit_expr(e, out, 0);
1245 }
1246}
1247
1248fn emit_expr(expr: &Expr, out: &mut String, parent_prec: u8) {
1251 match expr {
1252 Expr::Nil => out.push_str("nil"),
1253 Expr::Bool(true) => out.push_str("true"),
1254 Expr::Bool(false) => out.push_str("false"),
1255 Expr::Number(n) => emit_number(n, out),
1256 Expr::StringLit(s) => emit_string(s, out),
1257 Expr::VarArg => out.push_str("..."),
1258 Expr::Name(n) => out.push_str(n),
1259 Expr::Global(n) => out.push_str(n),
1260 Expr::Register(r) => write!(out, "r{}", r).unwrap(),
1261 Expr::Upvalue(u) => write!(out, "upval{}", u).unwrap(),
1262 Expr::Index(table, key) => {
1263 emit_expr(table, out, 10);
1264 out.push('[');
1265 emit_expr(key, out, 0);
1266 out.push(']');
1267 }
1268 Expr::Field(table, field) => {
1269 emit_expr(table, out, 10);
1270 out.push('.');
1271 out.push_str(field);
1272 }
1273 Expr::BinOp(op, lhs, rhs) => {
1274 let prec = op.precedence();
1275 let needs_parens = prec < parent_prec;
1276 if needs_parens {
1277 out.push('(');
1278 }
1279 emit_expr(lhs, out, prec);
1280 write!(out, " {} ", op.symbol()).unwrap();
1281 let rhs_prec = if op.is_right_assoc() { prec } else { prec + 1 };
1283 emit_expr(rhs, out, rhs_prec);
1284 if needs_parens {
1285 out.push(')');
1286 }
1287 }
1288 Expr::UnOp(op, operand) => {
1289 let prec = op.precedence();
1290 let needs_parens = prec < parent_prec;
1291 if needs_parens {
1292 out.push('(');
1293 }
1294 out.push_str(op.symbol());
1295 emit_expr(operand, out, prec);
1296 if needs_parens {
1297 out.push(')');
1298 }
1299 }
1300 Expr::FuncCall(call) => {
1301 emit_call(call, out);
1302 }
1303 Expr::MethodCall(call) => {
1304 emit_call(call, out);
1305 }
1306 Expr::FunctionDef(func) => {
1307 emit_function_def(func, out, false);
1308 }
1309 Expr::Table(fields) => {
1310 emit_table(fields, out);
1311 }
1312 }
1313}
1314
1315fn emit_call(call: &CallExpr, out: &mut String) {
1316 emit_expr(&call.func, out, 10);
1317 out.push('(');
1318 emit_expr_list(&call.args, out);
1319 out.push(')');
1320}
1321
1322fn emit_function_def(func: &Function, out: &mut String, _as_stat: bool) {
1323 out.push_str("function(");
1324 let mut params = func.params.join(", ");
1325 if func.is_vararg {
1326 if !params.is_empty() {
1327 params.push_str(", ");
1328 }
1329 params.push_str("...");
1330 }
1331 out.push_str(¶ms);
1332 out.push_str(")\n");
1333
1334 let current_indent = count_trailing_indent(out);
1336 emit_block(&func.body, out, current_indent + 1);
1337
1338 let pad = " ".repeat(current_indent);
1339 write!(out, "{}end", pad).unwrap();
1340}
1341
1342fn emit_table(fields: &[TableField], out: &mut String) {
1343 if fields.is_empty() {
1344 out.push_str("{}");
1345 return;
1346 }
1347
1348 let current_indent = count_trailing_indent(out);
1350 let inline = emit_table_inline(fields);
1351 let use_multiline = inline.len() > 80 || fields.len() > 4 && inline.len() > 60;
1353
1354 if !use_multiline {
1355 out.push_str(&inline);
1356 return;
1357 }
1358
1359 let inner_pad = " ".repeat(current_indent + 1);
1361 let outer_pad = " ".repeat(current_indent);
1362 out.push_str("{\n");
1363 for (i, field) in fields.iter().enumerate() {
1364 out.push_str(&inner_pad);
1365 match field {
1366 TableField::IndexField(key, val) => {
1367 out.push('[');
1368 emit_expr(key, out, 0);
1369 out.push_str("] = ");
1370 emit_expr(val, out, 0);
1371 }
1372 TableField::NameField(name, val) => {
1373 out.push_str(name);
1374 out.push_str(" = ");
1375 emit_expr(val, out, 0);
1376 }
1377 TableField::Value(val) => {
1378 emit_expr(val, out, 0);
1379 }
1380 }
1381 if i + 1 < fields.len() {
1382 out.push(',');
1383 }
1384 out.push('\n');
1385 }
1386 write!(out, "{}}}", outer_pad).unwrap();
1387}
1388
1389fn emit_table_inline(fields: &[TableField]) -> String {
1391 let mut s = String::new();
1392 s.push('{');
1393 for (i, field) in fields.iter().enumerate() {
1394 if i > 0 {
1395 s.push_str(", ");
1396 }
1397 match field {
1398 TableField::IndexField(key, val) => {
1399 s.push('[');
1400 emit_expr(key, &mut s, 0);
1401 s.push_str("] = ");
1402 emit_expr(val, &mut s, 0);
1403 }
1404 TableField::NameField(name, val) => {
1405 s.push_str(name);
1406 s.push_str(" = ");
1407 emit_expr(val, &mut s, 0);
1408 }
1409 TableField::Value(val) => {
1410 emit_expr(val, &mut s, 0);
1411 }
1412 }
1413 }
1414 s.push('}');
1415 s
1416}
1417
1418fn emit_number(n: &NumLit, out: &mut String) {
1419 match n {
1420 NumLit::Int(v) => write!(out, "{}", v).unwrap(),
1421 NumLit::Float(v) => {
1422 if v.fract() == 0.0 && v.abs() < 1e15 {
1423 write!(out, "{}", *v as i64).unwrap();
1425 } else {
1426 write!(out, "{}", v).unwrap();
1427 }
1428 }
1429 }
1430}
1431
1432fn emit_string(bytes: &[u8], out: &mut String) {
1435 if let Ok(s) = std::str::from_utf8(bytes) {
1437 emit_string_content(s, bytes, out);
1438 return;
1439 }
1440
1441 let (decoded, _, had_errors) = encoding_rs::GBK.decode(bytes);
1443 if !had_errors {
1444 emit_string_content(&decoded, bytes, out);
1445 return;
1446 }
1447
1448 out.push('"');
1450 for &b in bytes {
1451 emit_byte_escaped(b, out);
1452 }
1453 out.push('"');
1454}
1455
1456fn emit_string_content(text: &str, _raw: &[u8], out: &mut String) {
1458 let has_newlines = text.contains('\n');
1459 let has_long_bracket_close = text.contains("]]");
1460 let is_printable = text.chars().all(|c| !c.is_control() || c == '\n' || c == '\r' || c == '\t');
1461
1462 if has_newlines && !has_long_bracket_close && is_printable {
1463 out.push_str("[[");
1465 if text.starts_with('\n') {
1466 out.push('\n');
1467 }
1468 out.push_str(text);
1469 out.push_str("]]");
1470 return;
1471 }
1472
1473 out.push('"');
1475 for ch in text.chars() {
1476 match ch {
1477 '\\' => out.push_str("\\\\"),
1478 '"' => out.push_str("\\\""),
1479 '\n' => out.push_str("\\n"),
1480 '\r' => out.push_str("\\r"),
1481 '\t' => out.push_str("\\t"),
1482 '\0' => out.push_str("\\0"),
1483 '\x07' => out.push_str("\\a"),
1484 '\x08' => out.push_str("\\b"),
1485 '\x0C' => out.push_str("\\f"),
1486 '\x0B' => out.push_str("\\v"),
1487 c if c >= ' ' && c <= '~' => out.push(c),
1488 c if !c.is_control() => out.push(c), c => {
1490 let mut buf = [0u8; 4];
1492 let s = c.encode_utf8(&mut buf);
1493 for &b in s.as_bytes() {
1494 write!(out, "\\{}", b).unwrap();
1495 }
1496 }
1497 }
1498 }
1499 out.push('"');
1500}
1501
1502fn emit_byte_escaped(b: u8, out: &mut String) {
1503 match b {
1504 b'\\' => out.push_str("\\\\"),
1505 b'"' => out.push_str("\\\""),
1506 b'\n' => out.push_str("\\n"),
1507 b'\r' => out.push_str("\\r"),
1508 b'\t' => out.push_str("\\t"),
1509 b'\0' => out.push_str("\\0"),
1510 0x07 => out.push_str("\\a"),
1511 0x08 => out.push_str("\\b"),
1512 0x0C => out.push_str("\\f"),
1513 0x0B => out.push_str("\\v"),
1514 0x20..=0x7E => out.push(b as char),
1515 _ => {
1516 write!(out, "\\{}", b).unwrap();
1517 }
1518 }
1519}
1520
1521fn count_trailing_indent(s: &str) -> usize {
1522 if let Some(last_nl) = s.rfind('\n') {
1524 let after = &s[last_nl + 1..];
1525 let spaces = after.len() - after.trim_start().len();
1526 spaces / 2
1527 } else {
1528 0
1529 }
1530}
1531
1532#[cfg(test)]
1533mod tests {
1534 use super::*;
1535
1536 fn emit_single(stat: Stat) -> String {
1537 emit_chunk(&Function {
1538 params: Vec::new(),
1539 is_vararg: false,
1540 body: vec![stat],
1541 })
1542 }
1543
1544 #[test]
1545 fn flattens_linear_nested_ifs() {
1546 let source = emit_single(Stat::If {
1547 cond: Expr::Name("a".to_string()),
1548 then_block: vec![Stat::If {
1549 cond: Expr::Name("b".to_string()),
1550 then_block: vec![Stat::Return(vec![])],
1551 elseif_clauses: Vec::new(),
1552 else_block: None,
1553 }],
1554 elseif_clauses: Vec::new(),
1555 else_block: None,
1556 });
1557
1558 assert!(source.contains("if a and b then"));
1559 assert!(!source.contains("if b then"));
1560 }
1561
1562 #[test]
1563 fn inverts_empty_then_branch() {
1564 let source = emit_single(Stat::If {
1565 cond: Expr::Name("a".to_string()),
1566 then_block: Vec::new(),
1567 elseif_clauses: Vec::new(),
1568 else_block: Some(vec![Stat::Return(vec![])]),
1569 });
1570
1571 assert!(source.contains("if not a then"));
1572 assert!(!source.contains("else"));
1573 }
1574
1575 #[test]
1576 fn simplifies_true_and_condition() {
1577 let source = emit_single(Stat::If {
1578 cond: Expr::BinOp(
1579 BinOp::And,
1580 Box::new(Expr::Bool(true)),
1581 Box::new(Expr::Name("ready".to_string())),
1582 ),
1583 then_block: vec![Stat::Return(vec![])],
1584 elseif_clauses: Vec::new(),
1585 else_block: None,
1586 });
1587
1588 assert!(source.contains("if ready then"));
1589 assert!(!source.contains("true and"));
1590 }
1591
1592 #[test]
1593 fn hoists_guard_tail_out_of_outer_if() {
1594 let source = emit_chunk(&Function {
1595 params: Vec::new(),
1596 is_vararg: false,
1597 body: vec![Stat::If {
1598 cond: Expr::Name("a".to_string()),
1599 then_block: vec![
1600 Stat::If {
1601 cond: Expr::Name("b".to_string()),
1602 then_block: vec![Stat::Return(vec![])],
1603 elseif_clauses: Vec::new(),
1604 else_block: None,
1605 },
1606 Stat::Call(CallExpr {
1607 func: Expr::Name("next_step".to_string()),
1608 args: Vec::new(),
1609 }),
1610 ],
1611 elseif_clauses: Vec::new(),
1612 else_block: None,
1613 }],
1614 });
1615
1616 assert!(source.contains("if a and b then"));
1617 assert!(source.contains("end\nnext_step()"));
1618 }
1619
1620 #[test]
1621 fn folds_else_if_to_elseif() {
1622 let source = emit_single(Stat::If {
1623 cond: Expr::Name("a".to_string()),
1624 then_block: vec![Stat::Call(CallExpr {
1625 func: Expr::Name("left".to_string()),
1626 args: Vec::new(),
1627 })],
1628 elseif_clauses: Vec::new(),
1629 else_block: Some(vec![Stat::If {
1630 cond: Expr::Name("b".to_string()),
1631 then_block: vec![Stat::Call(CallExpr {
1632 func: Expr::Name("right".to_string()),
1633 args: Vec::new(),
1634 })],
1635 elseif_clauses: Vec::new(),
1636 else_block: None,
1637 }]),
1638 });
1639
1640 assert!(source.contains("elseif b then"));
1641 assert!(!source.contains("else\n if b then"));
1642 }
1643
1644 #[test]
1645 fn merges_identical_elseif_bodies() {
1646 let body = vec![Stat::Call(CallExpr {
1647 func: Expr::Name("apply".to_string()),
1648 args: Vec::new(),
1649 })];
1650 let source = emit_single(Stat::If {
1651 cond: Expr::Name("left".to_string()),
1652 then_block: body.clone(),
1653 elseif_clauses: vec![
1654 (Expr::Name("right".to_string()), body.clone()),
1655 (Expr::Name("extra".to_string()), body),
1656 ],
1657 else_block: None,
1658 });
1659
1660 assert!(source.contains("if left or right or extra then"));
1661 assert!(!source.contains("elseif"));
1662 }
1663
1664 #[test]
1665 fn caches_repeated_getter_call_inside_if() {
1666 let buff_call = Expr::FuncCall(Box::new(CallExpr {
1667 func: Expr::Field(Box::new(Expr::Name("a1".to_string())), "GetBuffByOwner".to_string()),
1668 args: vec![
1669 Expr::Number(NumLit::Int(30349)),
1670 Expr::Number(NumLit::Int(0)),
1671 Expr::Field(Box::new(Expr::Name("a0".to_string())), "dwID".to_string()),
1672 ],
1673 }));
1674 let source = emit_single(Stat::If {
1675 cond: Expr::BinOp(
1676 BinOp::And,
1677 Box::new(Expr::Name("ready".to_string())),
1678 Box::new(buff_call.clone()),
1679 ),
1680 then_block: vec![Stat::Assign {
1681 targets: vec![Expr::Name("damage".to_string())],
1682 values: vec![Expr::Field(Box::new(buff_call), "nStackNum".to_string())],
1683 }],
1684 elseif_clauses: Vec::new(),
1685 else_block: None,
1686 });
1687
1688 assert!(source.contains("if ready then"));
1689 assert!(source.contains("local buff_30349 = a1.GetBuffByOwner(30349, 0, a0.dwID)"));
1690 assert!(source.contains("if buff_30349 then"));
1691 assert!(source.contains("damage = buff_30349.nStackNum"));
1692 }
1693
1694 #[test]
1695 fn caches_shared_negated_getter_across_sibling_ifs() {
1696 let guard_call = Expr::FuncCall(Box::new(CallExpr {
1697 func: Expr::Field(Box::new(Expr::Name("a1".to_string())), "GetBuffByOwner".to_string()),
1698 args: vec![
1699 Expr::Number(NumLit::Int(4706)),
1700 Expr::Number(NumLit::Int(1)),
1701 Expr::Field(Box::new(Expr::Name("a0".to_string())), "dwID".to_string()),
1702 ],
1703 }));
1704 let source = emit_chunk(&Function {
1705 params: Vec::new(),
1706 is_vararg: false,
1707 body: vec![
1708 Stat::If {
1709 cond: Expr::BinOp(
1710 BinOp::And,
1711 Box::new(Expr::Name("left".to_string())),
1712 Box::new(Expr::UnOp(UnOp::Not, Box::new(guard_call.clone()))),
1713 ),
1714 then_block: vec![Stat::Call(CallExpr {
1715 func: Expr::Name("first".to_string()),
1716 args: Vec::new(),
1717 })],
1718 elseif_clauses: Vec::new(),
1719 else_block: None,
1720 },
1721 Stat::If {
1722 cond: Expr::BinOp(
1723 BinOp::And,
1724 Box::new(Expr::Name("right".to_string())),
1725 Box::new(Expr::UnOp(UnOp::Not, Box::new(guard_call))),
1726 ),
1727 then_block: vec![Stat::Call(CallExpr {
1728 func: Expr::Name("second".to_string()),
1729 args: Vec::new(),
1730 })],
1731 elseif_clauses: Vec::new(),
1732 else_block: None,
1733 },
1734 ],
1735 });
1736
1737 assert!(source.contains("if left or right then"));
1738 assert!(source.contains("local buff_4706 = a1.GetBuffByOwner(4706, 1, a0.dwID)"));
1739 assert!(source.contains("if left and not buff_4706 then"));
1740 assert!(source.contains("if right and not buff_4706 then"));
1741 }
1742}