Skip to main content

chrobry_core/
processor.rs

1use crate::ast::*;
2use regex::{Captures, Regex};
3use std::collections::HashMap;
4
5enum Context {
6    None,
7    Struct(String),
8    Enum(String),
9}
10
11pub fn process<F>(
12    ast: &Ast,
13    separator: &str,
14    variables: HashMap<String, String>,
15    _on_import: F,
16) -> Result<String, String>
17where
18    F: FnMut(&str) -> Result<String, String>,
19{
20    let impls = get_impl_targets(ast);
21    validate_type_impls(ast, &impls)?;
22    let mut output = String::default();
23    for code in &ast.injects {
24        process_code(&Context::None, code, ast, &variables, &mut output)?;
25        output.push_str(separator);
26    }
27    for external in &ast.externs {
28        process_extern(external, ast, separator, &mut output)?;
29    }
30    for enum_ in &ast.enums {
31        process_enum(enum_, ast, separator, &mut output)?;
32    }
33    for struct_ in &ast.structs {
34        process_struct(struct_, ast, separator, &mut output)?;
35    }
36    for replace in &ast.replacements {
37        output = process_replacement(replace, &output, ast, &variables);
38    }
39    Ok(output)
40}
41
42fn get_impl_targets(ast: &Ast) -> Vec<(String, AstImplementationTarget)> {
43    ast.implementations
44        .iter()
45        .map(|i| (i.name.to_owned(), i.target.clone()))
46        .collect::<Vec<_>>()
47}
48
49fn validate_type_impls(
50    ast: &Ast,
51    impl_targets: &Vec<(String, AstImplementationTarget)>,
52) -> Result<(), String> {
53    for external in &ast.externs {
54        for type_ in &external.types {
55            for (implementation, _) in &external.implementations {
56                if !impl_targets.iter().any(|(n, _)| implementation == n) {
57                    return Err(format!(
58                        "Trying to apply non-existing trait `{}` for external type `{}`",
59                        implementation, type_
60                    ));
61                }
62            }
63        }
64    }
65    for struct_ in &ast.structs {
66        for (tag, _) in &struct_.tags {
67            if !impl_targets
68                .iter()
69                .any(|(n, t)| tag == n && t.is_valid(AstImplementationTarget::Struct))
70            {
71                return Err(format!(
72                    "Trying to apply non-existing or non-struct trait `{}` for struct `{}`",
73                    tag, struct_.name
74                ));
75            }
76        }
77    }
78    for enum_ in &ast.enums {
79        for (tag, _) in &enum_.tags {
80            if !impl_targets
81                .iter()
82                .any(|(n, t)| tag == n && t.is_valid(AstImplementationTarget::Enum))
83            {
84                return Err(format!(
85                    "Trying to apply non-existing or non-enum trait `{}` for enum `{}`",
86                    tag, enum_.name
87                ));
88            }
89        }
90    }
91    Ok(())
92}
93
94fn process_code(
95    context: &Context,
96    code: &AstCode,
97    ast: &Ast,
98    variables: &HashMap<String, String>,
99    output: &mut String,
100) -> Result<(), String> {
101    for chunk in &code.0 {
102        match chunk {
103            AstCodeChunk::Content(content) => output.push_str(&content),
104            AstCodeChunk::Variable(variable) => {
105                if let Some(found) = variables.get(variable) {
106                    output.push_str(found);
107                } else {
108                    return Err(format!(
109                        "Trying to place non-existing variable `{}`",
110                        variable
111                    ));
112                }
113            }
114            AstCodeChunk::For(for_) => process_code_for(context, for_, ast, variables, output)?,
115            AstCodeChunk::None => {}
116        }
117    }
118    Ok(())
119}
120
121fn process_code_for(
122    context: &Context,
123    code: &AstCodeFor,
124    ast: &Ast,
125    variables: &HashMap<String, String>,
126    output: &mut String,
127) -> Result<(), String> {
128    if code.variables.is_empty() {
129        unreachable!();
130    }
131    let iterables = get_container_iterables(context, &code.container, ast, variables)?;
132    let count = iterables.len() / code.variables.len();
133    for i in 0..count {
134        let start = i * code.variables.len();
135        let end = start + code.variables.len();
136        let values = &iterables[start..end];
137        let mut variables = variables.clone();
138        for (name, value) in code.variables.iter().zip(values.iter()) {
139            variables.insert(name.to_owned(), value.to_owned());
140        }
141        process_code(context, &code.code, ast, &variables, output)?;
142    }
143    Ok(())
144}
145
146fn get_container_iterables(
147    context: &Context,
148    container: &AstIn,
149    ast: &Ast,
150    variables: &HashMap<String, String>,
151) -> Result<Vec<String>, String> {
152    match container {
153        AstIn::Fields => match context {
154            Context::Struct(name) => {
155                let s = ast.structs.iter().find(|s| &s.name == name).unwrap();
156                Ok(s.fields
157                    .iter()
158                    .flat_map(|(n, t)| vec![n.to_owned(), t.to_string()])
159                    .collect::<Vec<_>>())
160            }
161            Context::Enum(name) => {
162                let e = ast.enums.iter().find(|e| &e.name == name).unwrap();
163                Ok(e.fields.clone())
164            }
165            Context::None => Err("Trying to iterate over fields of no context".to_owned()),
166        },
167        AstIn::Variable(variable) => {
168            if let Some(found) = variables.get(variable) {
169                Ok(found.split("|").map(str::to_owned).collect::<Vec<_>>())
170            } else {
171                Err(format!(
172                    "Trying to iterate over non-existing variable `{}`",
173                    variable
174                ))
175            }
176        }
177        AstIn::None => Err("There is no container specified to iterate over".to_owned()),
178    }
179}
180
181fn process_replacement(
182    replace: &AstReplace,
183    input: &str,
184    ast: &Ast,
185    variables: &HashMap<String, String>,
186) -> String {
187    let pattern = Regex::new(&replace.pattern).expect("Could not parse replacement pattern");
188    println!("* Replace pattern: `{:?}`", pattern);
189    pattern
190        .replace_all(input, |captures: &Captures| {
191            let mut variables = variables.clone();
192            for i in 0..captures.len() {
193                if let Some(capture) = captures.get(i) {
194                    variables.insert(format!("_{}", i), capture.as_str().to_owned());
195                }
196            }
197            let mut output = String::new();
198            process_code(
199                &Context::None,
200                &replace.template,
201                ast,
202                &variables,
203                &mut output,
204            )
205            .expect("Could not process replacement template code");
206            output
207        })
208        .to_owned()
209        .into()
210}
211
212fn process_extern(
213    external: &AstExtern,
214    ast: &Ast,
215    separator: &str,
216    output: &mut String,
217) -> Result<(), String> {
218    for type_ in &external.types {
219        let mut variables = HashMap::new();
220        variables.insert("TYPENAME".to_owned(), type_.to_owned());
221        for (_, code) in &external.implementations {
222            process_code(&Context::None, code, ast, &variables, output)?;
223            output.push_str(separator);
224        }
225    }
226    Ok(())
227}
228
229fn process_enum(
230    enum_: &AstEnum,
231    ast: &Ast,
232    separator: &str,
233    output: &mut String,
234) -> Result<(), String> {
235    let context = Context::Enum(enum_.name.to_owned());
236    for (name, params) in &enum_.tags {
237        let mut variables = HashMap::new();
238        variables.insert("TYPENAME".to_owned(), enum_.name.to_owned());
239        for (key, value) in params {
240            variables.insert(key.to_owned(), value.to_owned());
241        }
242        let trait_ = ast
243            .implementations
244            .iter()
245            .find(|i| &i.name == name && i.target == AstImplementationTarget::Enum)
246            .unwrap();
247        // TODO: where rules validation.
248        process_code(&context, &trait_.code, ast, &variables, output)?;
249        output.push_str(separator);
250    }
251    Ok(())
252}
253
254fn process_struct(
255    struct_: &AstStruct,
256    ast: &Ast,
257    separator: &str,
258    output: &mut String,
259) -> Result<(), String> {
260    let context = Context::Struct(struct_.name.to_owned());
261    for (name, params) in &struct_.tags {
262        let mut variables = HashMap::new();
263        variables.insert("TYPENAME".to_owned(), struct_.name.to_owned());
264        for (key, value) in params {
265            variables.insert(key.to_owned(), value.to_owned());
266        }
267        let trait_ = ast
268            .implementations
269            .iter()
270            .find(|i| &i.name == name && i.target == AstImplementationTarget::Struct)
271            .unwrap();
272        // TODO: where rules validation.
273        process_code(&context, &trait_.code, ast, &variables, output)?;
274        output.push_str(separator);
275    }
276    Ok(())
277}