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 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 process_code(&context, &trait_.code, ast, &variables, output)?;
274 output.push_str(separator);
275 }
276 Ok(())
277}