use super::*;
use crate::instruction::OpCode;
#[test]
fn csharp_translates_loop_to_while_true() {
let script = [
OpCode::Initslot.byte(),
0x01,
0x00,
OpCode::Push0.byte(),
OpCode::Stloc0.byte(),
OpCode::Ldloc0.byte(),
OpCode::Push3.byte(),
OpCode::Lt.byte(),
OpCode::Jmpifnot.byte(),
0x07,
OpCode::Nop.byte(),
OpCode::Ldloc0.byte(),
OpCode::Push1.byte(),
OpCode::Add.byte(),
OpCode::Stloc0.byte(),
OpCode::Jmp.byte(),
0xF4,
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("while (true) {"),
"C# output should translate `loop {{` to `while (true) {{`: {csharp}"
);
assert!(
!csharp.contains("loop {"),
"C# output should not retain the high-level `loop` keyword: {csharp}"
);
}
#[test]
fn csharp_translates_switch_to_idiomatic_c_sharp() {
let script = [
OpCode::Initslot.byte(),
0x01,
0x00,
OpCode::Push1.byte(),
OpCode::Stloc0.byte(),
OpCode::Ldloc0.byte(),
OpCode::Push0.byte(),
OpCode::Equal.byte(),
OpCode::Jmpifnot.byte(),
0x06,
OpCode::Push10.byte(),
OpCode::Stloc0.byte(),
OpCode::Jmp.byte(),
0x0D,
OpCode::Ldloc0.byte(),
OpCode::Push1.byte(),
OpCode::Equal.byte(),
OpCode::Jmpifnot.byte(),
0x06,
OpCode::Push11.byte(),
OpCode::Stloc0.byte(),
OpCode::Jmp.byte(),
0x04,
OpCode::Push12.byte(),
OpCode::Stloc0.byte(),
OpCode::Ldloc0.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("switch (loc0) {"),
"switch scrutinee should be parenthesised: {csharp}"
);
assert!(
csharp.contains("case 0: {"),
"case label should use C# `: {{` form: {csharp}"
);
assert!(
csharp.contains("case 1: {"),
"second case should also use `: {{`: {csharp}"
);
assert!(
csharp.contains("default: {"),
"default label should use `default: {{`: {csharp}"
);
let break_count = csharp.matches("break;").count();
assert!(
break_count >= 3,
"each case (including default) should end with `break;` (found {break_count}): {csharp}"
);
assert!(
!csharp.contains("case 0 {"),
"C# output should not retain the high-level `case X {{` form: {csharp}"
);
assert!(
!csharp.contains("default {"),
"C# output should not retain the high-level `default {{` form: {csharp}"
);
}
#[test]
fn csharp_else_if_chain_uses_parenthesised_conditions() {
let script = [
OpCode::Initslot.byte(),
0x01,
0x00,
OpCode::Push1.byte(),
OpCode::Stloc0.byte(),
OpCode::Ldloc0.byte(),
OpCode::Push0.byte(),
OpCode::Equal.byte(),
OpCode::Jmpifnot.byte(),
0x06,
OpCode::Push10.byte(),
OpCode::Stloc0.byte(),
OpCode::Jmp.byte(),
0x0D,
OpCode::Ldloc0.byte(),
OpCode::Push1.byte(),
OpCode::Equal.byte(),
OpCode::Jmpifnot.byte(),
0x06,
OpCode::Push11.byte(),
OpCode::Stloc0.byte(),
OpCode::Jmp.byte(),
0x04,
OpCode::Push12.byte(),
OpCode::Stloc0.byte(),
OpCode::Ldloc0.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
for line in csharp.lines() {
let trimmed = line.trim();
assert!(
!trimmed.starts_with("else if ") || trimmed.starts_with("else if ("),
"C# else-if must use parenthesised condition: {trimmed}"
);
assert!(
!trimmed.starts_with("} else if ") || trimmed.starts_with("} else if ("),
"C# `}} else if` must use parenthesised condition: {trimmed}"
);
}
}
#[test]
fn csharp_uses_label_style_for_transfer_placeholders() {
let nef_bytes = build_nef(&[
OpCode::Endtry.byte(),
0x06,
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::Add.byte(),
OpCode::Stloc5.byte(),
OpCode::Ret.byte(),
]);
let decompilation = Decompiler::new()
.decompile_bytes_with_manifest(&nef_bytes, None, OutputFormat::All)
.expect("decompile succeeds");
let csharp = decompilation.csharp.as_deref().expect("csharp output");
assert!(
csharp.contains("goto label_0x0006;"),
"C# should normalize leave-transfers to goto label style: {csharp}"
);
assert!(
csharp.contains("label_0x0006:"),
"C# should emit label declaration for transfer targets: {csharp}"
);
assert!(
!csharp.contains("leave label_"),
"C# should not emit non-C# leave statements: {csharp}"
);
assert!(
!csharp.contains("leave_0x"),
"C# should not emit legacy function-style transfer placeholders: {csharp}"
);
}