orn_cli/
gen_const.rs

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                    // Case 1: CONST,
59                    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                    // Case 2: , CONST and CONST
71                    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    // remove constant function declaration
137    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    // remove constants block if it was generated before
147    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    // remove '()' if it's a constant function call
159    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    // insert constants block
184    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    // replace old constants block with new block
191    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    // insert into the beginning of the module
213    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}