hlbc_decompiler/
fmt.rs

1use std::fmt;
2use std::fmt::{Display, Formatter};
3
4use hlbc::fmt::{BytecodeFmt, EnhancedFmt};
5use hlbc::types::{Function, RefField, Type};
6use hlbc::Str;
7use hlbc::{Bytecode, Resolve};
8
9use crate::ast::{Class, Constant, ConstructorCall, Expr, Method, Operation, Statement};
10
11const INDENT: &'static str = "                                                                ";
12
13#[derive(Clone)]
14pub struct FormatOptions {
15    indent: &'static str,
16    inc_indent: usize,
17}
18
19impl FormatOptions {
20    pub fn new(inc_indent: usize) -> Self {
21        Self {
22            indent: "",
23            inc_indent,
24        }
25    }
26
27    pub fn inc_nesting(&self) -> Self {
28        FormatOptions {
29            indent: &INDENT[..self.indent.len() + self.inc_indent],
30            ..*self
31        }
32    }
33}
34
35impl Display for FormatOptions {
36    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
37        write!(f, "{}", self.indent)
38    }
39}
40
41fn to_haxe_type<'a>(ty: &Type, ctx: &'a Bytecode) -> impl Display + 'a {
42    use crate::Type::*;
43    match ty {
44        Void => Str::from_static("Void"),
45        I32 => Str::from_static("Int"),
46        F64 => Str::from_static("Float"),
47        Bool => Str::from_static("Bool"),
48        Bytes => Str::from_static("hl.Bytes"),
49        Dyn => Str::from_static("Dynamic"),
50        Fun(_) => Str::from_static("Function"),
51        Obj(obj) => ctx.get(obj.name),
52        _ => Str::from_static("other"),
53    }
54}
55
56impl Class {
57    pub fn display<'a>(&'a self, ctx: &'a Bytecode, opts: &'a FormatOptions) -> impl Display + 'a {
58        let new_opts = opts.inc_nesting();
59        fmtools::fmt! { move
60            {opts}"class "{self.name} if let Some(parent) = self.parent.as_ref() { " extends "{parent} } " {\n"
61            for f in &self.fields {
62                {new_opts} if f.static_ { "static " } "var "{f.name}": "{to_haxe_type(&ctx[f.ty], ctx)}";\n"
63            }
64            for m in &self.methods {
65                "\n"
66                {m.display(ctx, &new_opts)}
67            }
68            {opts}"}"
69        }
70    }
71}
72
73impl Method {
74    pub fn display<'a>(&'a self, ctx: &'a Bytecode, opts: &'a FormatOptions) -> impl Display + 'a {
75        let new_opts = opts.inc_nesting();
76        let fun = self.fun.as_fn(ctx).unwrap();
77        fmtools::fmt! { move
78            {opts} if self.static_ { "static " } if self.dynamic { "dynamic " }
79            "function "{fun.name(ctx)}"("
80            {fmtools::join(", ", fun.args(ctx).iter().enumerate().skip(if self.static_ { 0 } else { 1 })
81                .map(move |(i, arg)| fmtools::fmt! {move
82                    {fun.arg_name(ctx, i).unwrap_or(Str::from("_"))}": "{to_haxe_type(&ctx[*arg], ctx)}
83                }))}
84            ")" if !fun.ty(ctx).ret.is_void() { ": "{to_haxe_type(fun.ret(ctx), ctx)} } " {"
85
86            if self.statements.is_empty() {
87                "}"
88            } else {
89                "\n"
90                for stmt in &self.statements {
91                    {new_opts}{stmt.display(&new_opts, ctx, fun)}"\n"
92                }
93                {opts}"}"
94            }
95            "\n"
96        }
97    }
98}
99
100impl Constant {
101    fn fmt(&self, f: &mut Formatter, code: &Bytecode) -> fmt::Result {
102        use Constant::*;
103        match *self {
104            InlineInt(c) => Display::fmt(&c, f),
105            Int(c) => EnhancedFmt.fmt_refint(f, code, c),
106            Float(c) => EnhancedFmt.fmt_reffloat(f, code, c),
107            String(c) => {
108                write!(f, "\"{}\"", code[c])
109            }
110            Bool(c) => Display::fmt(&c, f),
111            Null => f.write_str("null"),
112            This => f.write_str("this"),
113        }
114    }
115}
116
117impl Operation {
118    pub fn display<'a>(
119        &'a self,
120        indent: &'a FormatOptions,
121        code: &'a Bytecode,
122        f: &'a Function,
123    ) -> impl Display + 'a {
124        use Operation::*;
125        macro_rules! disp {
126            ($e:ident) => {
127                $e.display(indent, code, f)
128            };
129        }
130        fmtools::fmt! { move
131            match self {
132                Add(e1, e2) => {{disp!(e1)}" + "{disp!(e2)}}
133                Sub(e1, e2) => {{disp!(e1)}" - "{disp!(e2)}}
134                Mul(e1, e2) => {{disp!(e1)}" * "{disp!(e2)}}
135                Div(e1, e2) => {{disp!(e1)}" / "{disp!(e2)}}
136                Mod(e1, e2) => {{disp!(e1)}" % "{disp!(e2)}}
137                Shl(e1, e2) => {{disp!(e1)}" << "{disp!(e2)}}
138                Shr(e1, e2) => {{disp!(e1)}" >> "{disp!(e2)}}
139                And(e1, e2) => {{disp!(e1)}" && "{disp!(e2)}}
140                Or(e1, e2) => {{disp!(e1)}" || "{disp!(e2)}}
141                Xor(e1, e2) => {{disp!(e1)}" ^ "{disp!(e2)}}
142                Neg(expr) => {"-"{disp!(expr)}}
143                Not(expr) => {"!"{disp!(expr)}}
144                Incr(expr) => {{disp!(expr)}"++"}
145                Decr(expr) => {{disp!(expr)}"--"}
146                Eq(e1, e2) => {{disp!(e1)}" == "{disp!(e2)}}
147                NotEq(e1, e2) => {{disp!(e1)}" != "{disp!(e2)}}
148                Gt(e1, e2) => {{disp!(e1)}" > "{disp!(e2)}}
149                Gte(e1, e2) => {{disp!(e1)}" >= "{disp!(e2)}}
150                Lt(e1, e2) => {{disp!(e1)}" < "{disp!(e2)}}
151                Lte(e1, e2) => {{disp!(e1)}" <= "{disp!(e2)}}
152            }
153        }
154    }
155}
156
157impl Expr {
158    pub fn display<'a>(
159        &'a self,
160        indent: &'a FormatOptions,
161        code: &'a Bytecode,
162        f: &'a Function,
163    ) -> impl Display + 'a {
164        macro_rules! disp {
165            ($e:expr) => {
166                $e.display(indent, code, f)
167            };
168        }
169        fmtools::fmt! { move
170            match self {
171                Expr::Anonymous(ty, values) => match &code[*ty] {
172                    Type::Virtual { fields } => {
173                        "{"{ fmtools::join(", ", fields
174                            .iter()
175                            .enumerate()
176                            .map(|(i, f)| {
177                                fmtools::fmt! { move
178                                    {f.name(code)}": "{disp!(values.get(&RefField(i)).unwrap())}
179                                }
180                            })) }"}"
181                    }
182                    _ => "[invalid anonymous type]",
183                },
184                Expr::Array(array, index) => {
185                    {disp!(array)}"["{disp!(index)}"]"
186                }
187                Expr::Call(call) => {
188                    {disp!(call.fun)}"("{fmtools::join(", ", call.args.iter().map(|e| disp!(e)))}")"
189                }
190                Expr::Constant(c) => {|f| c.fmt(f, code)?;},
191                Expr::Constructor(ConstructorCall { ty, args }) => {
192                    "new "{ty.display::<EnhancedFmt>(code)}"("{fmtools::join(", ", args.iter().map(|e| disp!(e)))}")"
193                }
194                Expr::Closure(f, stmts) => {
195                    let fun = f.as_fn(code).unwrap();
196                    "("{fmtools::join(", ", fun.ty(code).args.iter().enumerate().map(move |(i, arg)|
197                        fmtools::fmt! { move
198                            {fun.arg_name(code, i).unwrap_or(Str::from("_"))}": "{to_haxe_type(&code[*arg], code)}
199                        }
200                    ))}") -> {\n"
201                    let indent2 = indent.inc_nesting();
202                    for stmt in stmts {
203                        {indent2}{stmt.display(&indent2, code, fun)}"\n"
204                    }
205                    {indent}"}"
206                }
207                Expr::EnumConstr(ty, constr, args) => {
208                    {constr.display::<EnhancedFmt>(code, &code[*ty])}"("{fmtools::join(", ", args.iter().map(|e| disp!(e)))}")"
209                }
210                Expr::Field(receiver, name) => {
211                    {disp!(receiver)}"."{name}
212                }
213                Expr::FunRef(fun) => {{fun.name(code)}},
214                Expr::IfElse { cond, if_, else_ } => {
215                    "if ("{disp!(cond)}") {\n"
216                    let indent2 = indent.inc_nesting();
217                    for stmt in if_ {
218                        {indent2}{stmt.display(&indent2, code, f)}"\n"
219                    }
220                    {indent}"} else {\n"
221                    for stmt in else_ {
222                        {indent2}{stmt.display(&indent2, code, f)}"\n"
223                    }
224                    {indent}"}"
225                }
226                Expr::Op(op) => {{disp!(op)}},
227                Expr::Unknown(msg) => {
228                     "["{msg}"]"
229                }
230                Expr::Variable(x, name) => {{
231                    if let Some(name) = name {
232                        name.clone()
233                    } else {
234                        Str::from(x.to_string())
235                    }
236                }}
237            }
238        }
239    }
240}
241
242impl Statement {
243    pub fn display<'a>(
244        &'a self,
245        indent: &'a FormatOptions,
246        code: &'a Bytecode,
247        f: &'a Function,
248    ) -> impl Display + 'a {
249        macro_rules! disp {
250            ($e:expr) => {
251                $e.display(indent, code, f)
252            };
253        }
254        fmtools::fmt! { move
255            match self {
256                Statement::Assign {
257                    declaration,
258                    variable,
259                    assign,
260                } => {
261                    if *declaration { "var " } else { "" }{disp!(variable)}" = "{disp!(assign)}";"
262                }
263                Statement::ExprStatement(expr) => {
264                    {disp!(expr)}";"
265                }
266                Statement::Return(expr) => {
267                    "return" if let Some(e) = expr { " "{disp!(e)} } ";"
268                }
269                Statement::IfElse { cond, if_, else_ } => {
270                    "if ("{disp!(cond)}") {\n"
271                    let indent2 = indent.inc_nesting();
272                    for stmt in if_ {
273                        {indent2}{stmt.display(&indent2, code, f)}"\n"
274                    }
275                    {indent}"}"
276                    if !else_.is_empty() {
277                        " else {\n"
278                        for stmt in else_ {
279                            {indent2}{stmt.display(&indent2, code, f)}"\n"
280                        }
281                        {indent}"}"
282                    }
283                }
284                Statement::Switch {arg, default, cases} => {
285                    "switch ("{disp!(arg)}") {\n"
286                    let indent2 = indent.inc_nesting();
287                    let indent3 = indent2.inc_nesting();
288                    if !default.is_empty() {
289                        {indent2}"default:\n"
290                        for stmt in default {
291                            {indent3}{stmt.display(&indent3, code, f)}"\n"
292                        }
293                    }
294                    for (pattern, stmts) in cases {
295                        {indent2}"case "{disp!(pattern)}":\n"
296                        for stmt in stmts {
297                            {indent3}{stmt.display(&indent3, code, f)}"\n"
298                        }
299                    }
300                    {indent}"}"
301                }
302                Statement::While { cond, stmts } => {
303                    "while ("{disp!(cond)}") {\n"
304                    let indent2 = indent.inc_nesting();
305                    for stmt in stmts {
306                        {indent2}{stmt.display(&indent2, code, f)}"\n"
307                    }
308                    {indent}"}"
309                }
310                Statement::Break => {
311                    "break;"
312                }
313                Statement::Continue => {
314                    "continue;"
315                }
316                Statement::Throw(exc) => {
317                    "throw "{disp!(exc)}
318                }
319                Statement::Try { stmts } => {
320                    "try {\n"
321                    let indent2 = indent.inc_nesting();
322                    for stmt in stmts {
323                        {indent2}{stmt.display(&indent2, code, f)}"\n"
324                    }
325                    {indent}"}"
326                }
327                Statement::Catch { stmts } => {
328                    "catch () {\n"
329                    let indent2 = indent.inc_nesting();
330                    for stmt in stmts {
331                        {indent2}{stmt.display(&indent2, code, f)}"\n"
332                    }
333                    {indent}"}"
334                }
335                Statement::Comment(comment) => {
336                    "// "{comment}
337                }
338            }
339        }
340    }
341}