use super::expressions::csharpize_expression;
use super::slots::SlotTypes;
use super::syntax::split_top_level_comma;
pub(in crate::decompiler) fn csharpize_statement(line: &str) -> String {
csharpize_statement_typed(line, &SlotTypes::default())
}
pub(in crate::decompiler) fn csharpize_statement_typed(line: &str, types: &SlotTypes) -> String {
let trimmed = line.trim();
if trimmed.is_empty() {
return String::new();
}
if trimmed.starts_with("//") {
return trimmed.to_string();
}
if let Some(stripped) = trimmed.strip_prefix("let ") {
let name = stripped
.split(|c: char| c.is_whitespace() || c == '=')
.next()
.unwrap_or("");
let body = csharpize_expression(stripped);
match types.declaration_type(name) {
Some(ty) => return format!("{ty} {body}"),
None => return format!("var {body}"),
}
}
if let Some(header) = csharpize_for_header(trimmed, Some(types)) {
return header;
}
csharpize_statement_untyped(trimmed)
}
fn csharpize_statement_untyped(trimmed: &str) -> String {
if trimmed == "loop {" {
return "while (true) {".to_string();
}
if let Some(condition) = trimmed
.strip_prefix("if ")
.and_then(|rest| rest.strip_suffix(" {"))
{
return format!("if ({}) {{", csharpize_expression(condition.trim()));
}
if let Some(condition) = trimmed
.strip_prefix("else if ")
.and_then(|rest| rest.strip_suffix(" {"))
{
return format!("else if ({}) {{", csharpize_expression(condition.trim()));
}
if let Some(condition) = trimmed
.strip_prefix("} else if ")
.and_then(|rest| rest.strip_suffix(" {"))
{
return format!("}} else if ({}) {{", csharpize_expression(condition.trim()));
}
if let Some(condition) = trimmed
.strip_prefix("while ")
.and_then(|rest| rest.strip_suffix(" {"))
{
return format!("while ({}) {{", csharpize_expression(condition.trim()));
}
if let Some(header) = csharpize_for_header(trimmed, None) {
return header;
}
if let Some(scrutinee) = trimmed
.strip_prefix("switch ")
.and_then(|rest| rest.strip_suffix(" {"))
{
return format!("switch ({}) {{", csharpize_expression(scrutinee.trim()));
}
if let Some(value) = trimmed
.strip_prefix("case ")
.and_then(|rest| rest.strip_suffix(" {"))
{
return format!("case {}: {{", value.trim());
}
if trimmed == "default {" {
return "default: {".to_string();
}
if let Some(target) = trimmed.strip_prefix("leave ") {
return format!("goto {target}");
}
if let Some(rest) = trimmed
.strip_prefix("throw(")
.and_then(|r| r.strip_suffix(");"))
{
let operand = csharpize_expression(rest);
return format!(
"throw new Exception({});",
wrap_exception_operand_for_csharp(&operand)
);
}
if trimmed == "abort();" {
return "throw new Exception();".to_string();
}
if let Some(rest) = trimmed
.strip_prefix("abort(")
.and_then(|r| r.strip_suffix(");"))
{
let operand = csharpize_expression(rest);
return format!(
"throw new Exception({});",
wrap_exception_operand_for_csharp(&operand)
);
}
if let Some(args) = trimmed
.strip_prefix("assert(")
.and_then(|r| r.strip_suffix(");"))
{
if let Some((cond, message)) = split_top_level_comma(args) {
let message_expr = csharpize_expression(message.trim());
return format!(
"if (!({})) throw new Exception({});",
csharpize_expression(cond.trim()),
wrap_exception_operand_for_csharp(&message_expr)
);
}
return format!(
"if (!({})) throw new Exception();",
csharpize_expression(args.trim())
);
}
csharpize_expression(trimmed)
}
fn csharpize_for_header(trimmed: &str, types: Option<&SlotTypes>) -> Option<String> {
if !trimmed.starts_with("for (") || !trimmed.ends_with(" {") {
return None;
}
let inner = &trimmed[4..trimmed.len() - 2];
let inner = inner.strip_prefix('(').unwrap_or(inner);
let inner = inner.strip_suffix(')').unwrap_or(inner).trim();
let converted = if let Some(stripped) = inner.strip_prefix("let ") {
let name = stripped
.split(|c: char| c.is_whitespace() || c == '=')
.next()
.unwrap_or("");
let ty = types
.and_then(|slot_types| slot_types.declaration_type(name))
.unwrap_or("var");
format!("{ty} {stripped}")
} else {
inner.to_string()
};
Some(format!("for ({}) {{", csharpize_expression(&converted)))
}
fn wrap_exception_operand_for_csharp(operand: &str) -> String {
let trimmed = operand.trim();
if operand_appears_string_typed(trimmed) {
operand.to_string()
} else {
format!("$\"{{{trimmed}}}\"")
}
}
fn operand_appears_string_typed(text: &str) -> bool {
text.contains('"')
}
pub(in crate::decompiler) fn line_is_csharp_terminator(line: &str) -> bool {
let trimmed = line.trim();
trimmed.starts_with("return ")
|| trimmed == "return;"
|| trimmed.starts_with("return;")
|| trimmed.starts_with("throw ")
|| trimmed == "throw;"
|| trimmed.starts_with("goto ")
|| trimmed == "break;"
|| trimmed == "continue;"
}
#[cfg(test)]
mod tests {
use super::{csharpize_statement_typed, SlotTypes};
#[test]
fn typed_for_headers_preserve_inferred_slot_types() {
let types = SlotTypes {
arguments: vec![],
locals: vec!["BigInteger"],
statics: vec![],
};
assert_eq!(
csharpize_statement_typed("for (let loc0 = 0; loc0 < 3; loc0++) {", &types),
"for (BigInteger loc0 = 0; loc0 < 3; loc0++) {"
);
}
}