1use std::collections::{HashMap, HashSet};
2
3use const_format::concatcp;
4use fancy_regex::{Captures, Regex};
5
6use crate::const_values::ConstantValue;
7
8const SNAKE_CASE_PATTERN: &str = r"([A-Z][A-Z0-9]*[_[A-Z0-9]+]+)";
9const ANY_CHAR_PATTERN: &str = r"([^}]*)";
10const IMPORT_STATEMENT_PATTERN: &str = r"\s*use\s+.*::([^;]+);";
11const CONST_BLOCK_BEGIN: &str =
12 " // This line is used for generating constants DO NOT REMOVE!\n";
13const CONST_BLOCK_END: &str = " // End of generating constants!\n\n";
14const IMPORT_BLOCK: &str = r"((\n\s*use\s+[^\n]+;)|(\s+\/\/.*))+\n";
15
16fn warn_const_unused(consts: &HashSet<String>, table: &HashMap<String, ConstantValue>) {
17 let table_keys: Vec<_> = table.keys().collect();
18 let consts_unused: Vec<_> = table_keys
19 .into_iter()
20 .filter(|item| !consts.contains(*item))
21 .collect();
22 for e in consts_unused {
23 println!("Unused: {}", e);
24 }
25}
26
27fn create_const_block(consts: &HashSet<String>, table: &HashMap<String, ConstantValue>) -> String {
28 if consts.is_empty() {
29 return "".to_string();
30 }
31
32 let mut consts: Vec<_> = consts.iter().collect();
33 consts.sort();
34
35 let mut result = CONST_BLOCK_BEGIN.to_string();
36 for c in consts {
37 let info = table.get(c).unwrap();
38 if let Some(comment) = info.comment.clone() {
39 result += format!(" // {}\n", comment).as_str();
40 }
41 result += format!(" const {}: {} = {};\n", c, info.r#type, info.value).as_str();
42 }
43 result += CONST_BLOCK_END;
44 result
45}
46
47fn remove_import(file_content: &str, table: &HashMap<String, ConstantValue>) -> String {
48 Regex::new(IMPORT_STATEMENT_PATTERN)
49 .unwrap()
50 .replace_all(file_content, |cap: &Captures| {
51 let mut new_statement = cap[1].trim().to_string();
52 if Regex::new(SNAKE_CASE_PATTERN)
53 .unwrap()
54 .is_match(&new_statement)
55 .unwrap()
56 {
57 if new_statement.starts_with('{') {
58 new_statement = Regex::new(&format!(r"{}\s*,\s*", SNAKE_CASE_PATTERN))
60 .unwrap()
61 .replace_all(&new_statement, |import_caps: &Captures| {
62 let const_name = import_caps[1].to_string();
63 if table.contains_key(&const_name) {
64 "".to_string()
65 } else {
66 import_caps[0].to_string()
67 }
68 })
69 .to_string();
70 new_statement = Regex::new(&format!(r",?\s*{}\s*", SNAKE_CASE_PATTERN))
72 .unwrap()
73 .replace_all(&new_statement, |import_caps: &Captures| {
74 let const_name = import_caps[1].to_string();
75 if table.contains_key(&const_name) {
76 "".to_string()
77 } else {
78 import_caps[0].to_string()
79 }
80 })
81 .to_string();
82 } else {
83 new_statement = "".to_string();
84 };
85 if new_statement.ends_with("{}") {
86 new_statement = "".to_string();
87 }
88 if new_statement.is_empty() {
89 "".to_string()
90 } else {
91 cap[0]
92 .replace(cap[1].to_string().as_str(), new_statement.as_ref())
93 .to_string()
94 }
95 } else {
96 cap[0].to_string()
97 }
98 })
99 .to_string()
100}
101
102pub fn get_import_regex(table: &HashMap<String, ConstantValue>) -> String {
103 let any_character = r"[a-zA-Z0-9_:,{}()\s]*";
104 format!(
105 r" use\s+{}({}){};\n",
106 any_character,
107 get_const_regex(table),
108 any_character,
109 )
110 .to_string()
111}
112
113pub fn get_const_funcs_regex(table: &HashMap<String, ConstantValue>) -> String {
114 format!(
115 "{}({}){}{}{}",
116 r"\s*public fun ",
117 get_const_regex(table),
118 r"\(\)\s*:\s*(\w+)\s*\{\s*",
119 ANY_CHAR_PATTERN,
120 r"*\}"
121 )
122 .to_string()
123}
124
125pub fn get_const_regex(table: &HashMap<String, ConstantValue>) -> String {
126 let mut result = table.iter().map(|(k, _)| k).collect::<Vec<_>>();
127 result.sort();
128 result.reverse();
129 let result = result
130 .into_iter()
131 .fold("".to_string(), |acc, k| format!("{}({})|", acc, k));
132 result[0..result.len() - 1].to_string()
133}
134
135pub fn gen_consts(file_content: &str, table: &HashMap<String, ConstantValue>) -> String {
136 let mut declared_funcs = vec![];
138 let mut result = Regex::new(&get_const_funcs_regex(table))
139 .unwrap()
140 .replace_all(file_content, |cap: &Captures| {
141 declared_funcs.push(cap[1].to_string());
142 ""
143 })
144 .to_string();
145
146 result = Regex::new(concatcp!(
148 CONST_BLOCK_BEGIN,
149 ANY_CHAR_PATTERN,
150 CONST_BLOCK_END
151 ))
152 .unwrap()
153 .replace_all(&result, |_: &Captures| {
154 format!("{}{}", CONST_BLOCK_BEGIN, CONST_BLOCK_END)
155 })
156 .to_string();
157
158 let mut consts = HashSet::<String>::new();
160 let const_regex_func_calls = format!(r"(.*?[^\w]*({}))(\(\))?", get_const_regex(table));
161 let comment_regex = Regex::new(r"^\s+\/\/").unwrap();
162 result = Regex::new(&const_regex_func_calls)
163 .unwrap()
164 .replace_all(&result, |caps: &Captures| {
165 let whole = caps[0].to_string();
166 if comment_regex.is_match(&whole).unwrap() {
167 return whole;
168 }
169 let wrapped_name = caps[1].to_string();
170 let name = caps[2].to_string();
171
172 if table.contains_key(&name) {
173 consts.insert(name.clone());
174 wrapped_name
175 } else {
176 whole
177 }
178 })
179 .to_string();
180
181 result = remove_import(&result, table);
182
183 if consts.is_empty() {
185 return result;
186 }
187
188 let const_block = create_const_block(&consts, table);
189 warn_const_unused(&consts, table);
190 if result.contains(format!("{}{}", CONST_BLOCK_BEGIN, CONST_BLOCK_END).as_str()) {
192 return result.replace(
193 format!("{}{}", CONST_BLOCK_BEGIN, CONST_BLOCK_END).as_str(),
194 &const_block,
195 );
196 }
197
198 let mut contain_import_block = false;
199 result = Regex::new(IMPORT_BLOCK)
200 .unwrap()
201 .replace(&result, |caps: &Captures| {
202 contain_import_block = true;
203 let whole = caps[0].to_string();
204 whole + "\n" + &const_block + "\n"
205 })
206 .to_string();
207
208 if contain_import_block {
209 return result;
210 }
211
212 let mut first_left_cb = result.find('{').unwrap();
214 while result.as_bytes()[first_left_cb] != u8::try_from('\n').unwrap() {
215 first_left_cb += 1;
216 }
217 result.insert_str(first_left_cb + 1, &const_block);
218
219 result
220}
221
222#[cfg(test)]
223mod test {
224 use crate::const_values::get_constant_values;
225 use crate::gen_const::gen_consts;
226
227 #[test]
228 fn test_gen_consts_sample1() {
229 let file_content = include_str!("./test_files/sample1_input.move");
230 let refined_content = include_str!("./test_files/sample1_expect.move");
231 let output = gen_consts(file_content, &get_constant_values());
232 assert_eq!(output, refined_content, "failed");
233 }
234 #[test]
235 fn test_gen_consts_sample2() {
236 let file_content = include_str!("./test_files/sample2_input.move");
237 let refined_content = include_str!("./test_files/sample2_expect.move");
238 let output = gen_consts(file_content, &get_constant_values());
239 assert_eq!(output, refined_content, "failed");
240 }
241 #[test]
242 fn test_gen_consts_sample3() {
243 let file_content = include_str!("./test_files/sample3_input.move");
244 let refined_content = include_str!("./test_files/sample3_expect.move");
245 let output = gen_consts(file_content, &get_constant_values());
246 assert_eq!(output, refined_content, "failed");
247 }
248}