botserver/src/basic/compiler/blocks/talk.rs

206 lines
7.7 KiB
Rust
Raw Normal View History

use log::{info, trace};
pub fn convert_talk_line_with_substitution(line: &str) -> String {
let mut result = String::new();
let mut chars = line.chars().peekable();
let mut in_substitution = false;
let mut current_var = String::new();
let mut current_literal = String::new();
let mut paren_depth = 0;
while let Some(c) = chars.next() {
match c {
'$' => {
if let Some(&'{') = chars.peek() {
chars.next();
if !current_literal.is_empty() {
// Output the literal with proper quotes
if result.is_empty() {
result.push_str("TALK \"");
} else {
result.push_str(" + \"");
}
let escaped = current_literal.replace('"', "\\\"");
result.push_str(&escaped);
result.push('"');
current_literal.clear();
}
in_substitution = true;
current_var.clear();
paren_depth = 0;
} else {
current_literal.push(c);
}
}
'}' if in_substitution => {
if paren_depth == 0 {
in_substitution = false;
if !current_var.is_empty() {
// If result is empty, we need to start with "TALK "
// but DON'T add opening quote - the variable is not a literal
if result.is_empty() {
result.push_str("TALK ");
} else {
result.push_str(" + ");
}
result.push_str(&current_var);
}
current_var.clear();
} else {
current_var.push(c);
paren_depth -= 1;
}
}
_ if in_substitution => {
if c.is_alphanumeric() || c == '_' || c == '.' || c == '[' || c == ']' || c == ',' || c == '"' {
current_var.push(c);
} else if c == '(' {
current_var.push(c);
paren_depth += 1;
} else if c == ')' && paren_depth > 0 {
current_var.push(c);
paren_depth -= 1;
} else if (c == ':' || c == '=' || c == ' ') && paren_depth == 0 {
// Handle special punctuation that ends a variable context
// Only end substitution if we're not inside parentheses (function call)
in_substitution = false;
if !current_var.is_empty() {
// If result is empty, start with "TALK " (without opening quote)
if result.is_empty() {
result.push_str("TALK ");
} else {
result.push_str(" + ");
}
result.push_str(&current_var);
}
current_var.clear();
current_literal.push(c);
} else if c == ' ' {
// Allow spaces inside function calls
current_var.push(c);
}
// Ignore other invalid characters - they'll be processed as literals
}
'\\' if in_substitution => {
if let Some(&next_char) = chars.peek() {
current_var.push(next_char);
chars.next();
}
}
_ => {
current_literal.push(c);
}
}
}
if !current_literal.is_empty() {
if result.is_empty() {
result.push_str("TALK \"");
} else {
result.push_str(" + \"");
}
let escaped = current_literal.replace('"', "\\\"");
result.push_str(&escaped);
result.push('"');
}
if result.is_empty() {
result = "TALK \"\"".to_string();
}
trace!("Converted TALK line: '{}' → '{}'", line, result);
result
}
pub fn convert_talk_block(lines: &[String]) -> String {
// Convert all lines first
let converted_lines: Vec<String> = lines.iter()
.map(|line| convert_talk_line_with_substitution(line))
.collect();
// Extract content after "TALK " prefix
let line_contents: Vec<String> = converted_lines.iter()
.map(|line| {
if let Some(stripped) = line.strip_prefix("TALK ") {
stripped.trim().to_string()
} else {
line.clone()
}
})
.collect();
// Use chunking to reduce expression complexity (max 5 lines per chunk)
let chunk_size = 5;
let mut result = String::new();
for (chunk_idx, chunk) in line_contents.chunks(chunk_size).enumerate() {
let var_name = format!("__talk_chunk_{}__", chunk_idx);
if chunk.len() == 1 {
result.push_str(&format!("let {} = {};\n", var_name, chunk[0]));
} else {
let mut chunk_expr = chunk[0].clone();
for line in &chunk[1..] {
chunk_expr.push_str(" + \"\\n\" + ");
chunk_expr.push_str(line);
}
result.push_str(&format!("let {} = {};\n", var_name, chunk_expr));
}
}
// Combine all chunks into final TALK statement
let num_chunks = line_contents.len().div_ceil(chunk_size);
if line_contents.is_empty() {
return "TALK \"\";\n".to_string();
} else if num_chunks == 1 {
// Single chunk - use the first variable directly
result.push_str("TALK __talk_chunk_0__;\n");
} else {
// Multiple chunks - need hierarchical chunking to avoid complexity
// Combine chunks in groups of 5 to create intermediate variables
let combine_chunk_size = 5;
let mut chunk_vars: Vec<String> = (0..num_chunks)
.map(|i| format!("__talk_chunk_{}__", i))
.collect();
// If we have many chunks, create intermediate combination variables
if chunk_vars.len() > combine_chunk_size {
let mut level = 0;
while chunk_vars.len() > combine_chunk_size {
let mut new_vars: Vec<String> = Vec::new();
for (idx, sub_chunk) in chunk_vars.chunks(combine_chunk_size).enumerate() {
let var_name = format!("__talk_combined_{}_{}__", level, idx);
if sub_chunk.len() == 1 {
new_vars.push(sub_chunk[0].clone());
} else {
let mut expr = sub_chunk[0].clone();
for var in &sub_chunk[1..] {
expr.push_str(" + \"\\n\" + ");
expr.push_str(var);
}
result.push_str(&format!("let {} = {};\n", var_name, expr));
new_vars.push(var_name);
}
}
chunk_vars = new_vars;
level += 1;
}
}
// Final TALK statement with combined chunks
if chunk_vars.len() == 1 {
result.push_str(&format!("TALK {};\n", chunk_vars[0]));
} else {
let mut expr = chunk_vars[0].clone();
for var in &chunk_vars[1..] {
expr.push_str(" + \"\\n\" + ");
expr.push_str(var);
}
result.push_str(&format!("TALK {};\n", expr));
}
}
result
}