edifact_types_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro2::{Ident, Span, TokenStream};
4use quote::{format_ident, quote};
5use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericArgument, PathArguments, Type};
6
7#[proc_macro_derive(DisplayInnerSegment)]
8pub fn display_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9    let ast = syn::parse_macro_input!(input as DeriveInput);
10    let toks = generate_inner_display(&ast).unwrap_or_else(|err| err.to_compile_error());
11    toks.into()
12}
13
14#[proc_macro_derive(DisplayOuterSegment)]
15pub fn display_outer(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
16    let ast = syn::parse_macro_input!(input as DeriveInput);
17    let toks = generate_outer_display(&ast).unwrap_or_else(|err| err.to_compile_error());
18    toks.into()
19}
20
21#[proc_macro_derive(DisplayEdifact)]
22pub fn display_edifact(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
23    let ast = syn::parse_macro_input!(input as DeriveInput);
24    let toks = generate_edifact(&ast).unwrap_or_else(|err| err.to_compile_error());
25    toks.into()
26}
27
28#[proc_macro_derive(DisplayEdifactSg)]
29pub fn display_edifact_sg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
30    let ast = syn::parse_macro_input!(input as DeriveInput);
31    let toks = generate_edifact_sg(&ast).unwrap_or_else(|err| err.to_compile_error());
32    toks.into()
33}
34
35fn generate_inner_display(ast: &DeriveInput) -> syn::Result<TokenStream> {
36    let name = &ast.ident;
37    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
38    let output = gen_types(ast);
39    Ok(quote! {
40        impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
41            fn fmt<'x>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42                let mut str: Vec<String> = vec![];
43                #(#output)*
44                let joined = str.join(":");
45                let joined = joined.trim_end_matches(":");
46                write!(f, "{}", joined)
47            }
48        }
49    })
50}
51
52fn generate_outer_display(ast: &DeriveInput) -> syn::Result<TokenStream> {
53    let name = &ast.ident;
54    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
55    let output = gen_types(ast);
56    let s = format_ident!("{}", name).to_string().to_uppercase();
57    Ok(quote! {
58        impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
59            fn fmt<'x>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60                let mut str: Vec<String> = vec![];
61                str.push(#s.to_string());
62                #(#output)*
63                let joined = str.join("+");
64                let joined = joined.trim_end_matches("+");
65                if joined.len() > 3 {
66                    write!(f, "{}", joined)
67                }else{
68                    write!(f, "")
69                }
70            }
71        }
72    })
73}
74
75fn generate_edifact(ast: &DeriveInput) -> syn::Result<TokenStream> {
76    let name = &ast.ident;
77    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
78    let output = gen_types(ast);
79    Ok(quote! {
80        impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
81            fn fmt<'x>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82                let mut str: Vec<String> = vec![];
83                #(#output)*
84                // filter empty lines
85                let str: Vec<String> = str
86                    .iter()
87                    .map(|v| v.clone())
88                    .filter(|s| !s.is_empty())
89                    .collect();
90                let joined = str.join("'\n");
91                write!(f, "{}'", joined)
92            }
93        }
94    })
95}
96
97fn generate_edifact_sg(ast: &DeriveInput) -> syn::Result<TokenStream> {
98    let name = &ast.ident;
99    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
100    let output = gen_types(ast);
101    Ok(quote! {
102        impl #impl_generics ::core::fmt::Display for #name #ty_generics #where_clause {
103            fn fmt<'x>(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104                let mut str: Vec<String> = vec![];
105                #(#output)*
106                // filter empty lines
107                let str: Vec<String> = str
108                    .iter()
109                    .map(|v| v.clone())
110                    .filter(|s| !s.is_empty())
111                    .collect();
112                let joined = str.join("'\n");
113                write!(f, "{}", joined)
114            }
115        }
116    })
117}
118
119fn gen_types(ast: &DeriveInput) -> Vec<TokenStream> {
120    let x = &ast.data;
121    let mut output = vec![];
122    if let Data::Struct(s) = x {
123        let f = &s.fields;
124        for o in f {
125            let id = o.ident.clone().unwrap();
126            let t = &o.ty;
127            if let syn::Type::Path(p) = t {
128                let s = &p.path.segments;
129                let w = &s.first().unwrap().ident;
130                let ty = w.to_string();
131                match ty.as_str() {
132                    "Vec" => {
133                        let ts = quote! {
134                            if self.#id.is_empty() {
135                                str.push("".to_string());
136                            }else{
137                                self.#id.iter().for_each(|x| str.push(format!("{}",x)));
138                            }
139                        };
140                        output.push(ts);
141                    }
142                    "Option" => {
143                        let ts = quote! {
144                            str.push(self.#id.as_ref().map_or("".to_string(),|x| format!("{}",x)));
145                        };
146                        output.push(ts);
147                    }
148                    _ => {
149                        let ts = quote! {
150                            str.push(format!("{}",self.#id));
151                        };
152                        output.push(ts);
153                    }
154                }
155            }
156        }
157    }
158    output
159}
160
161#[proc_macro_derive(ParseInnerSegment)]
162pub fn parse_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
163    let ast = syn::parse_macro_input!(input as DeriveInput);
164    let toks = generate_inner_parse(&ast).unwrap_or_else(|err| err.to_compile_error());
165    toks.into()
166}
167
168fn generate_inner_parse(ast: &DeriveInput) -> syn::Result<TokenStream> {
169    let name = &ast.ident;
170    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
171    let output = gen_inner_props(ast);
172    let prop_count = output.len();
173    Ok(quote! {
174        impl #impl_generics ::core::str::FromStr for #name #ty_generics #where_clause {
175            type Err = ParseError;
176
177            fn from_str(s: &str) -> Result<Self, Self::Err> {
178                let parts: Vec<&str> = s.split(':').collect();
179                if parts.len() > #prop_count {
180                    Err(ParseError {
181                        msg: "too many segments".to_string(),
182                    })
183                } else {
184                    Ok(#name {
185                        #(#output)*
186                    })
187                }
188            }
189        }
190    })
191}
192
193fn gen_inner_props(ast: &DeriveInput) -> Vec<TokenStream> {
194    let x = &ast.data;
195    let mut output = vec![];
196    let mut idx: usize = 0;
197    if let Data::Struct(s) = x {
198        let f = &s.fields;
199        for o in f {
200            let id = o.ident.clone().unwrap();
201            let t = &o.ty;
202            if let syn::Type::Path(p) = t {
203                let s = &p.path.segments;
204                let w = &s.first().unwrap().ident;
205                let ty = w.to_string();
206                match ty.as_str() {
207                    "Option" => {
208                        let chunk = quote! {
209                            #id: parts.get(#idx).map(|x| x.to_string()),
210                        };
211                        output.push(chunk);
212                    }
213                    _ => {
214                        let chunk = quote! {
215                            #id: parts.get(#idx).map(|x| x.to_string()).unwrap_or_default(),
216                        };
217                        output.push(chunk);
218                    }
219                }
220            }
221            idx += 1;
222        }
223    }
224    output
225}
226
227#[proc_macro_derive(ParseElement)]
228pub fn parse_element(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
229    let input = parse_macro_input!(input as DeriveInput);
230    let output = generate_element_parser(&input).unwrap_or_else(|err| err.to_compile_error());
231    #[cfg(feature = "debug")]
232    println!("{output}");
233    proc_macro::TokenStream::from(output)
234}
235
236fn generate_element_parser(ast: &DeriveInput) -> syn::Result<TokenStream> {
237    let name = &ast.ident;
238    let tok = parse_all(ast);
239    let s = format_ident!("{}", name).to_string().to_uppercase();
240    let res = quote! {
241        impl<'a> crate::util::Parser<&'a str, #name, nom::error::Error<&'a str>> for #name {
242            fn parse(input: &'a str) -> ::nom::IResult<&'a str, #name> {
243                #[cfg(feature = "logging")]
244                log::debug!("Parser is inside {}", #s);
245                let (_, vars) = crate::util::parse_colon_section(input)?;
246                #[cfg(feature = "logging")]
247                log::debug!("Variables created {vars:?}");
248                let output = #name {
249                    #(#tok)*
250                };
251                Ok(("", output))
252            }
253        }
254    };
255    Ok(res)
256}
257
258#[proc_macro_derive(ParseSg)]
259pub fn parse_sg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
260    let input = parse_macro_input!(input as DeriveInput);
261    let output = generate_sg_parser(&input, true).unwrap_or_else(|err| err.to_compile_error());
262    proc_macro::TokenStream::from(output)
263}
264
265#[proc_macro_derive(ParseMsg)]
266pub fn parse_msg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
267    let input = parse_macro_input!(input as DeriveInput);
268    let output = generate_sg_parser(&input, false).unwrap_or_else(|err| err.to_compile_error());
269    #[cfg(feature = "debug")]
270    println!("{output}");
271    proc_macro::TokenStream::from(output)
272}
273
274fn generate_sg_parser(ast: &DeriveInput, is_sg: bool) -> syn::Result<TokenStream> {
275    let name = &ast.ident;
276    let mut lefties = vec![];
277    let mut attries = vec![];
278    if let Data::Struct(left_vec) = &ast.data {
279        if let Fields::Named(f) = &left_vec.fields {
280            for (idx, ff) in (&f.named).into_iter().enumerate() {
281                let left = match &ff.ident {
282                    Some(l) => l.clone(),
283                    None => Ident::new("", Span::call_site()),
284                };
285
286                // right side can be any of String, Struct, Enum, Option<..>, Vec<..>
287
288                let all = if let Type::Path(tyty) = &ff.ty {
289                    let inside = &tyty.path.segments[0];
290                    let inside_str = inside.ident.to_string();
291                    // First occurence, can be Option, Vec or Type
292                    match inside_str.as_str() {
293                        "Vec" | "Option" => {
294                            if let PathArguments::AngleBracketed(inside_optvec) = &inside.arguments
295                            {
296                                if let GenericArgument::Type(Type::Path(t)) = &inside_optvec.args[0]
297                                {
298                                    let ti = &t.path.segments[0].ident;
299                                    (
300                                        quote! {
301                                            #left
302                                        },
303                                        if inside_str == "Vec" {
304                                            // if we are in msg_type stage, or if vec is the first element of struct
305                                            // many0 will come back positive and the surroung element will not collaps
306                                            if !is_sg || idx != 0 {
307                                                quote! {
308                                                // let (outer_rest, dtm) = many0(DTM::parse)(outer_rest)?;
309                                                    let (outer_rest, #left) = nom::multi::many0(#ti::parse).parse(outer_rest)?;
310                                                }
311                                            } else {
312                                                quote! {
313                                                    let (outer_rest, #left) = nom::multi::many1(#ti::parse).parse(outer_rest)?;
314                                                }
315                                            }
316                                        } else {
317                                            quote! {
318                                                let (outer_rest, #left) = nom::combinator::opt(#ti::parse).parse(outer_rest)?;
319                                            }
320                                        },
321                                    )
322                                } else {
323                                    (quote! {}, quote! {})
324                                }
325                            } else {
326                                (quote! {}, quote! {})
327                            }
328                        }
329                        _ => {
330                            let i = inside.ident.clone();
331                            (
332                                quote! {
333                                    #left
334                                },
335                                quote! {
336                                    // let (outer_rest, loc) = LOC::parse(input)?;
337                                    let (outer_rest, #left) = #i::parse(outer_rest)?;
338                                },
339                            )
340                        }
341                    }
342                } else {
343                    (quote! {}, quote! {})
344                };
345                lefties.push(all.0);
346                attries.push(all.1);
347            }
348        }
349    };
350    let s = format_ident!("{}", name).to_string().to_uppercase();
351    let res = quote! {
352        // impl<'a> Parser<&'a str, IftminSg1, nom::error::Error<&'a str>> for IftminSg1 {
353        //     fn parse(input: &'a str) -> IResult<&'a str, IftminSg1> {
354        //         let (outer_rest, loc) = LOC::parse(input)?;
355        //         let (outer_rest, dtm) = many0(DTM::parse)(outer_rest)?;
356        //         Ok((outer_rest, IftminSg1 { loc, dtm }))
357        //     }
358        // }
359        impl<'a> crate::util::Parser<&'a str, #name, nom::error::Error<&'a str>> for #name {
360            fn parse(input: &'a str) -> ::nom::IResult<&'a str, #name> {
361                #[cfg(feature = "logging")]
362                log::debug!("Parser is inside {}", #s);
363                let outer_rest = input;
364                #(#attries)*
365                Ok((outer_rest, #name { #(#lefties),* }))
366            }
367        }
368    };
369    #[cfg(feature = "debug")]
370    println!("{res}");
371    Ok(res)
372}
373
374// impl<'a> Parser<&'a str, C002, nom::error::Error<&'a str>> for C002 {
375//     fn parse(input: &'a str) -> IResult<&'a str, C002> {
376//         let (_, vars) = crate::util::parse_colon_section(input)?;
377//         let output = C002 {
378//             _010: vars.first().map(|x| _1001::from_str(clean_num(x)).unwrap()),
379//             _020: vars.get(1).map(|x| _1131::from_str(clean_num(x)).unwrap()),
380//             _030: vars.get(2).map(|x| _3055::from_str(clean_num(x)).unwrap()),
381//             _040: vars.get(3).map(|x| x.to_string()),
382//         };
383//         Ok(("", output))
384//     }
385// }
386
387#[proc_macro_derive(ParseSegment)]
388pub fn parse_segment(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
389    let input = parse_macro_input!(input as DeriveInput);
390    let output = generate_segment_parser(&input).unwrap_or_else(|err| err.to_compile_error());
391    #[cfg(feature = "debug")]
392    println!("{output}");
393    proc_macro::TokenStream::from(output)
394}
395
396// impl<'a> Parser<&'a str, COM, nom::error::Error<&'a str>> for COM {
397//     fn parse(input: &'a str) -> IResult<&'a str, COM> {
398//         let (output_rest, vars) = crate::util::parse_line(input, "COM")?;
399//         let output = COM {
400//             _010: vars.first().map(|x| C076::parse(x).unwrap().1).unwrap(),
401//         };
402//         Ok((output_rest, output))
403//     }
404// }
405
406fn generate_segment_parser(ast: &DeriveInput) -> syn::Result<TokenStream> {
407    let name = &ast.ident;
408    let tok = parse_all(ast);
409    let s = format_ident!("{}", name).to_string().to_uppercase();
410    let res = quote! {
411        impl<'a> crate::util::Parser<&'a str, #name, nom::error::Error<&'a str>> for #name {
412            fn parse(input: &'a str) -> ::nom::IResult<&'a str, #name> {
413                #[cfg(feature = "logging")]
414                log::debug!("Parser is inside {}", #s);
415                let (output_rest, vars) = crate::util::parse_line(input, #s)?;
416                #[cfg(feature = "logging")]
417                log::debug!("Variables created {vars:?}");
418                #[cfg(feature = "logging")]
419                log::debug!("Left over string {output_rest:?}");
420                let output = #name {
421                    #(#tok)*
422                };
423                Ok((output_rest, output))
424            }
425        }
426    };
427    Ok(res)
428}
429
430fn parse_all(ast: &DeriveInput) -> Vec<TokenStream> {
431    let x = &ast.data;
432    let name = format!("{}", &ast.ident);
433    let mut output = vec![];
434    if let Data::Struct(s) = x {
435        let f = &s.fields;
436        for (idx, o) in f.into_iter().enumerate() {
437            // _010, _020, etc
438            let struct_field = o.ident.clone().unwrap();
439            let sf_string = struct_field.to_string();
440            let syn::Type::Path(tp) = &o.ty else {
441                panic!("Path type not found!")
442            };
443            let s = tp.path.segments.first().unwrap();
444            let opt_vec = s.ident.clone();
445            let ov_string = opt_vec.to_string();
446            match opt_vec.to_string().as_str() {
447                "Option" | "Vec" => {
448                    // List, String, Segment inside option or vec
449                    let mut inside_opt_vec: Ident = Ident::new("placeholder", Span::call_site());
450                    if let PathArguments::AngleBracketed(abga) = &s.arguments {
451                        if let GenericArgument::Type(syn::Type::Path(tp)) =
452                            abga.args.first().unwrap()
453                        {
454                            inside_opt_vec = tp.path.segments.first().unwrap().ident.clone();
455                        }
456                    }
457                    let iov_string = inside_opt_vec.to_string();
458
459                    // Can be String, _XXX (List), or CXXX,SXXX (Segment)
460                    if inside_opt_vec == "String" {
461                        output.push(quote! {
462                            #struct_field: vars.get(#idx).map(|x| x.to_string()),
463                        });
464                    } else if inside_opt_vec.to_string().starts_with('_') {
465                        // List (types.rs)
466                        output.push(quote! {
467                                #struct_field: vars.get(#idx).filter(|&f| !f.is_empty()).map(|x| match #inside_opt_vec::from_str(clean_num(x)) {
468                                    Ok(f) => f,
469                                    Err(e) => {
470                                        #[cfg(feature = "logging")]
471                                        log::error!("Line: {input}\nFor struct {}, parsing optional list item {} failed. Enum {} encountered the following error: {}", #name, #sf_string, #iov_string, e);
472                                        panic!("Parsing optional list item failed");
473                                    },
474                                }),
475                            });
476                    } else {
477                        // Segment or Element
478                        output.push(quote! {
479                                #struct_field: vars.get(#idx).filter(|&f| !f.is_empty()).map(|x| match #inside_opt_vec::parse(x) {
480                                    Ok((_,r)) => r,
481                                    Err(e) => {
482                                        #[cfg(feature = "logging")]
483                                        log::error!("Line: {input}\nFor struct {}, parsing optional segment or element {} failed. Struct {} encountered the following error: {}", #name, #sf_string, #iov_string, e);
484                                        panic!("Parsing optional segment or element failed");
485                                    },
486                                }),
487                            });
488                    }
489                }
490                "String" => {
491                    output.push(quote! {
492                        #struct_field: match vars.get(#idx).filter(|&f| !f.is_empty()).map(|x| x.to_string()) {
493                            Some(f) => f,
494                            None => {
495                                #[cfg(feature = "logging")]
496                                log::error!("Line: {input}\nFor struct {}, parsing mandatory {}.to_string() was not found", #name, #sf_string);
497                                panic!("Parsing mandatory to_string() failed");
498                        },
499                        },
500                    });
501                }
502                _ => {
503                    // Can be _XXX (List), or CXXX,SXXX (Segment)
504                    if opt_vec.to_string().starts_with('_') {
505                        // List (types.rs)
506                        output.push(quote! {
507                            #struct_field: vars.get(#idx).filter(|&f| !f.is_empty()).map(|x|match #opt_vec::from_str(clean_num(x)){
508                                Ok(f) => f,
509                                Err(e) => {
510                                    #[cfg(feature = "logging")]
511                                    log::error!("Line: {input}\nFor struct {}, parsing list item {} failed. Enum {} encountered the following error: {}", #name, #sf_string, #ov_string, e);
512                                    panic!("Parsing list item failed");
513                                },
514                            }).expect("Parsing List: not found"),
515                        });
516                    } else {
517                        // Segment or Element
518                        output.push(quote! {
519                            #struct_field: vars.get(#idx).filter(|&f| !f.is_empty()).map(|x| match #opt_vec::parse(x) {
520                                Ok((_,r)) => r,
521                                Err(e) => {
522                                    #[cfg(feature = "logging")]
523                                    log::error!("Line: {input}\nFor struct {}, parsing segment or element {} failed. Struct {} encountered the following error: {}", #name, #sf_string, #ov_string, e);
524                                    panic!("Parsing list item failed");
525                                },
526                            }).expect("Parsing Segement or Element: not found"),
527                        });
528                    }
529                }
530            }
531        }
532    }
533    output
534}
535
536#[proc_macro_derive(ParseOuterSegment)]
537pub fn parse_outer(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
538    let ast = syn::parse_macro_input!(input as DeriveInput);
539    let toks = generate_outer_parse(&ast).unwrap_or_else(|err| err.to_compile_error());
540    toks.into()
541}
542
543fn generate_outer_parse(ast: &DeriveInput) -> syn::Result<TokenStream> {
544    let name = &ast.ident;
545    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
546    let output = gen_outer_props(ast);
547    let prop_count = output.len();
548    let upper_name = format_ident!("{}", name).to_string().to_uppercase();
549    Ok(quote! {
550        impl #impl_generics ::core::str::FromStr for #name #ty_generics #where_clause {
551            type Err = ParseError;
552
553            fn from_str(s: &str) -> Result<Self, Self::Err> {
554                let x = s.trim_end_matches('\'');
555                let parts: Vec<&str> = x.split('+').collect();
556                if parts[0] == #upper_name {
557                    if parts.len() > #prop_count +1  {
558                        Err(ParseError {
559                            msg: "too many segments".to_string(),
560                        })
561                    } else {
562                        let mut obj = #name::default();
563                        #(#output)*
564                        Ok(obj)
565                    }
566                } else {
567                    Err(ParseError {
568                        msg: "segment name wrong".to_string(),
569                    })
570                }
571            }
572        }
573    })
574}
575
576fn gen_outer_props(ast: &DeriveInput) -> Vec<TokenStream> {
577    let x = &ast.data;
578    let mut output = vec![];
579    let mut idx: usize = 1;
580    if let Data::Struct(s) = x {
581        let f = &s.fields;
582        for o in f {
583            let id = o.ident.clone().unwrap();
584            let t = &o.ty;
585            if let syn::Type::Path(p) = t {
586                let s = &p.path.segments;
587                let we = s.first().unwrap();
588                let w = &we.ident;
589                let arg = &we.arguments;
590                let sub_id = &we.ident;
591                let ty = w.to_string();
592                match ty.as_str() {
593                    "Option" => {
594                        // look for type inside
595                        if let PathArguments::AngleBracketed(c) = arg {
596                            let o = c.args.first().unwrap();
597                            if let syn::GenericArgument::Type(syn::Type::Path(tpo)) = o {
598                                let sg = tpo.path.segments.first().unwrap();
599                                let ident = &sg.ident;
600                                let type_name = ident.to_string();
601                                match type_name.as_str() {
602                                    "String" => {
603                                        let chunk = quote! {
604                                            if let Some(val) = parts.get(#idx) {
605                                                obj.#id = Some(val.to_string());
606                                            }
607                                        };
608                                        output.push(chunk);
609                                    }
610                                    _ => {
611                                        let chunk = quote! {
612                                            if let Some(val) = parts.get(#idx) {
613                                                let t = #ident::from_str(val).unwrap();
614                                                obj.#id = Some(t);
615                                            }
616                                        };
617                                        output.push(chunk);
618                                    }
619                                }
620                            }
621                        }
622                    }
623                    "String" => {
624                        let chunk = quote! {
625                            if let Some(val) = parts.get(#idx) {
626                                obj.#id = val.to_string();
627                            }
628                        };
629                        output.push(chunk);
630                    }
631                    _ => {
632                        let chunk = quote! {
633                            if let Some(val) = parts.get(#idx) {
634                                obj.#id = #sub_id::from_str(val).unwrap();
635                            }
636                        };
637                        output.push(chunk);
638                    }
639                }
640            }
641            idx += 1;
642        }
643    }
644    output
645}