Skip to main content

luadec_rust/lua51/
emit.rs

1use std::fmt::Write;
2
3use crate::lua51::ast::*;
4
5/// Emit a Lua function as source code.
6pub fn emit_function(func: &Function) -> String {
7    let mut out = String::new();
8    let body = fold_elseif_chain(&func.body);
9    emit_block(&body, &mut out, 0);
10    out
11}
12
13/// Emit a complete chunk (wrapping the main function body).
14pub fn emit_chunk(func: &Function) -> String {
15    let mut out = String::new();
16    let body = fold_elseif_chain(&func.body);
17    emit_block(&body, &mut out, 0);
18    // Remove trailing blank lines
19    while out.ends_with("\n\n") {
20        out.pop();
21    }
22    if !out.ends_with('\n') {
23        out.push('\n');
24    }
25    out
26}
27
28/// Fold nested if-else chains into elseif clauses.
29/// Transforms: `if A then B else if C then D else E end end`
30/// into:       `if A then B elseif C then D else E end`
31fn fold_elseif_chain(block: &Block) -> Block {
32    block.iter().map(|stat| fold_stat(stat)).collect()
33}
34
35fn fold_stat(stat: &Stat) -> Stat {
36    match stat {
37        Stat::If {
38            cond,
39            then_block,
40            elseif_clauses,
41            else_block,
42        } => {
43            let then_block = fold_elseif_chain(then_block);
44            let mut new_elseifs: Vec<(Expr, Block)> = elseif_clauses
45                .iter()
46                .map(|(c, b)| (c.clone(), fold_elseif_chain(b)))
47                .collect();
48
49            // Try to flatten: if else_block is a single `if` statement, merge it
50            let new_else = if let Some(eb) = else_block {
51                let folded = fold_elseif_chain(eb);
52                if folded.len() == 1 {
53                    if let Stat::If {
54                        cond: inner_cond,
55                        then_block: inner_then,
56                        elseif_clauses: inner_elseifs,
57                        else_block: inner_else,
58                    } = &folded[0]
59                    {
60                        // Merge into elseif
61                        new_elseifs.push((inner_cond.clone(), inner_then.clone()));
62                        new_elseifs.extend(inner_elseifs.iter().cloned());
63                        inner_else.clone()
64                    } else {
65                        Some(folded)
66                    }
67                } else {
68                    Some(folded)
69                }
70            } else {
71                None
72            };
73
74            Stat::If {
75                cond: cond.clone(),
76                then_block,
77                elseif_clauses: new_elseifs,
78                else_block: new_else,
79            }
80        }
81        Stat::While { cond, body } => Stat::While {
82            cond: cond.clone(),
83            body: fold_elseif_chain(body),
84        },
85        Stat::Repeat { body, cond } => Stat::Repeat {
86            body: fold_elseif_chain(body),
87            cond: cond.clone(),
88        },
89        Stat::NumericFor { name, start, limit, step, body } => Stat::NumericFor {
90            name: name.clone(),
91            start: start.clone(),
92            limit: limit.clone(),
93            step: step.clone(),
94            body: fold_elseif_chain(body),
95        },
96        Stat::GenericFor { names, iterators, body } => Stat::GenericFor {
97            names: names.clone(),
98            iterators: iterators.clone(),
99            body: fold_elseif_chain(body),
100        },
101        Stat::DoBlock(body) => Stat::DoBlock(fold_elseif_chain(body)),
102        other => other.clone(),
103    }
104}
105
106fn emit_block(block: &Block, out: &mut String, indent: usize) {
107    for (i, stat) in block.iter().enumerate() {
108        // Add blank lines between top-level logical groups
109        if indent == 0 && i > 0 {
110            let prev = &block[i - 1];
111            if should_separate(prev, stat) {
112                out.push('\n');
113            }
114        }
115        emit_stat(stat, out, indent);
116    }
117}
118
119/// Decide whether to insert a blank line between two consecutive top-level statements.
120fn should_separate(prev: &Stat, curr: &Stat) -> bool {
121    // Always separate after a function definition
122    if is_func_def(prev) {
123        return true;
124    }
125    // Separate before a function definition
126    if is_func_def(curr) {
127        return true;
128    }
129    // Separate between different "kinds" of statements
130    // (e.g., after a block of assignments before a call, or vice versa)
131    false
132}
133
134fn is_func_def(stat: &Stat) -> bool {
135    match stat {
136        Stat::Assign { values, .. } => {
137            values.len() == 1 && matches!(&values[0], Expr::FunctionDef(_))
138        }
139        Stat::LocalAssign { exprs, .. } => {
140            exprs.len() == 1 && matches!(&exprs[0], Expr::FunctionDef(_))
141        }
142        _ => false,
143    }
144}
145
146fn emit_stat(stat: &Stat, out: &mut String, indent: usize) {
147    let pad = "  ".repeat(indent);
148    match stat {
149        Stat::LocalAssign { names, exprs } => {
150            write!(out, "{}local {}", pad, names.join(", ")).unwrap();
151            if !exprs.is_empty() {
152                out.push_str(" = ");
153                emit_expr_list(exprs, out);
154            }
155            out.push('\n');
156        }
157        Stat::Assign { targets, values } => {
158            // Pretty-print `name = function(...) end` as `function name(...) end`
159            if targets.len() == 1 && values.len() == 1 {
160                if let Expr::FunctionDef(func) = &values[0] {
161                    let name = match &targets[0] {
162                        Expr::Global(n) => Some(n.clone()),
163                        Expr::Name(n) => Some(n.clone()),
164                        Expr::Field(table, field) => {
165                            // t.method = function(...) -> function t.method(...)
166                            let mut s = String::new();
167                            emit_expr(table, &mut s, 10);
168                            s.push('.');
169                            s.push_str(field);
170                            Some(s)
171                        }
172                        _ => None,
173                    };
174                    if let Some(fname) = name {
175                        write!(out, "{}function {}(", pad, fname).unwrap();
176                        let mut params = func.params.join(", ");
177                        if func.is_vararg {
178                            if !params.is_empty() {
179                                params.push_str(", ");
180                            }
181                            params.push_str("...");
182                        }
183                        out.push_str(&params);
184                        out.push_str(")\n");
185                        emit_block(&func.body, out, indent + 1);
186                        writeln!(out, "{}end", pad).unwrap();
187                        return;
188                    }
189                }
190            }
191            write!(out, "{}", pad).unwrap();
192            emit_expr_list(targets, out);
193            out.push_str(" = ");
194            emit_expr_list(values, out);
195            out.push('\n');
196        }
197        Stat::Call(call) => {
198            write!(out, "{}", pad).unwrap();
199            emit_call(call, out);
200            out.push('\n');
201        }
202        Stat::DoBlock(body) => {
203            writeln!(out, "{}do", pad).unwrap();
204            emit_block(body, out, indent + 1);
205            writeln!(out, "{}end", pad).unwrap();
206        }
207        Stat::While { cond, body } => {
208            write!(out, "{}while ", pad).unwrap();
209            emit_expr(cond, out, 0);
210            out.push_str(" do\n");
211            emit_block(body, out, indent + 1);
212            writeln!(out, "{}end", pad).unwrap();
213        }
214        Stat::Repeat { body, cond } => {
215            writeln!(out, "{}repeat", pad).unwrap();
216            emit_block(body, out, indent + 1);
217            write!(out, "{}until ", pad).unwrap();
218            emit_expr(cond, out, 0);
219            out.push('\n');
220        }
221        Stat::If {
222            cond,
223            then_block,
224            elseif_clauses,
225            else_block,
226        } => {
227            write!(out, "{}if ", pad).unwrap();
228            emit_expr(cond, out, 0);
229            out.push_str(" then\n");
230            emit_block(then_block, out, indent + 1);
231            for (ec, eb) in elseif_clauses {
232                write!(out, "{}elseif ", pad).unwrap();
233                emit_expr(ec, out, 0);
234                out.push_str(" then\n");
235                emit_block(eb, out, indent + 1);
236            }
237            if let Some(eb) = else_block {
238                writeln!(out, "{}else", pad).unwrap();
239                emit_block(eb, out, indent + 1);
240            }
241            writeln!(out, "{}end", pad).unwrap();
242        }
243        Stat::NumericFor {
244            name,
245            start,
246            limit,
247            step,
248            body,
249        } => {
250            write!(out, "{}for {} = ", pad, name).unwrap();
251            emit_expr(start, out, 0);
252            out.push_str(", ");
253            emit_expr(limit, out, 0);
254            if let Some(s) = step {
255                out.push_str(", ");
256                emit_expr(s, out, 0);
257            }
258            out.push_str(" do\n");
259            emit_block(body, out, indent + 1);
260            writeln!(out, "{}end", pad).unwrap();
261        }
262        Stat::GenericFor {
263            names,
264            iterators,
265            body,
266        } => {
267            write!(out, "{}for {} in ", pad, names.join(", ")).unwrap();
268            emit_expr_list(iterators, out);
269            out.push_str(" do\n");
270            emit_block(body, out, indent + 1);
271            writeln!(out, "{}end", pad).unwrap();
272        }
273        Stat::Return(exprs) => {
274            write!(out, "{}return", pad).unwrap();
275            if !exprs.is_empty() {
276                out.push(' ');
277                emit_expr_list(exprs, out);
278            }
279            out.push('\n');
280        }
281        Stat::Break => {
282            writeln!(out, "{}break", pad).unwrap();
283        }
284        Stat::Comment(text) => {
285            writeln!(out, "{}-- {}", pad, text).unwrap();
286        }
287    }
288}
289
290fn emit_expr_list(exprs: &[Expr], out: &mut String) {
291    for (i, e) in exprs.iter().enumerate() {
292        if i > 0 {
293            out.push_str(", ");
294        }
295        emit_expr(e, out, 0);
296    }
297}
298
299/// Emit an expression, handling operator precedence and parenthesization.
300/// `parent_prec` is the precedence of the enclosing operator (0 = no enclosing op).
301fn emit_expr(expr: &Expr, out: &mut String, parent_prec: u8) {
302    match expr {
303        Expr::Nil => out.push_str("nil"),
304        Expr::Bool(true) => out.push_str("true"),
305        Expr::Bool(false) => out.push_str("false"),
306        Expr::Number(n) => emit_number(n, out),
307        Expr::StringLit(s) => emit_string(s, out),
308        Expr::VarArg => out.push_str("..."),
309        Expr::Name(n) => out.push_str(n),
310        Expr::Global(n) => out.push_str(n),
311        Expr::Register(r) => write!(out, "r{}", r).unwrap(),
312        Expr::Upvalue(u) => write!(out, "upval{}", u).unwrap(),
313        Expr::Index(table, key) => {
314            emit_expr(table, out, 10);
315            out.push('[');
316            emit_expr(key, out, 0);
317            out.push(']');
318        }
319        Expr::Field(table, field) => {
320            emit_expr(table, out, 10);
321            out.push('.');
322            out.push_str(field);
323        }
324        Expr::BinOp(op, lhs, rhs) => {
325            let prec = op.precedence();
326            let needs_parens = prec < parent_prec;
327            if needs_parens {
328                out.push('(');
329            }
330            emit_expr(lhs, out, prec);
331            write!(out, " {} ", op.symbol()).unwrap();
332            // Right-associative: right child needs prec+1 to avoid unnecessary parens
333            let rhs_prec = if op.is_right_assoc() { prec } else { prec + 1 };
334            emit_expr(rhs, out, rhs_prec);
335            if needs_parens {
336                out.push(')');
337            }
338        }
339        Expr::UnOp(op, operand) => {
340            let prec = op.precedence();
341            let needs_parens = prec < parent_prec;
342            if needs_parens {
343                out.push('(');
344            }
345            out.push_str(op.symbol());
346            emit_expr(operand, out, prec);
347            if needs_parens {
348                out.push(')');
349            }
350        }
351        Expr::FuncCall(call) => {
352            emit_call(call, out);
353        }
354        Expr::MethodCall(call) => {
355            emit_call(call, out);
356        }
357        Expr::FunctionDef(func) => {
358            emit_function_def(func, out, false);
359        }
360        Expr::Table(fields) => {
361            emit_table(fields, out);
362        }
363    }
364}
365
366fn emit_call(call: &CallExpr, out: &mut String) {
367    emit_expr(&call.func, out, 10);
368    out.push('(');
369    emit_expr_list(&call.args, out);
370    out.push(')');
371}
372
373fn emit_function_def(func: &Function, out: &mut String, _as_stat: bool) {
374    out.push_str("function(");
375    let mut params = func.params.join(", ");
376    if func.is_vararg {
377        if !params.is_empty() {
378            params.push_str(", ");
379        }
380        params.push_str("...");
381    }
382    out.push_str(&params);
383    out.push_str(")\n");
384
385    // Estimate current indent from trailing whitespace
386    let current_indent = count_trailing_indent(out);
387    emit_block(&func.body, out, current_indent + 1);
388
389    let pad = "  ".repeat(current_indent);
390    write!(out, "{}end", pad).unwrap();
391}
392
393fn emit_table(fields: &[TableField], out: &mut String) {
394    if fields.is_empty() {
395        out.push_str("{}");
396        return;
397    }
398
399    // Estimate inline length to decide single-line vs multi-line
400    let current_indent = count_trailing_indent(out);
401    let inline = emit_table_inline(fields);
402    // Use multi-line if: inline is too long, or has nested tables, or many fields
403    let use_multiline = inline.len() > 80 || fields.len() > 4 && inline.len() > 60;
404
405    if !use_multiline {
406        out.push_str(&inline);
407        return;
408    }
409
410    // Multi-line format
411    let inner_pad = "  ".repeat(current_indent + 1);
412    let outer_pad = "  ".repeat(current_indent);
413    out.push_str("{\n");
414    for (i, field) in fields.iter().enumerate() {
415        out.push_str(&inner_pad);
416        match field {
417            TableField::IndexField(key, val) => {
418                out.push('[');
419                emit_expr(key, out, 0);
420                out.push_str("] = ");
421                emit_expr(val, out, 0);
422            }
423            TableField::NameField(name, val) => {
424                out.push_str(name);
425                out.push_str(" = ");
426                emit_expr(val, out, 0);
427            }
428            TableField::Value(val) => {
429                emit_expr(val, out, 0);
430            }
431        }
432        if i + 1 < fields.len() {
433            out.push(',');
434        }
435        out.push('\n');
436    }
437    write!(out, "{}}}", outer_pad).unwrap();
438}
439
440/// Emit a table as a single-line string (for length estimation).
441fn emit_table_inline(fields: &[TableField]) -> String {
442    let mut s = String::new();
443    s.push('{');
444    for (i, field) in fields.iter().enumerate() {
445        if i > 0 {
446            s.push_str(", ");
447        }
448        match field {
449            TableField::IndexField(key, val) => {
450                s.push('[');
451                emit_expr(key, &mut s, 0);
452                s.push_str("] = ");
453                emit_expr(val, &mut s, 0);
454            }
455            TableField::NameField(name, val) => {
456                s.push_str(name);
457                s.push_str(" = ");
458                emit_expr(val, &mut s, 0);
459            }
460            TableField::Value(val) => {
461                emit_expr(val, &mut s, 0);
462            }
463        }
464    }
465    s.push('}');
466    s
467}
468
469fn emit_number(n: &NumLit, out: &mut String) {
470    match n {
471        NumLit::Int(v) => write!(out, "{}", v).unwrap(),
472        NumLit::Float(v) => {
473            if v.fract() == 0.0 && v.abs() < 1e15 {
474                // Emit as integer-looking float if it has no fractional part
475                write!(out, "{}", *v as i64).unwrap();
476            } else {
477                write!(out, "{}", v).unwrap();
478            }
479        }
480    }
481}
482
483/// Emit a string literal with proper escaping.
484/// Supports multi-line string detection, GBK decoding, and non-UTF-8 byte escapes.
485fn emit_string(bytes: &[u8], out: &mut String) {
486    // Try UTF-8 first
487    if let Ok(s) = std::str::from_utf8(bytes) {
488        emit_string_content(s, bytes, out);
489        return;
490    }
491
492    // Try GBK decoding (common in JX3 Lua scripts)
493    let (decoded, _, had_errors) = encoding_rs::GBK.decode(bytes);
494    if !had_errors {
495        emit_string_content(&decoded, bytes, out);
496        return;
497    }
498
499    // Fallback: emit with byte escapes for non-ASCII
500    out.push('"');
501    for &b in bytes {
502        emit_byte_escaped(b, out);
503    }
504    out.push('"');
505}
506
507/// Emit a string that has been successfully decoded to text.
508fn emit_string_content(text: &str, raw: &[u8], out: &mut String) {
509    let has_newlines = text.contains('\n');
510    let has_long_bracket_close = text.contains("]]");
511    let is_printable = text.chars().all(|c| !c.is_control() || c == '\n' || c == '\r' || c == '\t');
512
513    if has_newlines && !has_long_bracket_close && is_printable {
514        // Use [[...]] long string
515        out.push_str("[[");
516        if text.starts_with('\n') {
517            out.push('\n');
518        }
519        out.push_str(text);
520        out.push_str("]]");
521        return;
522    }
523
524    // Use quoted string with escapes
525    out.push('"');
526    for ch in text.chars() {
527        match ch {
528            '\\' => out.push_str("\\\\"),
529            '"' => out.push_str("\\\""),
530            '\n' => out.push_str("\\n"),
531            '\r' => out.push_str("\\r"),
532            '\t' => out.push_str("\\t"),
533            '\0' => out.push_str("\\0"),
534            '\x07' => out.push_str("\\a"),
535            '\x08' => out.push_str("\\b"),
536            '\x0C' => out.push_str("\\f"),
537            '\x0B' => out.push_str("\\v"),
538            c if c >= ' ' && c <= '~' => out.push(c),
539            c if !c.is_control() => out.push(c), // printable Unicode (incl. CJK)
540            c => {
541                // Control character: emit as byte escapes
542                let mut buf = [0u8; 4];
543                let s = c.encode_utf8(&mut buf);
544                for &b in s.as_bytes() {
545                    write!(out, "\\{}", b).unwrap();
546                }
547            }
548        }
549    }
550    out.push('"');
551}
552
553fn emit_byte_escaped(b: u8, out: &mut String) {
554    match b {
555        b'\\' => out.push_str("\\\\"),
556        b'"' => out.push_str("\\\""),
557        b'\n' => out.push_str("\\n"),
558        b'\r' => out.push_str("\\r"),
559        b'\t' => out.push_str("\\t"),
560        b'\0' => out.push_str("\\0"),
561        0x07 => out.push_str("\\a"),
562        0x08 => out.push_str("\\b"),
563        0x0C => out.push_str("\\f"),
564        0x0B => out.push_str("\\v"),
565        0x20..=0x7E => out.push(b as char),
566        _ => {
567            write!(out, "\\{}", b).unwrap();
568        }
569    }
570}
571
572fn count_trailing_indent(s: &str) -> usize {
573    // Count indent level from last newline
574    if let Some(last_nl) = s.rfind('\n') {
575        let after = &s[last_nl + 1..];
576        let spaces = after.len() - after.trim_start().len();
577        spaces / 2
578    } else {
579        0
580    }
581}