Skip to main content

a2lfile/
ifdata.rs

1use crate::a2ml::{A2mlTaggedTypeSpec, A2mlTypeSpec, GenericIfData, GenericIfDataTaggedItem};
2use crate::parser::{BlockContent, ParseContext, ParserError, ParserState};
3use crate::tokenizer::{A2lToken, A2lTokenType};
4use std::collections::HashMap;
5
6#[cfg(feature = "ifdata_cleanup")]
7use crate::specification::{A2lFile, IfData};
8
9// parse_ifdata()
10// entry point for ifdata parsing.
11// Three attmpts to parse the data will be made:
12// 1) parse according to the built-in spec provided using the a2ml_specification! macro
13// 2) parse according to the spec in the A2ML block
14// 3) fallback parsing using parse_unknown_ifdata()
15pub(crate) fn parse_ifdata(
16    parser: &mut ParserState,
17    context: &ParseContext,
18) -> Result<(Option<GenericIfData>, bool), ParserError> {
19    let mut result = None;
20    let mut valid = false;
21    // is there any content in the IF_DATA?
22    if let Some(token) = parser.peek_token()
23        && token.ttype != A2lTokenType::End
24    {
25        // try parsing according to the spec provided by the user of the crate in the a2ml_specification! macro
26        let spec_list = std::mem::take(&mut parser.a2mlspec);
27        for a2mlspec in &spec_list {
28            if let Some(ifdata_items) = parse_ifdata_from_spec(parser, context, a2mlspec) {
29                result = Some(ifdata_items);
30                valid = true;
31                break;
32            }
33        }
34        parser.a2mlspec = spec_list;
35
36        if result.is_none() {
37            // this will succeed if the data format follows the basic a2l rules (e.g. matching /begin and /end)
38            // if it does not, a ParseErrror is generated
39            result = Some(parse_unknown_ifdata_start(parser, context)?);
40            valid = false;
41        }
42    }
43
44    Ok((result, valid))
45}
46
47// parse_ifdata_from_spec()
48// parse the items of an IF_DATA block according to a spec.
49// If parsing fails, the token cursor is set back to the beginning of the input so that parsing can be retried
50fn parse_ifdata_from_spec(
51    parser: &mut ParserState,
52    context: &ParseContext,
53    spec: &A2mlTypeSpec,
54) -> Option<GenericIfData> {
55    let pos = parser.get_tokenpos();
56    if let Ok(ifdata) = parse_ifdata_item(parser, context, spec) {
57        if let Some(A2lToken {
58            ttype: A2lTokenType::End,
59            ..
60        }) = parser.peek_token()
61        {
62            Some(parse_ifdata_make_block(
63                ifdata,
64                parser.get_incfilename(context.fileid),
65                context.line,
66            ))
67        } else {
68            // parsed some (or maybe none if the spec allows this!), but not all elements of the IF_DATA.
69            // put the token_cursor back at the beginning of the IF_DATA input
70            parser.set_tokenpos(pos);
71            None
72        }
73    } else {
74        // put the token_cursor back at the beginning of the IF_DATA input
75        parser.set_tokenpos(pos);
76        None
77    }
78}
79
80// parse_ifdata_item()
81// parse one item together with all of its dependent elements according to an A2mlTypeSpec
82fn parse_ifdata_item(
83    parser: &mut ParserState,
84    context: &ParseContext,
85    spec: &A2mlTypeSpec,
86) -> Result<GenericIfData, ParserError> {
87    Ok(match spec {
88        A2mlTypeSpec::None => GenericIfData::None,
89        A2mlTypeSpec::Char => {
90            let value = parser.get_integer(context)?;
91            GenericIfData::Char(parser.get_line_offset(), value)
92        }
93        A2mlTypeSpec::Int => {
94            let value = parser.get_integer(context)?;
95            GenericIfData::Int(parser.get_line_offset(), value)
96        }
97        A2mlTypeSpec::Long => {
98            let value = parser.get_integer(context)?;
99            GenericIfData::Long(parser.get_line_offset(), value)
100        }
101        A2mlTypeSpec::Int64 => {
102            let value = parser.get_integer(context)?;
103            GenericIfData::Int64(parser.get_line_offset(), value)
104        }
105        A2mlTypeSpec::UChar => {
106            let value = parser.get_integer(context)?;
107            GenericIfData::UChar(parser.get_line_offset(), value)
108        }
109        A2mlTypeSpec::UInt => {
110            let value = parser.get_integer(context)?;
111            GenericIfData::UInt(parser.get_line_offset(), value)
112        }
113        A2mlTypeSpec::ULong => {
114            let value = parser.get_integer(context)?;
115            GenericIfData::ULong(parser.get_line_offset(), value)
116        }
117        A2mlTypeSpec::UInt64 => {
118            let value = parser.get_integer(context)?;
119            GenericIfData::UInt64(parser.get_line_offset(), value)
120        }
121        A2mlTypeSpec::Float => {
122            let value = parser.get_float(context)?;
123            GenericIfData::Float(parser.get_line_offset(), value)
124        }
125        A2mlTypeSpec::Double => {
126            let value = parser.get_double(context)?;
127            GenericIfData::Double(parser.get_line_offset(), value)
128        }
129        A2mlTypeSpec::Array(arraytype, dim) => {
130            if **arraytype == A2mlTypeSpec::Char {
131                let value = parser.get_string_maxlen(context, *dim)?;
132                GenericIfData::String(parser.get_line_offset(), value)
133            } else {
134                let mut arrayitems = Vec::new();
135                for _ in 0..*dim {
136                    arrayitems.push(parse_ifdata_item(parser, context, arraytype)?);
137                }
138                GenericIfData::Array(arrayitems)
139            }
140        }
141        A2mlTypeSpec::Enum(enumspec) => {
142            let enumitem = parser.get_identifier(context)?;
143            let pos = parser.get_line_offset();
144            if enumspec.get(&enumitem).is_some() {
145                GenericIfData::EnumItem(pos, enumitem)
146            } else {
147                return Err(ParserError::invalid_enum_value(parser, context, &enumitem));
148            }
149        }
150        A2mlTypeSpec::Struct(structspec) => {
151            let mut structitems = Vec::with_capacity(structspec.len());
152            for itemspec in structspec {
153                structitems.push(parse_ifdata_item(parser, context, itemspec)?);
154            }
155            GenericIfData::Struct(parser.get_incfilename(context.fileid), 0, structitems)
156        }
157        A2mlTypeSpec::Sequence(seqspec) => {
158            let mut seqitems = Vec::new();
159            let mut checkpoint = parser.get_tokenpos();
160            while let Ok(item) = parse_ifdata_item(parser, context, seqspec) {
161                seqitems.push(item);
162                checkpoint = parser.get_tokenpos();
163            }
164            parser.set_tokenpos(checkpoint);
165            GenericIfData::Sequence(seqitems)
166        }
167        A2mlTypeSpec::TaggedStruct(tsspec) => {
168            GenericIfData::TaggedStruct(parse_ifdata_taggedstruct(parser, context, tsspec)?)
169        }
170        A2mlTypeSpec::TaggedUnion(tuspec) => {
171            let mut result = HashMap::new();
172            if let Some(taggeditem) = parse_ifdata_taggeditem(parser, context, tuspec)? {
173                result.insert(taggeditem.tag.clone(), vec![taggeditem]);
174            }
175            GenericIfData::TaggedUnion(result)
176        }
177    })
178}
179
180// parse_ifdata_taggedstruct()
181// parse all the tagged items of a TaggedStruct (TaggedUnions are not handled here because no loop is needed for those)
182fn parse_ifdata_taggedstruct(
183    parser: &mut ParserState,
184    context: &ParseContext,
185    tsspec: &HashMap<String, A2mlTaggedTypeSpec>,
186) -> Result<HashMap<String, Vec<GenericIfDataTaggedItem>>, ParserError> {
187    let mut result = HashMap::<String, Vec<GenericIfDataTaggedItem>>::new();
188    while let Some(taggeditem) = parse_ifdata_taggeditem(parser, context, tsspec)? {
189        if let Some(itemvec) = result.get_mut(&taggeditem.tag) {
190            itemvec.push(taggeditem);
191        } else {
192            result.insert(taggeditem.tag.clone(), vec![taggeditem]);
193        }
194    }
195
196    Ok(result)
197}
198
199// parse_ifdata_taggeditem()
200// try to parse a TaggedItem inside a TaggedStruct or TaggedUnion
201//
202// behold the ridiculous^Wglorious return type:
203//  - Parsing can fail completely because the file is structurally broken -> Outer layer is Result to handle this
204//  - It is possible that the function succeeds but can't return a value -> middle layer of Option handles this
205//  - finally, a GenericIfDataTaggedItem value is returned
206fn parse_ifdata_taggeditem(
207    parser: &mut ParserState,
208    context: &ParseContext,
209    spec: &HashMap<String, A2mlTaggedTypeSpec>,
210) -> Result<Option<GenericIfDataTaggedItem>, ParserError> {
211    let checkpoint = parser.get_tokenpos();
212
213    // skip all comments, since this function must return a tagged item
214    while let Some(A2lToken {
215        ttype: A2lTokenType::Comment,
216        ..
217    }) = parser.peek_token()
218    {
219        // skip comments
220        parser.get_token(context)?;
221    }
222
223    // check if there is a tag
224    if let Ok(BlockContent::Block(token, is_block, start_offset)) =
225        parser.get_next_tag_or_comment(context)
226    {
227        let tag = parser.get_token_text(token);
228
229        // check if the tag is valid inside this TaggedStruct/TaggedUnion. If it is not, parsing should abort so that the caller sees the tag
230        if let Some(taggedspec) = spec.get(tag) {
231            if taggedspec.is_block != is_block {
232                parser.set_tokenpos(checkpoint);
233                return Ok(None);
234            }
235            let uid = parser.get_next_id();
236
237            // parse the content of the tagged item
238            let newcontext = ParseContext::from_token(tag, token);
239            let data = parse_ifdata_item(parser, &newcontext, &taggedspec.item)?;
240            let parsed_item = parse_ifdata_make_block(
241                data,
242                parser.get_incfilename(newcontext.fileid),
243                newcontext.line,
244            );
245
246            // make sure that blocks that started with /begin end with /end
247            let end_offset = if is_block {
248                parser.expect_token(&newcontext, A2lTokenType::End)?;
249                let end_offset = parser.get_line_offset();
250                let endident = parser.expect_token(&newcontext, A2lTokenType::Identifier)?;
251                let endtag = parser.get_token_text(endident);
252                if endtag != tag {
253                    return Err(ParserError::IncorrectEndTag {
254                        filename: parser.filenames[context.fileid].to_string(),
255                        error_line: parser.last_token_position,
256                        tag: endtag.to_owned(),
257                        block: newcontext.element.clone(),
258                        block_line: newcontext.line,
259                    });
260                }
261                end_offset
262            } else {
263                // only blocks have an /end tag which uses an end_offset
264                0
265            };
266
267            Ok(Some(GenericIfDataTaggedItem {
268                incfile: parser.get_incfilename(newcontext.fileid),
269                line: newcontext.line,
270                uid,
271                start_offset,
272                end_offset,
273                tag: tag.to_string(),
274                data: parsed_item,
275                is_block,
276            }))
277        } else {
278            parser.set_tokenpos(checkpoint);
279            Ok(None)
280        }
281    } else {
282        parser.set_tokenpos(checkpoint);
283        Ok(None)
284    }
285}
286
287// parse_ifdata_make_block()
288// turn the GenericIfData contained in a TaggedItem into a block.
289fn parse_ifdata_make_block(
290    data: GenericIfData,
291    incfile: Option<String>,
292    line: u32,
293) -> GenericIfData {
294    match data {
295        GenericIfData::Struct(_, _, structitems) => GenericIfData::Block {
296            incfile,
297            line,
298            items: structitems,
299        },
300        _ => GenericIfData::Block {
301            incfile,
302            line,
303            items: vec![data],
304        },
305    }
306}
307
308pub(crate) fn parse_unknown_ifdata_start(
309    parser: &mut ParserState,
310    context: &ParseContext,
311) -> Result<GenericIfData, ParserError> {
312    let token_peek = parser.peek_token();
313    // by convention, the elements inside of IF_DATA are wrapped in a taggedunion
314    if let Some(A2lToken {
315        ttype: A2lTokenType::Identifier,
316        ..
317    }) = token_peek
318    {
319        // first token is an identifier, so it could be a tag of a tagegdunion
320        let token = parser.get_token(context)?;
321        let start_offset = parser.get_line_offset();
322        let tag = parser.get_token_text(token);
323        let uid = parser.get_next_id();
324        let newcontext = ParseContext::from_token(tag, token);
325        let result = parse_unknown_ifdata(parser, &newcontext, true)?;
326        // hack: temporarily undo the previous token, so that we can get it's end offset
327        parser.undo_get_token();
328        let end_offset = parser.get_line_offset();
329        let _ = parser.get_token(context);
330        let taggeditem = GenericIfDataTaggedItem {
331            incfile: parser.get_incfilename(newcontext.fileid),
332            line: newcontext.line,
333            uid,
334            start_offset,
335            end_offset,
336            tag: tag.to_string(),
337            data: result,
338            is_block: false,
339        };
340        let mut tuitem = HashMap::new();
341        tuitem.insert(tag.to_string(), vec![taggeditem]);
342
343        let items: Vec<GenericIfData> = vec![GenericIfData::TaggedUnion(tuitem)];
344
345        Ok(GenericIfData::Block {
346            incfile: parser.get_incfilename(context.fileid),
347            line: start_offset,
348            items,
349        })
350    } else {
351        // first token cannot be a tag, so the format is totally unknown
352        parse_unknown_ifdata(parser, context, true)
353    }
354}
355
356// parse_unknown_ifdata()
357// this function provides a fallback in case the data inside of an IF_DATA block cannot be
358// parsed using either the built-in specification or the A2ML spec in the file
359// If the data is strucutrally sane (matching /begin and /end tags), then it returns a result.
360// The returned data is not intended to be useful for further processing, but only to preserve
361// it so that is can be written out to a file again later.
362pub(crate) fn parse_unknown_ifdata(
363    parser: &mut ParserState,
364    context: &ParseContext,
365    is_block: bool,
366) -> Result<GenericIfData, ParserError> {
367    let mut items: Vec<GenericIfData> = Vec::new();
368
369    loop {
370        let token_peek = parser.peek_token();
371        if token_peek.is_none() {
372            return Err(ParserError::unexpected_eof(parser, context));
373        }
374        let token = token_peek.unwrap();
375
376        match token.ttype {
377            A2lTokenType::Identifier => {
378                // an arbitrary identifier; it could be a tag of a taggedstruct, but we don't know that here. The other option is an enum value.
379                let value = parser.get_identifier(context)?;
380                items.push(GenericIfData::EnumItem(parser.get_line_offset(), value));
381            }
382            A2lTokenType::String => {
383                let value = parser.get_string(context)?;
384                items.push(GenericIfData::String(parser.get_line_offset(), value));
385            }
386            A2lTokenType::Number => {
387                if let Ok(num) = parser.get_integer::<i32>(context) {
388                    let line_offset = parser.get_line_offset();
389                    items.push(GenericIfData::Long(line_offset, num));
390                } else {
391                    // try again, looks like the number is a float instead
392                    parser.undo_get_token();
393                    let floatnum = parser.get_float(context)?; // if this also returns an error, it is neither int nor float, which is a genuine parse error
394                    let line_offset = parser.get_line_offset();
395                    items.push(GenericIfData::Float(line_offset, floatnum));
396                }
397            }
398            A2lTokenType::Begin => {
399                // if this is directly within a block level element, then a new taggedstruct will be created to contain the new block element
400                // if it is not, this block belongs to the parent and we only need to break and exit here
401                if is_block {
402                    items.push(parse_unknown_taggedstruct(parser, context)?);
403                } else {
404                    break;
405                }
406            }
407            A2lTokenType::End => {
408                // the end of this unknown block. Contained unknown blocks are handled recursively, so we don't see their /end tags in this loop
409                break;
410            }
411            A2lTokenType::Include => { /* A2lTokenType::Include doesn't matter here */ }
412            A2lTokenType::Comment => {
413                /* A2lTokenType::Comment can be ignored, but the token must be consumed */
414                parser.get_token(context)?;
415            }
416        }
417    }
418
419    Ok(GenericIfData::Struct(
420        parser.get_incfilename(context.fileid),
421        0,
422        items,
423    ))
424}
425
426// parse_unknown_taggedstruct()
427// A taggedstruct is constructed to contain inner blocks, because the parse tree struct(block) is not permitted, while struct (taggedstruct(block)) is
428// The function will interpret as much as possible of the following data as elements of the taggedstruct
429fn parse_unknown_taggedstruct(
430    parser: &mut ParserState,
431    context: &ParseContext,
432) -> Result<GenericIfData, ParserError> {
433    let mut tsitems: HashMap<String, Vec<GenericIfDataTaggedItem>> = HashMap::new();
434
435    while let Some(A2lToken {
436        ttype: A2lTokenType::Comment,
437        ..
438    }) = parser.peek_token()
439    {
440        // skip comments
441        parser.get_token(context)?;
442    }
443
444    loop {
445        let (token, is_block, start_offset) = match parser.get_next_tag_or_comment(context) {
446            Ok(BlockContent::Block(token, is_block, start_offset)) => {
447                (token, is_block, start_offset)
448            }
449            Ok(BlockContent::Comment(..)) => continue,
450            Ok(BlockContent::None) | Err(_) => break,
451        };
452
453        let uid = parser.get_next_id();
454        let tag = parser.get_token_text(token);
455        let newcontext = ParseContext::from_token(tag, token);
456        let result = parse_unknown_ifdata(parser, &newcontext, is_block)?;
457
458        let end_offset = if is_block {
459            parser.expect_token(&newcontext, A2lTokenType::End)?;
460            let end_offset = parser.get_line_offset();
461            let endident = parser.expect_token(&newcontext, A2lTokenType::Identifier)?;
462            let endtag = parser.get_token_text(endident);
463            if endtag != tag {
464                return Err(ParserError::IncorrectEndTag {
465                    filename: parser.filenames[newcontext.fileid].to_string(),
466                    error_line: parser.last_token_position,
467                    tag: endtag.to_owned(),
468                    block: newcontext.element.clone(),
469                    block_line: newcontext.line,
470                });
471            }
472            end_offset
473        } else {
474            // only blocks have an /end tag which uses an end_offset
475            0
476        };
477
478        let taggeditem = GenericIfDataTaggedItem {
479            incfile: parser.get_incfilename(newcontext.fileid),
480            line: newcontext.line,
481            uid,
482            start_offset,
483            end_offset,
484            tag: tag.to_string(),
485            data: result,
486            is_block,
487        };
488
489        if !tsitems.contains_key(tag) {
490            tsitems.insert(tag.to_string(), vec![]);
491        }
492        tsitems.get_mut(tag).unwrap().push(taggeditem);
493    }
494
495    // There shouldn't be an unused /begin token after the loop has run. If there is, then this indicates that the file is damaged
496    if let Some(A2lToken {
497        ttype: A2lTokenType::Begin,
498        ..
499    }) = parser.peek_token()
500    {
501        return Err(ParserError::InvalidBegin {
502            filename: parser.filenames[context.fileid].to_string(),
503            error_line: parser.last_token_position,
504            block: context.element.clone(),
505        });
506    }
507
508    Ok(GenericIfData::TaggedStruct(tsitems))
509}
510
511#[cfg(feature = "ifdata_cleanup")]
512pub(crate) fn remove_unknown_ifdata(a2l_file: &mut A2lFile) {
513    for module in &mut a2l_file.project.module {
514        remove_unknown_ifdata_from_list(&mut module.if_data);
515
516        if let Some(mod_par) = &mut module.mod_par {
517            for memory_layout in &mut mod_par.memory_layout {
518                remove_unknown_ifdata_from_list(&mut memory_layout.if_data);
519            }
520
521            for memory_segment in &mut mod_par.memory_segment {
522                remove_unknown_ifdata_from_list(&mut memory_segment.if_data);
523            }
524        }
525
526        for axis_pts in &mut module.axis_pts {
527            remove_unknown_ifdata_from_list(&mut axis_pts.if_data);
528        }
529
530        for blob in &mut module.blob {
531            remove_unknown_ifdata_from_list(&mut blob.if_data);
532        }
533
534        for characteristic in &mut module.characteristic {
535            remove_unknown_ifdata_from_list(&mut characteristic.if_data);
536        }
537
538        for frame in &mut module.frame {
539            remove_unknown_ifdata_from_list(&mut frame.if_data);
540        }
541
542        for function in &mut module.function {
543            remove_unknown_ifdata_from_list(&mut function.if_data);
544        }
545
546        for group in &mut module.group {
547            remove_unknown_ifdata_from_list(&mut group.if_data);
548        }
549
550        for instance in &mut module.instance {
551            remove_unknown_ifdata_from_list(&mut instance.if_data);
552        }
553
554        for measurement in &mut module.measurement {
555            remove_unknown_ifdata_from_list(&mut measurement.if_data);
556        }
557    }
558}
559
560#[cfg(feature = "ifdata_cleanup")]
561fn remove_unknown_ifdata_from_list(ifdata_list: &mut Vec<IfData>) {
562    let mut new_ifdata_list = Vec::new();
563    std::mem::swap(ifdata_list, &mut new_ifdata_list);
564    for if_data in new_ifdata_list {
565        if if_data.ifdata_valid {
566            ifdata_list.push(if_data);
567        }
568    }
569}
570
571#[cfg(test)]
572mod ifdata_test {
573    use super::*;
574    use crate::{self as a2lfile, Filename, IfData};
575
576    crate::a2ml_specification! {
577        <A2mlTest>
578
579        block "IF_DATA" taggedunion if_data {
580            "CHAR" char a;
581            "INT" int b;
582            "LONG" long c;
583            "INT64" int64 d;
584            "UCHAR" uchar e;
585            "UINT" uint64 f;
586            "ULONG" ulong g;
587            "UINT64" uint64 h;
588            "DOUBLE" double i;
589            "FLOAT" float j;
590            "STRUCT" struct structname {
591                char[256];
592                int;
593            };
594            block "BLOCK" taggedstruct tagged_struct {
595                "TAG1" int intval;
596            };
597            "ENUM" enum EnumTest {
598                "ENUMVAL1" = 1,
599                "ENUMVAL2"
600            } named_enum;
601            "ARRAY" uint arr[3];
602            block "SEQUENCE" (char[256] name)*;
603            "NONE";
604        };
605    }
606
607    #[test]
608    fn parse_ifdata() {
609        let result = parse_helper(r##"CHAR 5 /end IFDATA"##);
610        assert!(result.is_ok());
611        let decoded_ifdata = check_and_decode(result);
612        assert!(matches!(decoded_ifdata.char, Some(Char { a: 5, .. })));
613
614        let result = parse_helper(r##"CHAR xyz /end IFDATA"##);
615        assert!(result.is_ok());
616        let (data, valid) = result.unwrap();
617        assert!(data.is_some());
618        assert!(!valid);
619
620        let result = parse_helper(r##"INT 5 /end IFDATA"##);
621        assert!(result.is_ok());
622        let decoded_ifdata = check_and_decode(result);
623        assert!(matches!(decoded_ifdata.int, Some(Int { b: 5, .. })));
624
625        let result = parse_helper(r##"INT xyz /end IFDATA"##);
626        assert!(result.is_ok());
627        let (data, valid) = result.unwrap();
628        assert!(data.is_some());
629        assert!(!valid);
630
631        let result = parse_helper(r##"LONG 5 /end IFDATA"##);
632        assert!(result.is_ok());
633        let decoded_ifdata = check_and_decode(result);
634        assert!(matches!(decoded_ifdata.long, Some(Long { c: 5, .. })));
635
636        let result = parse_helper(r##"LONG xyz /end IFDATA"##);
637        assert!(result.is_ok());
638        let (data, valid) = result.unwrap();
639        assert!(data.is_some());
640        assert!(!valid);
641
642        let result = parse_helper(r##"INT64 5 /end IFDATA"##);
643        assert!(result.is_ok());
644        let decoded_ifdata = check_and_decode(result);
645        assert!(matches!(decoded_ifdata.int64, Some(Int64 { d: 5, .. })));
646
647        let result = parse_helper(r##"INT64 xyz /end IFDATA"##);
648        assert!(result.is_ok());
649        let (data, valid) = result.unwrap();
650        assert!(data.is_some());
651        assert!(!valid);
652
653        let result = parse_helper(r##"UCHAR 5 /end IFDATA"##);
654        assert!(result.is_ok());
655        let decoded_ifdata = check_and_decode(result);
656        assert!(matches!(decoded_ifdata.uchar, Some(Uchar { e: 5, .. })));
657
658        let result = parse_helper(r##"UCHAR xyz /end IFDATA"##);
659        assert!(result.is_ok());
660        let (data, valid) = result.unwrap();
661        assert!(data.is_some());
662        assert!(!valid);
663
664        let result = parse_helper(r##"UINT 5 /end IFDATA"##);
665        assert!(result.is_ok());
666        let decoded_ifdata = check_and_decode(result);
667        assert!(matches!(decoded_ifdata.uint, Some(Uint { f: 5, .. })));
668
669        let result = parse_helper(r##"UINT xyz /end IFDATA"##);
670        assert!(result.is_ok());
671        let (data, valid) = result.unwrap();
672        assert!(data.is_some());
673        assert!(!valid);
674
675        let result = parse_helper(r##"ULONG 5 /end IFDATA"##);
676        assert!(result.is_ok());
677        let decoded_ifdata = check_and_decode(result);
678        assert!(matches!(decoded_ifdata.ulong, Some(Ulong { g: 5, .. })));
679
680        let result = parse_helper(r##"ULONG xyz /end IFDATA"##);
681        assert!(result.is_ok());
682        let (data, valid) = result.unwrap();
683        assert!(data.is_some());
684        assert!(!valid);
685
686        let result = parse_helper(r##"UINT64 5 /end IFDATA"##);
687        assert!(result.is_ok());
688        let decoded_ifdata = check_and_decode(result);
689        assert!(matches!(decoded_ifdata.uint64, Some(Uint64 { h: 5, .. })));
690
691        let result = parse_helper(r##"UINT64 xyz /end IFDATA"##);
692        assert!(result.is_ok());
693        let (data, valid) = result.unwrap();
694        assert!(data.is_some());
695        assert!(!valid);
696
697        let result = parse_helper(r##"DOUBLE 5.5 /end IFDATA"##);
698        assert!(result.is_ok());
699        let decoded_ifdata = check_and_decode(result);
700        assert!(matches!(decoded_ifdata.double, Some(Double { .. })));
701
702        let result = parse_helper(r##"DOUBLE xyz /end IFDATA"##);
703        assert!(result.is_ok());
704        let (data, valid) = result.unwrap();
705        assert!(data.is_some());
706        assert!(!valid);
707
708        let result = parse_helper(r##"FLOAT 5.5 /end IFDATA"##);
709        assert!(result.is_ok());
710        let decoded_ifdata = check_and_decode(result);
711        assert!(matches!(decoded_ifdata.float, Some(Float { .. })));
712
713        let result = parse_helper(r##"FLOAT xyz /end IFDATA"##);
714        assert!(result.is_ok());
715        let (data, valid) = result.unwrap();
716        assert!(data.is_some());
717        assert!(!valid);
718
719        let result = parse_helper(r##"STRUCT "text value" 3 /end IFDATA"##);
720        assert!(result.is_ok());
721        let decoded_ifdata = check_and_decode(result);
722        let var_struct = decoded_ifdata.var_struct.unwrap();
723        assert_eq!(var_struct.item, "text value");
724        assert_eq!(var_struct.item_2, 3);
725
726        let result = parse_helper(r##"STRUCT 5.5 xyz /end IFDATA"##);
727        assert!(result.is_ok());
728        let (data, valid) = result.unwrap();
729        assert!(data.is_some());
730        assert!(!valid);
731
732        let result = parse_helper(r##"/begin BLOCK TAG1 3 /end BLOCK /end IFDATA"##);
733        assert!(result.is_ok());
734        let decoded_ifdata = check_and_decode(result);
735        assert_eq!(decoded_ifdata.block.unwrap().tag1.unwrap().intval, 3);
736
737        let result = parse_helper(r##"ENUM ENUMVAL2 /end IFDATA"##);
738        assert!(result.is_ok());
739        let decoded_ifdata = check_and_decode(result);
740        assert_eq!(
741            decoded_ifdata.var_enum.unwrap().named_enum,
742            EnumTest::Enumval2
743        );
744
745        let result = parse_helper(r##"ENUM NOTVALID /end IFDATA"##);
746        assert!(result.is_ok());
747        let (data, valid) = result.unwrap();
748        assert!(data.is_some());
749        assert!(!valid);
750
751        let result = parse_helper(r##"ARRAY 7 8 9 /end IFDATA"##);
752        assert!(result.is_ok());
753        let decoded_ifdata = check_and_decode(result);
754        assert_eq!(decoded_ifdata.array.unwrap().arr, [7, 8, 9]);
755
756        let result = parse_helper(r##"ARRAY 7 8 "bad" /end IFDATA"##);
757        assert!(result.is_ok());
758        let (data, valid) = result.unwrap();
759        assert!(data.is_some());
760        assert!(!valid);
761
762        let result =
763            parse_helper(r##"/begin SEQUENCE "name 1" "name 2" /end SEQUENCE /end IFDATA"##);
764        assert!(result.is_ok());
765        let decoded_ifdata = check_and_decode(result);
766        assert_eq!(decoded_ifdata.sequence.unwrap().item, ["name 1", "name 2"]);
767
768        let result = parse_helper(r##"NONE /end IFDATA"##);
769        assert!(result.is_ok());
770        let decoded_ifdata = check_and_decode(result);
771        assert!(decoded_ifdata.none.is_some());
772    }
773
774    fn parse_helper(ifdata: &str) -> Result<(Option<GenericIfData>, bool), ParserError> {
775        let token_result =
776            a2lfile::tokenizer::tokenize(&Filename::from("test"), 0, ifdata).unwrap();
777        let mut log_msgs = Vec::new();
778        let ifdatas = [ifdata.to_string()];
779        let filenames = [Filename::from("test")];
780        let mut parser = ParserState::new_internal(
781            &token_result.tokens,
782            &ifdatas,
783            &filenames,
784            &mut log_msgs,
785            false,
786        );
787        parser.a2mlspec.push(
788            a2lfile::a2ml::parse_a2ml(&Filename::from("test"), A2MLTEST_TEXT)
789                .unwrap()
790                .0,
791        );
792        super::parse_ifdata(
793            &mut parser,
794            &a2lfile::ParseContext {
795                fileid: 0,
796                line: 0,
797                element: "IFDATA".to_string(),
798            },
799        )
800    }
801
802    fn check_and_decode(result: Result<(Option<GenericIfData>, bool), ParserError>) -> A2mlTest {
803        let (data, valid) = result.unwrap();
804        assert!(data.is_some());
805        assert!(valid);
806        let mut if_data = IfData::new();
807        if_data.ifdata_items = data;
808        if_data.ifdata_valid = valid;
809        A2mlTest::load_from_ifdata(&if_data).unwrap()
810    }
811
812    #[test]
813    fn test_parse_unknown() {
814        // parse unknown valid data
815        let result = parse_helper(
816            r##"abc def ghi /begin AAA 12 23 34 45 "foo" "bar" /end AAA /end IFDATA"##,
817        );
818        assert!(result.is_ok());
819        let (gen_ifdata, valid) = result.unwrap();
820        assert!(!valid);
821        let gen_ifdata = gen_ifdata.unwrap();
822        let (_, _, items) = gen_ifdata.get_block_items().unwrap();
823
824        // by default, IF_DATA is wrapped in a taggedunion. Therefore "abc" is the tag.
825        assert_eq!(items.len(), 1);
826        let taggedunion = &items[0];
827        let opt_tag_content = taggedunion
828            .get_single_optitem("abc", |data, _, _, _| Ok(data.clone()))
829            .unwrap()
830            .unwrap();
831        // the tag conntains a struct with 3 items: "def", "ghi" and a taggedstruct "AAA"
832        let (_, _, items) = opt_tag_content.get_struct_items().unwrap();
833        assert_eq!(items.len(), 3);
834
835        // parse unknown invalid data
836        let result =
837            parse_helper(r##"abc def ghi /begin AAA 12 23 34 45 "foo" "bar" /end IFDATA"##);
838        assert!(result.is_err());
839    }
840
841    #[test]
842    fn test_parse_unknown_with_comments() {
843        // Block comments inside an unknown IF_DATA block should be skipped by the fallback parser.
844
845        // block comment before a /begin block
846        let result = parse_helper(
847            r##"abc /* block comment */ /begin AAA 12 /end AAA /end IFDATA"##,
848        );
849        assert!(result.is_ok());
850        let (gen_ifdata, valid) = result.unwrap();
851        assert!(!valid);
852        assert!(gen_ifdata.is_some());
853
854        // line comment before a /begin block
855        let result = parse_helper(
856            r##"abc // line comment
857            /begin AAA 12 /end AAA /end IFDATA"##,
858        );
859        assert!(result.is_ok());
860        let (gen_ifdata, valid) = result.unwrap();
861        assert!(!valid);
862        assert!(gen_ifdata.is_some());
863
864        // multiple block comments (simulates copyright headers in included files)
865        let result = parse_helper(
866            r##"abc /* first comment */ /* second comment */ /begin AAA 12 /end AAA /end IFDATA"##,
867        );
868        assert!(result.is_ok());
869        let (gen_ifdata, valid) = result.unwrap();
870        assert!(!valid);
871        assert!(gen_ifdata.is_some());
872    }
873}