use super::GO_BLOCK_RE;
use super::go_blocks::extract_block_parts;
use super::static_regex;
use regex::Regex;
use std::sync::LazyLock;
static LIST_SUBEXPR_RE: LazyLock<Regex> = LazyLock::new(|| {
let item = r#"(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[a-zA-Z_][a-zA-Z0-9_.]*)"#;
let pattern = format!(r"\(list\s+({item}(?:\s+{item})*)\)");
static_regex(&pattern)
});
pub(super) fn preprocess_list_subexpr(template: &str) -> String {
GO_BLOCK_RE
.replace_all(template, |caps: ®ex::Captures| {
let block = &caps[0];
if !block.contains("(list ") {
return block.to_string();
}
LIST_SUBEXPR_RE
.replace_all(block, |lcaps: ®ex::Captures| {
let inner = &lcaps[1];
static ITEM_RE: LazyLock<Regex> = LazyLock::new(|| {
static_regex(
r#""(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[a-zA-Z_][a-zA-Z0-9_.]*"#,
)
});
let items: Vec<&str> = ITEM_RE.find_iter(inner).map(|m| m.as_str()).collect();
format!("[{}]", items.join(", "))
})
.to_string()
})
.to_string()
}
const COMPARISON_OPS: &[(&str, &str)] = &[
("eq", "=="),
("ne", "!="),
("gt", ">"),
("lt", "<"),
("ge", ">="),
("le", "<="),
];
pub(super) fn preprocess_go_builtins(template: &str) -> String {
GO_BLOCK_RE
.replace_all(template, |caps: ®ex::Captures| {
let block = &caps[0];
let (open, inner, close) = extract_block_parts(block);
let needs_rewrite = COMPARISON_OPS.iter().any(|(name, _)| {
let with_space = format!("{} ", name);
inner.contains(&*with_space)
}) || inner.contains("and ")
|| inner.contains("or ")
|| inner.contains("len ");
if !needs_rewrite {
return block.to_string();
}
let rewritten = rewrite_go_builtins_in_expr(inner);
format!("{}{}{}", open, rewritten, close)
})
.to_string()
}
fn rewrite_go_builtins_in_expr(expr: &str) -> String {
let mut result = expr.to_string();
result = rewrite_logical_with_paren_args(&result);
result = rewrite_not_with_paren_comparison(&result);
for (func_name, operator) in COMPARISON_OPS {
result = rewrite_prefix_to_infix(&result, func_name, operator);
}
result = rewrite_prefix_to_infix(&result, "and", "and");
result = rewrite_prefix_to_infix(&result, "or", "or");
result = rewrite_len(&result);
result
}
fn rewrite_logical_with_paren_args(expr: &str) -> String {
static LOGICAL_PAREN_RE: LazyLock<Regex> = LazyLock::new(|| {
let paren_group = r#"\(([^()]*(?:\([^()]*\)[^()]*)*)\)"#;
let pattern = format!(
r"(?:^|(?P<pre>[^a-zA-Z0-9_]))(?P<op>and|or)\s+{}\s+{}",
paren_group, paren_group
);
static_regex(&pattern)
});
LOGICAL_PAREN_RE
.replace_all(expr, |caps: ®ex::Captures| {
let pre = caps.name("pre").map_or("", |m| m.as_str());
let logical_op = caps.name("op").map_or("", |m| m.as_str());
let inner1 = &caps[3]; let inner2 = &caps[4];
let rewritten1 = rewrite_comparison_expr(inner1);
let rewritten2 = rewrite_comparison_expr(inner2);
format!("{}{} {} {}", pre, rewritten1, logical_op, rewritten2)
})
.to_string()
}
fn rewrite_not_with_paren_comparison(expr: &str) -> String {
static NOT_PAREN_RE: LazyLock<Regex> = LazyLock::new(|| {
let paren_group = r#"\(([^()]*)\)"#;
static_regex(&format!(r"not\s+{}", paren_group))
});
NOT_PAREN_RE
.replace_all(expr, |caps: ®ex::Captures| {
let inner = &caps[1];
let rewritten = rewrite_comparison_expr(inner);
if rewritten != inner {
format!("not {}", rewritten)
} else {
caps[0].to_string()
}
})
.to_string()
}
fn rewrite_comparison_expr(expr: &str) -> String {
let mut result = expr.to_string();
for (func_name, operator) in COMPARISON_OPS {
result = rewrite_prefix_to_infix(&result, func_name, operator);
}
result = rewrite_len(&result);
result
}
fn rewrite_prefix_to_infix(expr: &str, func_name: &str, operator: &str) -> String {
use std::collections::HashMap;
use std::sync::Mutex;
static REGEX_CACHE: LazyLock<Mutex<HashMap<String, Regex>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
let arg_pattern = r#"(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\((?:[^()]*(?:\([^()]*\))*[^()]*)\)|[a-zA-Z_][a-zA-Z0-9_.]*|\d+)"#;
let pattern = format!(
r"(?:^|(?P<pre>[^a-zA-Z0-9_])){}\s+(?P<a1>{})\s+(?P<tail>{}(?:\s+{})*)",
regex::escape(func_name),
arg_pattern,
arg_pattern,
arg_pattern
);
let re = {
let mut cache = REGEX_CACHE
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
cache
.entry(func_name.to_string())
.or_insert_with(|| static_regex(&pattern))
.clone()
};
let split_re = {
static SPLIT_RE: LazyLock<Regex> = LazyLock::new(|| {
let arg = r#"(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\((?:[^()]*(?:\([^()]*\))*[^()]*)\)|[a-zA-Z_][a-zA-Z0-9_.]*|\d+)"#;
static_regex(arg)
});
&*SPLIT_RE
};
re.replace_all(expr, |caps: ®ex::Captures| {
let pre = caps.name("pre").map_or("", |m| m.as_str());
let arg1 = caps.name("a1").map_or("", |m| m.as_str());
let tail = caps.name("tail").map_or("", |m| m.as_str());
let rest_args: Vec<&str> = split_re.find_iter(tail).map(|m| m.as_str()).collect();
if rest_args.len() == 1 {
format!("{}{} {} {}", pre, arg1, operator, rest_args[0])
} else {
let parts: Vec<String> = rest_args
.iter()
.map(|a| format!("{} {} {}", arg1, operator, a))
.collect();
format!("{}{}", pre, parts.join(" or "))
}
})
.to_string()
}
fn rewrite_len(expr: &str) -> String {
static LEN_RE: LazyLock<Regex> = LazyLock::new(|| {
let arg_pattern =
r#"(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\([^()]*\)|[a-zA-Z_][a-zA-Z0-9_.]*)"#;
let pattern = format!(
r"(?:^|(?P<pre>[^a-zA-Z0-9_]))len\s+(?P<arg>{})",
arg_pattern
);
static_regex(&pattern)
});
LEN_RE
.replace_all(expr, |caps: ®ex::Captures| {
let pre = caps.name("pre").map_or("", |m| m.as_str());
let arg = caps.name("arg").map_or("", |m| m.as_str());
format!("{}{} | length", pre, arg)
})
.to_string()
}