dfwasm_template/
splitter.rs

1use std::collections::HashSet;
2
3use crate::{Args, Block, BracketDirection, Item, Template};
4
5/// The amount of blocks in a code block.
6const CODE_BLOCK_SIZE: usize = 2;
7const SAFE_SPACE: usize = 4;
8
9/// Splits a list of templates into smaller templates if they are too large.
10///
11/// Warning: There are a few assumptions made in this function:
12/// - The function depth of individual blocks do not matter.
13/// - Global scope is not overly used (i.e. if statements should be a few blocks maximum).
14/// - Non-function templates cannot be split.
15pub fn split_templates(templates: Vec<Template>, max_size: usize) -> Vec<Template> {
16    let mut function_names = HashSet::new();
17
18    for template in &templates {
19        if let Some(Block::Function { name, .. }) = template.blocks.first() {
20            function_names.insert(name.clone());
21        }
22    }
23
24    let mut stack = templates;
25    let mut result = Vec::new();
26
27    while let Some(mut template) = stack.pop() {
28        // If the template is small enough, add it to the result
29        if template.blocks.len() * CODE_BLOCK_SIZE + SAFE_SPACE <= max_size {
30            result.push(template);
31            continue;
32        }
33
34        // Get the new name of the template
35        let new_function_name = match template.blocks.first() {
36            Some(Block::Function { args, name }) => {
37                let mut non_tag_args = args
38                    .0
39                    .iter()
40                    .filter(|(_, item)| !matches!(item, Item::Tag { .. }));
41
42                if non_tag_args.next().is_none() {
43                    get_next_function_name(name, &mut function_names)
44                } else {
45                    panic!("Functions with parameters are not supported for splitting");
46                }
47            }
48            _ => panic!("Non-functions are not supported for splitting"),
49        };
50
51        // Calculate the last global scope
52        let last_global_scope = find_last_global_scope(&template.blocks, max_size);
53
54        // Create a new
55        let mut right_template = Template::start_function_hidden(new_function_name.clone());
56
57        // Split the blocks at the last global scope
58        split_into_existing(
59            &mut template.blocks,
60            last_global_scope,
61            &mut right_template.blocks,
62        );
63
64        // Insert the new function call at the end of the left half
65        template.blocks.push(Block::CallFunction {
66            args: Args::default(),
67            func: new_function_name,
68        });
69
70        // The left half is now a valid template less than max_size, so we can add it to the result.
71        result.push(template);
72
73        // Now verify that the right half is also not too large
74        stack.push(right_template);
75    }
76
77    result
78}
79
80/// Finds the last global scope less than `max_size` in the given blocks.
81fn find_last_global_scope(blocks: &[Block], max_size: usize) -> usize {
82    let mut bracket_depth = 0;
83    let mut last_global_scope = 0;
84
85    for (i, block) in blocks.iter().enumerate() {
86        match block {
87            Block::IfEntity { .. }
88            | Block::IfGame { .. }
89            | Block::IfPlayer { .. }
90            | Block::IfVariable { .. }
91            | Block::Else => {
92                // Skip these blocks, cannot split here
93            }
94            Block::Bracket { direction, .. } => match direction {
95                BracketDirection::Open => bracket_depth += 1,
96                BracketDirection::Close => bracket_depth -= 1,
97            },
98            _ => {
99                if (i + 1) * CODE_BLOCK_SIZE + SAFE_SPACE > max_size {
100                    // We have reached the max size, so stop searching
101                    break;
102                } else if bracket_depth == 0 {
103                    // We are at the global scope, so we can split here
104                    last_global_scope = i;
105                }
106            }
107        }
108    }
109
110    last_global_scope
111}
112
113/// Given a function name, returns a new unique function name by incrementing the number at the end of the name.
114fn get_next_function_name(s: &str, function_names: &mut HashSet<String>) -> String {
115    let (prefix, mut n) = if let Some(pos) = s.rfind("--") {
116        let prefix = &s[..pos];
117        let suffix = &s[pos + 2..];
118
119        if let Ok(n) = suffix.parse::<usize>() {
120            (prefix.to_string(), n)
121        } else {
122            (s.to_string(), 0)
123        }
124    } else {
125        (s.to_string(), 0)
126    };
127
128    loop {
129        n += 1;
130        let new_name = format!("{}--{}", prefix, n);
131        if !function_names.contains(&new_name) {
132            function_names.insert(new_name.clone());
133            return new_name;
134        }
135    }
136}
137
138/// Splits a vector into two parts at the given index, moving the second part into the target vector.
139fn split_into_existing<T>(vec: &mut Vec<T>, at: usize, target: &mut Vec<T>) {
140    let drained = vec.drain(at..);
141    target.extend(drained);
142}