use super::expressions::csharpize_expression;
use super::syntax::{find_matching_close_paren, split_top_level_args};
pub(super) struct HelperRule {
pub(super) replacement: &'static str,
pub(super) needle_len: usize,
pub(super) int_cast_args: &'static [usize],
}
pub(super) struct HelperRewrite {
pub(super) body: String,
pub(super) consumed: usize,
}
pub(super) fn match_collection_constructor(rest: &str) -> Option<(&'static str, usize)> {
const TABLE: &[(&str, &str)] = &[
("Map()", "new Map<object, object>()"),
("Struct()", "new Struct()"),
("[]", "new object[0]"),
];
for (needle, replacement) in TABLE {
if rest.starts_with(needle) {
return Some((replacement, needle.len()));
}
}
None
}
pub(super) fn match_unary_pattern(rest: &str) -> Option<HelperRewrite> {
if let Some(rendered) = match_simple_unary(rest, "is_null(", |arg| format!("({arg} is null)")) {
return Some(rendered);
}
if let Some(rendered) = match_simple_unary(rest, "new_buffer(", |arg| {
format!("new byte[{}]", wrap_int_cast_unless_literal(arg))
}) {
return Some(rendered);
}
if let Some(rendered) = match_simple_unary(rest, "new_array(", |arg| {
format!("new object[{}]", wrap_int_cast_unless_literal(arg))
}) {
return Some(rendered);
}
if let Some(rendered) = match_simple_unary(rest, "clear_items(", |arg| format!("{arg}.Clear()"))
{
return Some(rendered);
}
if let Some(rendered) = match_simple_unary(rest, "keys(", |arg| format!("{arg}.Keys")) {
return Some(rendered);
}
if let Some(rendered) = match_simple_unary(rest, "values(", |arg| format!("{arg}.Values")) {
return Some(rendered);
}
if let Some(rendered) =
match_simple_unary(rest, "reverse_items(", |arg| format!("{arg}.Reverse()"))
{
return Some(rendered);
}
for (needle, method) in METHOD_CALL_TABLE {
if let Some(rendered) = match_method_call(rest, needle, method) {
return Some(rendered);
}
}
for (needle, csharp_type) in CONVERT_TYPED_TABLE {
if let Some(rendered) =
match_simple_unary(rest, needle, |arg| format!("({csharp_type})({arg})"))
{
return Some(rendered);
}
}
for (needle, csharp_type) in IS_TYPE_TYPED_TABLE {
if let Some(rendered) =
match_simple_unary(rest, needle, |arg| format!("({arg} is {csharp_type})"))
{
return Some(rendered);
}
}
None
}
const METHOD_CALL_TABLE: &[(&str, &str)] = &[
("remove_item(", "Remove"),
("append(", "Add"),
("has_key(", "ContainsKey"),
];
const CONVERT_TYPED_TABLE: &[(&str, &str)] = &[
("convert_to_bool(", "bool"),
("convert_to_integer(", "BigInteger"),
("convert_to_bytestring(", "ByteString"),
("convert_to_buffer(", "byte[]"),
];
const IS_TYPE_TYPED_TABLE: &[(&str, &str)] = &[
("is_type_bool(", "bool"),
("is_type_integer(", "BigInteger"),
("is_type_bytestring(", "ByteString"),
("is_type_buffer(", "byte[]"),
];
fn wrap_int_cast_unless_literal(arg: &str) -> String {
let trimmed = arg.trim();
let is_decimal_literal = !trimmed.is_empty()
&& trimmed
.strip_prefix('-')
.unwrap_or(trimmed)
.chars()
.all(|ch| ch.is_ascii_digit());
if is_decimal_literal {
trimmed.to_string()
} else {
format!("(int)({trimmed})")
}
}
fn match_simple_unary(
rest: &str,
needle: &str,
render: impl FnOnce(&str) -> String,
) -> Option<HelperRewrite> {
if !rest.starts_with(needle) {
return None;
}
let after_open = &rest[needle.len()..];
let close_index = find_matching_close_paren(after_open.as_bytes())?;
let arg = after_open[..close_index].trim();
Some(HelperRewrite {
body: render(arg),
consumed: needle.len() + close_index + 1,
})
}
fn match_method_call(rest: &str, needle: &str, method_name: &str) -> Option<HelperRewrite> {
if !rest.starts_with(needle) {
return None;
}
let after_open = &rest[needle.len()..];
let close_index = find_matching_close_paren(after_open.as_bytes())?;
let args = &after_open[..close_index];
let parts = split_top_level_args(args);
if parts.is_empty() {
return None;
}
let receiver = parts[0].trim();
let rest_args = parts[1..]
.iter()
.map(|p| p.trim())
.collect::<Vec<_>>()
.join(", ");
Some(HelperRewrite {
body: format!("{receiver}.{method_name}({rest_args})"),
consumed: needle.len() + close_index + 1,
})
}
pub(super) fn match_numeric_helper(bytes: &[u8]) -> Option<HelperRule> {
const TABLE: &[(&[u8], &str, &[usize])] = &[
(b"abs(", "BigInteger.Abs", &[]),
(b"min(", "BigInteger.Min", &[]),
(b"max(", "BigInteger.Max", &[]),
(b"pow(", "BigInteger.Pow", &[1]),
(b"modpow(", "BigInteger.ModPow", &[]),
(b"sign(", "Helper.Sign", &[]),
(b"sqrt(", "Helper.Sqrt", &[]),
(b"modmul(", "Helper.ModMul", &[]),
(b"within(", "Helper.Within", &[]),
(b"left(", "Helper.Left", &[1]),
(b"right(", "Helper.Right", &[1]),
(b"substr(", "Helper.Substr", &[1, 2]),
];
for (needle, replacement, int_cast_args) in TABLE {
if bytes.starts_with(needle) {
return Some(HelperRule {
replacement,
needle_len: needle.len(),
int_cast_args,
});
}
}
None
}
pub(super) fn format_helper_with_casts(rest: &str, rule: &HelperRule) -> Option<HelperRewrite> {
let after_open = &rest[rule.needle_len..];
let close_index = find_matching_close_paren(after_open.as_bytes())?;
let args = &after_open[..close_index];
let parts = split_top_level_args(args);
let mut rendered = Vec::with_capacity(parts.len());
for (index, part) in parts.iter().enumerate() {
let normalized = csharpize_expression(part.trim());
if rule.int_cast_args.contains(&index) {
rendered.push(wrap_int_cast_unless_literal(&normalized));
} else {
rendered.push(normalized);
}
}
let body = format!("{}({})", rule.replacement, rendered.join(", "));
Some(HelperRewrite {
consumed: rule.needle_len + close_index + 1,
body,
})
}