1use crate::{
2 leaptypes::{Comment, CommentType, LeapEnum, LeapStruct, LeapType, Name, ValueType},
3 parser::{commentsparser, parser::Parser},
4};
5
6#[derive(Debug)]
7struct Block {
8 start: usize,
9 next_start: usize,
10 trail_indent: usize,
11 new_section: bool,
12 text: String,
13}
14
15pub fn format(data: &str) -> Option<String> {
16 let types = Parser::parse(data).ok()?;
17 let mut formatted: Vec<Block> = vec![];
18 for next_type in &types {
19 let mut lines = format_type(next_type);
20 if !lines.is_empty() {
21 if let Some(last) = formatted.last_mut() {
22 last.next_start = lines.last().unwrap().start;
23 }
24 update_trail_indent(&mut lines);
25 formatted.append(&mut lines);
26 }
27 }
28 if let Some(last) = formatted.last_mut() {
29 last.next_start = data.len();
30 }
31 let mut comments = commentsparser::parse(data).into_iter().peekable();
32 let mut result = vec![];
33 for b in formatted {
34 while comments
35 .peek()
36 .map_or(false, |c| c.position.start < b.start)
37 {
38 let indent = if b.new_section { 0 } else { 4 };
39 result.push(format_comment(&comments.next().unwrap(), indent));
40 }
41 let has_trail_comment = comments.peek().map_or(false, |c| match c.comment_type {
42 CommentType::Trail => b.start < c.position.start && c.position.start < b.next_start,
43 _ => false,
44 });
45 if has_trail_comment {
46 let indent = b.trail_indent - b.text.len();
47 let mut text = b.text;
48 text.push_str(&format_comment(&comments.next().unwrap(), indent));
49 result.push(text);
50 } else {
51 result.push(b.text);
52 }
53 }
54 for c in comments {
55 result.push(format_comment(&c, 0));
56 }
57 let mut new_result: Vec<String> = vec![];
59 for r in result {
60 if r.is_empty() {
61 let last_separator_or_empty = new_result.last().map(|s| s.is_empty()).unwrap_or(true);
63 if !last_separator_or_empty {
64 new_result.push(r)
65 }
66 } else {
67 new_result.push(r);
68 }
69 }
70 result = new_result;
71 if result.last().map(|s| s.is_empty()).unwrap_or(false) {
73 result.pop();
74 }
75 let mut result = result.join("\n");
76 if !result.is_empty() {
78 result.push('\n');
79 }
80 Some(result)
81}
82
83fn update_trail_indent(lines: &mut [Block]) {
84 let max_len = lines.iter().map(|b| b.text.len()).max().unwrap_or(0);
85 let indent = (max_len / 4) * 4 + 4;
86 for b in lines {
87 b.trail_indent = indent;
88 }
89}
90
91fn format_type(leap_type: &LeapType) -> Vec<Block> {
92 let mut lines = vec![];
93 let mut type_lines = match leap_type {
94 LeapType::Struct(s) => format_struct(s),
95 LeapType::Enum(e) => format_enum(e),
96 };
97 lines.append(&mut type_lines);
98 lines
99}
100
101fn format_struct(leap_struct: &LeapStruct) -> Vec<Block> {
102 let mut lines = vec![];
103 let text = format!(
104 ".struct {}{}",
105 leap_struct.name.get(),
106 format_type_args(&leap_struct.args)
107 );
108 let next_start = if let Some(last) = leap_struct.props.last() {
109 last.position.start
110 } else {
111 leap_struct.position.end()
112 };
113 lines.push(Block {
114 start: leap_struct.position.start,
115 next_start,
116 trail_indent: 0,
117 new_section: true,
118 text,
119 });
120 for i in 0..leap_struct.props.len() {
121 let prop = &leap_struct.props[i];
122 let next_prop = leap_struct.props.get(i + 1);
123 let text = format!(
124 " {}: {}",
125 prop.name.get(),
126 format_prop_type(&prop.prop_type)
127 );
128 let next_start = if let Some(next) = next_prop {
129 next.position.start
130 } else {
131 prop.position.end()
132 };
133 lines.push(Block {
134 start: prop.position.start,
135 next_start,
136 trail_indent: 0,
137 new_section: false,
138 text,
139 })
140 }
141 lines
142}
143
144fn format_enum(leap_enum: &LeapEnum) -> Vec<Block> {
145 let mut lines = vec![];
146 let text = format!(
147 ".enum {}{}",
148 leap_enum.name.get(),
149 format_type_args(&leap_enum.args)
150 );
151 let next_start = if let Some(last) = leap_enum.variants.last() {
152 last.position.start
153 } else {
154 leap_enum.position.end()
155 };
156 lines.push(Block {
157 start: leap_enum.position.start,
158 next_start,
159 trail_indent: 0,
160 new_section: true,
161 text,
162 });
163 for i in 0..leap_enum.variants.len() {
164 let variant = &leap_enum.variants[i];
165 let next_var = leap_enum.variants.get(i + 1);
166 let text = if variant.name.get() == variant.prop_type.name() {
167 format!(" {}", format_prop_type(&variant.prop_type))
168 } else {
169 format!(
170 " {}: {}",
171 variant.name.get(),
172 format_prop_type(&variant.prop_type)
173 )
174 };
175 let next_start = if let Some(next) = next_var {
176 next.position.start
177 } else {
178 variant.position.end()
179 };
180 lines.push(Block {
181 start: variant.position.start,
182 next_start,
183 trail_indent: 0,
184 new_section: false,
185 text,
186 })
187 }
188 lines
189}
190
191fn format_type_args(args: &[Name]) -> String {
192 if args.is_empty() {
193 "".to_owned()
194 } else {
195 let mut tokens = vec![];
196 for name in args {
197 tokens.push(name.get());
198 }
199 let tokens = tokens.join(" ");
200 format!("[{}]", tokens)
201 }
202}
203
204fn format_prop_type(prop_type: &ValueType) -> String {
205 match prop_type {
206 ValueType::Simple(t) => t.name(),
207 ValueType::List(t) => format!("list[{}]", format_prop_type(t)),
208 ValueType::TypeArg(n) => n.get().to_owned(),
209 ValueType::LeapType { name, args } => {
210 if args.is_empty() {
211 name.get().to_owned()
212 } else {
213 let args = args
214 .iter()
215 .map(format_prop_type)
216 .collect::<Vec<_>>()
217 .join(" ");
218 format!("{}[{}]", name.get(), args)
219 }
220 }
221 }
222}
223
224fn format_comment(comment: &Comment, indent: usize) -> String {
225 let indent = String::from_utf8(vec![b' '; indent]).unwrap();
226 match comment.comment_type {
227 CommentType::Line | CommentType::Trail => format!("{}/-- {}", indent, comment.comment),
228 CommentType::Separator => "".to_owned(),
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_update_trail_indent() {
238 let mut blocks = vec![Block {
239 start: 0,
240 next_start: 0,
241 trail_indent: 0,
242 new_section: false,
243 text: "aaaa".to_owned(),
244 }];
245 update_trail_indent(&mut blocks);
246 assert_eq!(blocks[0].trail_indent, 8);
247 let mut blocks = vec![Block {
248 start: 0,
249 next_start: 0,
250 trail_indent: 0,
251 new_section: false,
252 text: "aaaaaaa".to_owned(),
253 }];
254 update_trail_indent(&mut blocks);
255 assert_eq!(blocks[0].trail_indent, 8);
256 }
257
258 #[test]
259 fn test_format() {
260 assert_eq!(format(".struct s1").unwrap(), ".struct s1\n");
261 assert_eq!(
262 format(".struct s1 [ a b ]").unwrap(),
263 ".struct s1[a b]\n"
264 );
265 assert_eq!(
266 format(".struct s1 a: int").unwrap(),
267 ".struct s1\n a: int\n"
268 );
269 assert_eq!(format(".enum e1 s1").unwrap(), ".enum e1\n s1\n");
270 assert_eq!(
271 format(".enum e1 aaa: s1").unwrap(),
272 ".enum e1\n aaa: s1\n"
273 );
274 assert_eq!(
275 format(".enum e1[a b] s1 v2[ a b ]").unwrap(),
276 ".enum e1[a b]\n s1\n v2[a b]\n"
277 );
278 }
279
280 #[test]
281 fn test_format_with_comments() {
282 assert_eq!(format("").unwrap(), "");
283 assert_eq!(
284 format("/ text\n.struct s1").unwrap(),
285 "/-- text\n.struct s1\n"
286 );
287 assert_eq!(
288 format("/- text\n.struct s1").unwrap(),
289 "/-- text\n.struct s1\n"
290 );
291 assert_eq!(
292 format("/-- text\n.struct s1").unwrap(),
293 "/-- text\n.struct s1\n"
294 );
295 assert_eq!(
296 format("/--- text\n.struct s1").unwrap(),
297 "/-- - text\n.struct s1\n"
298 );
299 assert_eq!(
300 format("/-- -- text\n.struct s1").unwrap(),
301 "/-- -- text\n.struct s1\n"
302 );
303 assert_eq!(
304 format("/ text\n\n.struct s1").unwrap(),
305 "/-- text\n\n.struct s1\n"
306 );
307 assert_eq!(
308 format("\n\n\n\n.struct \n\n\n\n s1\n\n\n\n").unwrap(),
309 ".struct s1\n"
310 );
311 assert_eq!(
312 format("/ text\n\n\n.struct s1").unwrap(),
313 "/-- text\n\n.struct s1\n"
314 );
315 assert_eq!(
316 format("/ text \n\n\n.struct s1").unwrap(),
317 "/-- text\n\n.struct s1\n"
318 );
319
320 assert_eq!(
321 format(".struct s1 / text").unwrap(),
322 ".struct s1 /-- text\n"
323 );
324 assert_eq!(format(".enum s1 / text").unwrap(), ".enum s1 /-- text\n");
325
326 assert_eq!(
327 format(".struct s1\n/ text").unwrap(),
328 ".struct s1\n/-- text\n"
329 );
330 assert_eq!(format(".enum s1\n/ text").unwrap(), ".enum s1\n/-- text\n");
331
332 assert_eq!(
333 format(".struct s1\nv: int / text").unwrap(),
334 ".struct s1\n v: int /-- text\n"
335 );
336 assert_eq!(
337 format(".enum s1\nval / text").unwrap(),
338 ".enum s1\n val /-- text\n"
339 );
340
341 assert_eq!(
342 format(".struct s1\n/ text\nv: int").unwrap(),
343 ".struct s1\n /-- text\n v: int\n"
344 );
345 assert_eq!(
346 format(".enum aaaaaa\n/ text\nval").unwrap(),
347 ".enum aaaaaa\n /-- text\n val\n"
348 );
349
350 assert_eq!(
351 format(".struct s1\n/ text\n\n\n/ text\nv: int").unwrap(),
352 ".struct s1\n /-- text\n\n /-- text\n v: int\n"
353 );
354 }
355
356 #[test]
357 fn test_format_complex() {
358 let formatted = format(
359 "
360 / text1 text
361 / tttt2
362
363 /text3
364
365 .struct some-my-struct / text4
366 /text5
367
368 /text6
369 /text7
370 v1: list[int] /text8
371 v2: list[list[int]]
372
373 /text9
374 /text10
375
376 .enum value-enum
377
378 / test11
379
380 val1
381 val2
382
383 / text12
384 ",
385 )
386 .unwrap();
387 let expected = "/-- text1 text
388/-- tttt2
389
390/-- text3
391
392.struct some-my-struct /-- text4
393 /-- text5
394
395 /-- text6
396 /-- text7
397 v1: list[int] /-- text8
398 v2: list[list[int]]
399
400/-- text9
401/-- text10
402
403.enum value-enum
404
405 /-- test11
406
407 val1
408 val2
409
410/-- text12
411";
412 assert_eq!(formatted, expected);
413 assert_eq!(format(&formatted).unwrap(), expected);
415 }
416}