use std::collections::HashSet;
use crate::{Args, Block, BracketDirection, Item, Template};
const CODE_BLOCK_SIZE: usize = 2;
const SAFE_SPACE: usize = 4;
pub fn split_templates(templates: Vec<Template>, max_size: usize) -> Vec<Template> {
let mut function_names = HashSet::new();
for template in &templates {
if let Some(Block::Function { name, .. }) = template.blocks.first() {
function_names.insert(name.clone());
}
}
let mut stack = templates;
let mut result = Vec::new();
while let Some(mut template) = stack.pop() {
if template.blocks.len() * CODE_BLOCK_SIZE + SAFE_SPACE <= max_size {
result.push(template);
continue;
}
let new_function_name = match template.blocks.first() {
Some(Block::Function { args, name }) => {
let mut non_tag_args = args
.0
.iter()
.filter(|(_, item)| !matches!(item, Item::Tag { .. }));
if non_tag_args.next().is_none() {
get_next_function_name(name, &mut function_names)
} else {
panic!("Functions with parameters are not supported for splitting");
}
}
_ => panic!("Non-functions are not supported for splitting"),
};
let last_global_scope = find_last_global_scope(&template.blocks, max_size);
let mut right_template = Template::start_function_hidden(new_function_name.clone());
split_into_existing(
&mut template.blocks,
last_global_scope,
&mut right_template.blocks,
);
template.blocks.push(Block::CallFunction {
args: Args::default(),
func: new_function_name,
});
result.push(template);
stack.push(right_template);
}
result
}
fn find_last_global_scope(blocks: &[Block], max_size: usize) -> usize {
let mut bracket_depth = 0;
let mut last_global_scope = 0;
for (i, block) in blocks.iter().enumerate() {
match block {
Block::IfEntity { .. }
| Block::IfGame { .. }
| Block::IfPlayer { .. }
| Block::IfVariable { .. }
| Block::Else => {
}
Block::Bracket { direction, .. } => match direction {
BracketDirection::Open => bracket_depth += 1,
BracketDirection::Close => bracket_depth -= 1,
},
_ => {
if (i + 1) * CODE_BLOCK_SIZE + SAFE_SPACE > max_size {
break;
} else if bracket_depth == 0 {
last_global_scope = i;
}
}
}
}
last_global_scope
}
fn get_next_function_name(s: &str, function_names: &mut HashSet<String>) -> String {
let (prefix, mut n) = if let Some(pos) = s.rfind("--") {
let prefix = &s[..pos];
let suffix = &s[pos + 2..];
if let Ok(n) = suffix.parse::<usize>() {
(prefix.to_string(), n)
} else {
(s.to_string(), 0)
}
} else {
(s.to_string(), 0)
};
loop {
n += 1;
let new_name = format!("{}--{}", prefix, n);
if !function_names.contains(&new_name) {
function_names.insert(new_name.clone());
return new_name;
}
}
}
fn split_into_existing<T>(vec: &mut Vec<T>, at: usize, target: &mut Vec<T>) {
let drained = vec.drain(at..);
target.extend(drained);
}