nom_bibtex/
parser.rs

1// //! In this module reside all the parsers need for the bibtex format.
2// //!
3// //! All the parsers are using the *nom* crates.
4//
5// // Required because the compiler don't seems do recognize
6// // that macros are use inside of each others..
7//
8use crate::model::{KeyValue, StringValueType};
9use nom::character::complete::char as _char;
10use nom::error::ParseError;
11use nom::IResult;
12use nom::{
13    branch::alt,
14    bytes::complete::{is_not, take_until, take_while1},
15    character::complete::{digit1, multispace0},
16    combinator::{map, opt, peek},
17    multi::{separated_list0, separated_list1},
18    sequence::{delimited, preceded, separated_pair, tuple},
19    AsChar, Slice,
20};
21use nom_locate::LocatedSpan;
22#[cfg(feature = "trace")]
23use nom_tracable::tracable_parser;
24use nom_tracable::TracableInfo;
25use std::num::NonZeroUsize;
26use std::str;
27
28const NEEDED_ONE: nom::Needed = nom::Needed::Size(unsafe { NonZeroUsize::new_unchecked(1) });
29
30pub type Span<'a> = LocatedSpan<&'a str, TracableInfo>;
31pub fn mkspan(s: &str) -> Span<'_> {
32    Span::new_extra(s, TracableInfo::new())
33}
34
35#[derive(Debug, PartialEq, Eq)]
36pub enum Entry {
37    Preamble(Vec<StringValueType>),
38    Comment(String),
39    Variable(KeyValue),
40    Bibliography(String, String, Vec<KeyValue>),
41}
42
43// Defines a parser with a common type signature
44macro_rules! def_parser {
45    ($vis:vis $name:ident(
46        $input_name:ident$(,)? $($arg:ident, $type:ty),*
47    ) -> $ret:ty; $body:tt) => {
48        // NOTE: Hidden behind feature gate because error messages are terrible
49        // with this directive included
50        #[cfg_attr(feature = "trace", tracable_parser)]
51        $vis fn $name<'a, E> (
52            $input_name: Span<'a>, $($arg: $ty),*
53        ) -> IResult<Span<'a>, $ret, E>
54            where E: ParseError<Span<'a>>,
55        {
56            $body
57        }
58    }
59}
60
61// Makes a parser whitespace insensitive before the content
62macro_rules! pws {
63    ($inner:expr) => {
64        preceded(multispace0, $inner)
65    };
66}
67// Makes a parser whitespace insensitive before and after the content
68macro_rules! dws {
69    ($inner:expr) => {
70        delimited(multispace0, $inner, multispace0)
71    };
72}
73
74// Helper macro for the chain_parsers macro.
75macro_rules! optional_ident {
76    () => {
77        _
78    };
79    ($name:ident) => {
80        $name
81    };
82}
83/**
84    Applies a series of parsers, and stores results from some of them
85
86    The first two arguments are the name of the input string, followed
87    by the name to store the rest of the input in after all parsers have
88    been applied
89
90    Example:
91    chain_parsers!{input, rest;
92        parser1 => name1,
93        parser2,
94        parser3 => name3
95    }
96    Ok((rest, (name1, name3)))
97*/
98macro_rules! chain_parsers {
99    ($input:ident, $rest:ident; $( $parser:expr $(=> $name:ident)? ),+) => {
100        let mut parser = tuple(( $( $parser ),* ));
101        let (
102            $rest,
103            ( $( optional_ident!($($name)?) ),* )
104        ) = parser($input)?;
105    };
106}
107
108// Converts a span into a raw string
109fn span_to_str(span: Span<'_>) -> &str {
110    span.fragment()
111}
112
113// Parses a single identifier
114def_parser!(ident(input) -> &str; {
115    map(
116        take_while1(|c: char| c.is_alphanum() || c == '_' || c == '-'),
117        span_to_str
118    )(input)
119});
120
121// Parses an abbreviation: An identifier that can be surrounded by whitespace
122def_parser!(abbreviation_only(input) -> StringValueType; {
123    map(
124        dws!(ident),
125        |v| StringValueType::Abbreviation(v.into())
126    )(input)
127});
128
129// Only used for bibliography tags.
130def_parser!(bracketed_string(input) -> &str; {
131    // We are not in a bracketed_string.
132    match input.fragment().chars().next() {
133        Some('{') => {},
134        Some(_) => {
135            return Err(nom::Err::Error(E::from_char(input, '{')));
136        }
137        None => {
138            return Err(nom::Err::Incomplete(NEEDED_ONE));
139        }
140    }
141
142    let mut brackets_queue = 0;
143
144    let mut last_idx = 0;
145    for (i, c) in input.fragment().char_indices().skip(1) {
146        last_idx = i+1;
147        match c {
148            '{' => brackets_queue += 1,
149            '}' => if brackets_queue == 0 {
150                break;
151            } else {
152                brackets_queue -= 1;
153            },
154            _ => continue,
155        }
156    }
157    Ok((
158        input.slice(last_idx..),
159        span_to_str(input.slice(1..last_idx-1)).trim()
160    ))
161});
162
163def_parser!(quoted_string(input) -> &str; {
164    match input.fragment().chars().next() {
165        Some('"') => {},
166        Some(_) => {
167            return Err(nom::Err::Error(E::from_char(input, '"')));
168        }
169        None => {
170            return Err(nom::Err::Incomplete(NEEDED_ONE));
171        }
172    }
173    let mut brackets_queue = 0;
174    let mut last_idx = 0;
175    for (i, c) in input.fragment().char_indices().skip(1) {
176        last_idx = i+1;
177        match c {
178            '{' => brackets_queue += 1,
179            '}' => {
180                brackets_queue -= 1;
181                if brackets_queue < 0 {
182                    return Err(nom::Err::Error(E::from_char(input, '"')));
183                }
184            }
185            '"' => if brackets_queue == 0 {
186                break;
187            },
188            _ => continue,
189        }
190    }
191    Ok((
192        input.slice(last_idx..),
193        span_to_str(input.slice(1..last_idx-1))
194    ))
195});
196
197def_parser!(pub abbreviation_string(input) -> Vec<StringValueType>; {
198    separated_list1(
199        pws!(_char('#')),
200        pws!(
201            alt((
202                abbreviation_only,
203                map(quoted_string, |v: &str| StringValueType::Str(v.into()))
204            ))
205        )
206    )(input)
207});
208
209// Parse a bibtex entry type which looks like:
210// @type{ ...
211//
212// But don't consume the last bracket.
213def_parser!(entry_type(input) -> &str; {
214    delimited(
215        pws!(_char('@')),
216        pws!(ident),
217        pws!(peek(alt((_char('{'), _char('(')))))
218    )(input)
219});
220
221// Parse key value pair which has the form:
222// key="value"
223def_parser!(variable_key_value_pair(input) -> KeyValue; {
224    map(
225        separated_pair(
226            pws!(ident),
227            dws!(_char('=')),
228            alt((
229                map(quoted_string, |v: &str| vec!(StringValueType::Str(v.into()))),
230                abbreviation_string,
231                map(abbreviation_only, |v| vec!(v)),
232            ))
233        ),
234        |v: (&str, Vec<StringValueType>)| KeyValue::new(v.0.into(), v.1)
235    )(input)
236});
237
238// String variable can be delimited by brackets or parenthesis.
239def_parser!(handle_variable(input) -> KeyValue; {
240    alt((
241        delimited(
242            pws!(_char('{')),
243            dws!(variable_key_value_pair),
244            peek(_char('}'))
245        ),
246        delimited(
247            pws!(_char('(')),
248            dws!(variable_key_value_pair),
249            peek(_char(')'))
250        )
251    ))(input)
252});
253
254// Handle a string variable from the bibtex format:
255// @String (key = "value") or @String {key = "value"}
256def_parser!(variable(input) -> Entry; {
257    chain_parsers!(input, rest;
258        entry_type,
259        handle_variable => key_val,
260        alt((_char('}'), _char(')')))
261    );
262    Ok((rest, Entry::Variable(key_val)))
263});
264
265// Handle a preamble of the format:
266// @Preamble { "my preamble" }
267def_parser!(preamble(input) -> Entry; {
268    chain_parsers!(input, rest;
269        entry_type,
270        pws!(_char('{')),
271        abbreviation_string => preamble,
272        pws!(_char('}'))
273    );
274    Ok((rest, Entry::Preamble(preamble)))
275});
276
277// Parse all the tags used by one bibliography entry separated by a comma.
278def_parser!(bib_tags(input) -> Vec<KeyValue>; {
279    separated_list0(
280        dws!(_char(',')),
281        map(
282            separated_pair(
283                ident,
284                dws!(_char('=')),
285                alt((
286                    map(digit1, |v| vec!(StringValueType::Str(span_to_str(v).into()))),
287                    abbreviation_string,
288                    map(quoted_string, |v| vec![StringValueType::Str(v.into())]),
289                    map(bracketed_string, |v| vec![StringValueType::Str(v.into())]),
290                    map(abbreviation_only, |v| vec![v]),
291                ))
292            ),
293            |v: (&str, Vec<StringValueType>)| KeyValue::new(v.0.into(), v.1)
294        )
295    )(input)
296});
297
298// Handle a bibliography entry of the format:
299// @entry_type { citation_key,
300//     tag1,
301//     tag2
302// }
303def_parser!(bibliography_entry(input) -> Entry; {
304    chain_parsers! (input, rem;
305        entry_type => entry_t ,
306        dws!(_char('{')),
307        map(take_until(","), span_to_str) => citation_key,
308        dws!(_char(',')),
309        bib_tags => tags ,
310        opt(pws!(_char(','))),
311        pws!(_char('}'))
312    );
313    Ok((rem, Entry::Bibliography(entry_t.into(), citation_key.into(), tags)))
314});
315
316// Handle a comment of the format:
317// @Comment { my comment }
318def_parser!(type_comment(input) -> Entry; {
319    chain_parsers!(input, rem;
320        entry_type,
321        bracketed_string => comment
322    );
323    Ok((rem, Entry::Comment(comment.into())))
324});
325
326// Same as entry_type but with peek so it doesn't consume the
327// entry type.
328def_parser!(peeked_entry_type(input) -> &str; {
329    peek(entry_type)(input)
330});
331
332// Parse any entry which starts with a @.
333def_parser!(entry_with_type(input) -> Entry; {
334    let entry_type = peeked_entry_type::<E>(input)?;
335
336    match entry_type.1.to_lowercase().as_ref() {
337        "comment" => type_comment(input),
338        "string" => variable(input),
339        "preamble" => preamble(input),
340        _ => bibliography_entry(input),
341    }
342});
343
344// Handle data beginning without an @ which are considered comments.
345def_parser!(no_type_comment(input) -> &str; {
346    map(is_not("@"), span_to_str)(input)
347});
348
349// Parse any entry in a bibtex file.
350// A good entry starts with a @ otherwise, it's
351// considered as a comment.
352def_parser!(entry(input) -> Entry; {
353    pws!(
354        alt((
355            entry_with_type,
356            map(no_type_comment, |v| Entry::Comment(v.to_string().trim().into()))
357        ))
358    )(input)
359});
360
361// Parses a whole bibtex file to yield a list of entries
362def_parser!(pub entries(input) -> Vec<Entry>; {
363    if input.fragment().trim().is_empty() {
364        Ok((input, vec!()))
365    }
366    else {
367        let (rest_slice, new_entry) = entry(input)?;
368        let (remaining_slice, mut rest_entries) = entries(rest_slice)?;
369        // NOTE: O(n) insertions, could cause issues in the future
370        rest_entries.insert(0, new_entry);
371        Ok((remaining_slice, rest_entries))
372    }
373});
374
375#[cfg(test)]
376mod tests {
377    // Each time we are using `separated_list`, we need to add a trailing
378    // character otherwise the parser will return `IResult::Incomplete`.
379    // Relevant nom issue: https://github.com/Geal/nom/issues/505
380
381    use super::*;
382
383    use nom::error::ErrorKind;
384
385    type Error<'a> = (Span<'a>, ErrorKind);
386
387    // Convenience macro to convert a Span<&str> to an &str which is required
388    // because `PartialEq` on spans differenciate between offsets. For asserts
389    // to work as expected, this macro can be used instead
390    macro_rules! str_err {
391        ($val:expr) => {
392            $val.map(|(span, parse)| (span_to_str(span), parse))
393        };
394    }
395
396    #[test]
397    fn test_entry() {
398        assert_eq!(
399            str_err!(entry::<Error>(mkspan(" comment"))),
400            Ok(("", Entry::Comment("comment".to_string())))
401        );
402
403        let kv = KeyValue::new(
404            "key".to_string(),
405            vec![StringValueType::Str("value".to_string())],
406        );
407        assert_eq!(
408            str_err!(entry::<Error>(mkspan(" @ StrIng { key = \"value\" }"))),
409            Ok(("", Entry::Variable(kv)))
410        );
411
412        let bib_str = "@misc{ patashnik-bibtexing,
413           author = \"Oren Patashnik\",
414           title = \"BIBTEXing\",
415           year = \"1988\" }";
416
417        let tags = vec![
418            KeyValue::new(
419                "author".to_string(),
420                vec![StringValueType::Str("Oren Patashnik".to_string())],
421            ),
422            KeyValue::new(
423                "title".to_string(),
424                vec![StringValueType::Str("BIBTEXing".to_string())],
425            ),
426            KeyValue::new(
427                "year".to_string(),
428                vec![StringValueType::Str("1988".to_string())],
429            ),
430        ];
431        assert_eq!(
432            str_err!(entry_with_type::<Error>(mkspan(bib_str))),
433            Ok((
434                "",
435                Entry::Bibliography("misc".to_string(), "patashnik-bibtexing".to_string(), tags)
436            ))
437        );
438    }
439
440    #[test]
441    fn test_entry_with_journal() {
442        assert_eq!(
443            str_err!(entry::<Error>(mkspan(" comment"))),
444            Ok(("", Entry::Comment("comment".to_string())))
445        );
446
447        let kv = KeyValue::new(
448            "key".to_string(),
449            vec![StringValueType::Str("value".to_string())],
450        );
451        assert_eq!(
452            str_err!(entry::<Error>(mkspan(" @ StrIng { key = \"value\" }"))),
453            Ok(("", Entry::Variable(kv)))
454        );
455
456        let bib_str = "@misc{ patashnik-bibtexing,
457           author = \"Oren Patashnik\",
458           title = \"BIBTEXing\",
459           journal = SOME_ABBREV,
460           year = \"1988\" }";
461
462        let tags = vec![
463            KeyValue::new(
464                "author".to_string(),
465                vec![StringValueType::Str("Oren Patashnik".to_string())],
466            ),
467            KeyValue::new(
468                "title".to_string(),
469                vec![StringValueType::Str("BIBTEXing".to_string())],
470            ),
471            KeyValue::new(
472                "journal".to_string(),
473                vec![StringValueType::Abbreviation("SOME_ABBREV".to_string())],
474            ),
475            KeyValue::new(
476                "year".to_string(),
477                vec![StringValueType::Str("1988".to_string())],
478            ),
479        ];
480        assert_eq!(
481            str_err!(entry_with_type::<Error>(mkspan(bib_str))),
482            Ok((
483                "",
484                Entry::Bibliography("misc".to_string(), "patashnik-bibtexing".to_string(), tags)
485            ))
486        );
487    }
488
489    #[test]
490    fn test_no_type_comment() {
491        assert_eq!(
492            str_err!(no_type_comment::<Error>(mkspan("test@"))),
493            Ok(("@", "test"))
494        );
495        assert_eq!(
496            str_err!(no_type_comment::<Error>(mkspan("test"))),
497            Ok(("", "test"))
498        );
499    }
500
501    #[test]
502    fn test_entry_with_type() {
503        assert_eq!(
504            str_err!(entry_with_type::<Error>(mkspan("@Comment{test}"))),
505            Ok(("", Entry::Comment("test".to_string())))
506        );
507
508        let kv = KeyValue::new(
509            "key".to_string(),
510            vec![StringValueType::Str("value".to_string())],
511        );
512        assert_eq!(
513            str_err!(entry_with_type::<Error>(mkspan("@String{key=\"value\"}"))),
514            Ok(("", Entry::Variable(kv)))
515        );
516
517        assert_eq!(
518            str_err!(entry_with_type::<Error>(mkspan(
519                "@preamble{name # \"'s preamble\"}"
520            ))),
521            Ok((
522                "",
523                Entry::Preamble(vec![
524                    StringValueType::Abbreviation("name".to_string()),
525                    StringValueType::Str("'s preamble".to_string())
526                ])
527            ))
528        );
529
530        let bib_str = "@misc{ patashnik-bibtexing,
531           author = \"Oren Patashnik\",
532           title = \"BIBTEXing\",
533           year = \"1988\" }";
534
535        let tags = vec![
536            KeyValue::new(
537                "author".to_string(),
538                vec![StringValueType::Str("Oren Patashnik".to_string())],
539            ),
540            KeyValue::new(
541                "title".to_string(),
542                vec![StringValueType::Str("BIBTEXing".to_string())],
543            ),
544            KeyValue::new(
545                "year".to_string(),
546                vec![StringValueType::Str("1988".to_string())],
547            ),
548        ];
549        assert_eq!(
550            str_err!(entry_with_type::<Error>(mkspan(bib_str))),
551            Ok((
552                "",
553                Entry::Bibliography("misc".to_string(), "patashnik-bibtexing".to_string(), tags)
554            ))
555        );
556    }
557
558    #[test]
559    fn test_entry_with_type_and_spaces() {
560        let kv = KeyValue::new(
561            "key".to_string(),
562            vec![StringValueType::Str("value".to_string())],
563        );
564        assert_eq!(
565            str_err!(entry_with_type::<Error>(mkspan("@ String{key=\"value\"}"))),
566            Ok(("", Entry::Variable(kv)))
567        );
568    }
569
570    #[test]
571    fn test_type_comment() {
572        let parse = type_comment::<Error>(mkspan("@Comment{test}"));
573
574        assert_eq!(
575            str_err!(parse),
576            Ok(("", Entry::Comment("test".to_string())))
577        );
578    }
579
580    #[test]
581    fn test_preamble() {
582        assert_eq!(
583            str_err!(preamble::<Error>(mkspan("@preamble{\"my preamble\"}"))),
584            Ok((
585                "",
586                Entry::Preamble(vec![StringValueType::Str("my preamble".to_string())])
587            ))
588        );
589    }
590
591    #[test]
592    fn test_variable() {
593        let kv1 = KeyValue::new(
594            "key".to_string(),
595            vec![StringValueType::Str("value".to_string())],
596        );
597        let kv2 = KeyValue::new(
598            "key".to_string(),
599            vec![StringValueType::Str("value".to_string())],
600        );
601        let kv3 = KeyValue::new(
602            "key".to_string(),
603            vec![
604                StringValueType::Abbreviation("varone".to_string()),
605                StringValueType::Abbreviation("vartwo".to_string()),
606            ],
607        );
608
609        assert_eq!(
610            str_err!(variable::<Error>(mkspan("@string{key=\"value\"}"))),
611            Ok(("", Entry::Variable(kv1)))
612        );
613
614        assert_eq!(
615            str_err!(variable::<Error>(mkspan("@string( key=\"value\" )"))),
616            Ok(("", Entry::Variable(kv2)))
617        );
618
619        assert_eq!(
620            str_err!(variable::<Error>(mkspan("@string( key=varone # vartwo)"))),
621            Ok(("", Entry::Variable(kv3)))
622        );
623    }
624
625    #[test]
626    fn test_variable_key_value_pair() {
627        let kv = KeyValue::new(
628            "key".to_string(),
629            vec![
630                StringValueType::Abbreviation("varone".to_string()),
631                StringValueType::Abbreviation("vartwo".to_string()),
632            ],
633        );
634
635        assert_eq!(
636            str_err!(variable_key_value_pair::<Error>(mkspan(
637                "key = varone # vartwo,"
638            ))),
639            Ok((",", kv))
640        );
641    }
642
643    #[test]
644    fn test_bibliography_entry() {
645        let bib_str = "@misc{ patashnik-bibtexing,
646           author = \"Oren Patashnik\",
647           title = \"BIBTEXing\",
648           year = \"1988\", }";
649
650        let tags = vec![
651            KeyValue::new(
652                "author".to_string(),
653                vec![StringValueType::Str("Oren Patashnik".to_string())],
654            ),
655            KeyValue::new(
656                "title".to_string(),
657                vec![StringValueType::Str("BIBTEXing".to_string())],
658            ),
659            KeyValue::new(
660                "year".to_string(),
661                vec![StringValueType::Str("1988".to_string())],
662            ),
663        ];
664        assert_eq!(
665            str_err!(bibliography_entry::<Error>(mkspan(bib_str))),
666            Ok((
667                "",
668                Entry::Bibliography("misc".to_string(), "patashnik-bibtexing".to_string(), tags)
669            ))
670        );
671    }
672    #[test]
673    fn test_bibliography_entry_works_with_bracketed_strings_at_end() {
674        let bib_str = "@misc{ patashnik-bibtexing,
675           year = {1988}}";
676
677        let tags = vec![KeyValue::new(
678            "year".to_string(),
679            vec![StringValueType::Str("1988".to_string())],
680        )];
681        assert_eq!(
682            str_err!(bibliography_entry::<Error>(mkspan(bib_str))),
683            Ok((
684                "",
685                Entry::Bibliography("misc".to_string(), "patashnik-bibtexing".to_string(), tags)
686            ))
687        );
688    }
689
690    #[test]
691    fn test_bib_tags() {
692        let tags_str = "author= \"Oren Patashnik\",
693            year=1988,
694            note= var # \"str\",
695            title= { My new book }}";
696
697        let result = vec![
698            KeyValue::new(
699                "author".to_string(),
700                vec![StringValueType::Str("Oren Patashnik".to_string())],
701            ),
702            KeyValue::new(
703                "year".to_string(),
704                vec![StringValueType::Str("1988".to_string())],
705            ),
706            KeyValue::new(
707                "note".to_string(),
708                vec![
709                    StringValueType::Abbreviation("var".to_string()),
710                    StringValueType::Str("str".to_string()),
711                ],
712            ),
713            KeyValue::new(
714                "title".to_string(),
715                vec![StringValueType::Str("My new book".to_string())],
716            ),
717        ];
718        assert_eq!(
719            str_err!(bib_tags::<Error>(mkspan(tags_str))),
720            Ok(("}", result))
721        );
722    }
723
724    #[test]
725    fn test_abbreviation_string() {
726        assert_eq!(
727            str_err!(abbreviation_string::<Error>(mkspan("var # \"string\","))),
728            Ok((
729                ",",
730                vec![
731                    StringValueType::Abbreviation("var".to_string()),
732                    StringValueType::Str("string".to_string()),
733                ]
734            ))
735        );
736        assert_eq!(
737            str_err!(abbreviation_string::<Error>(mkspan("\"string\" # var,"))),
738            Ok((
739                ",",
740                vec![
741                    StringValueType::Str("string".to_string()),
742                    StringValueType::Abbreviation("var".to_string()),
743                ]
744            ))
745        );
746        assert_eq!(
747            str_err!(abbreviation_string::<Error>(mkspan("string # var,"))),
748            Ok((
749                ",",
750                vec![
751                    StringValueType::Abbreviation("string".to_string()),
752                    StringValueType::Abbreviation("var".to_string()),
753                ]
754            ))
755        );
756    }
757
758    #[test]
759    fn test_abbreviation_string_does_not_match_multiple_bare_words() {
760        assert_eq!(
761            str_err!(abbreviation_string::<()>(mkspan("var string"))),
762            Ok((
763                "string",
764                vec![StringValueType::Abbreviation("var".to_string())]
765            ))
766        );
767    }
768
769    #[test]
770    fn test_abbreviation_only() {
771        assert_eq!(
772            str_err!(abbreviation_only::<Error>(mkspan(" var "))),
773            Ok(("", StringValueType::Abbreviation("var".to_string())))
774        );
775    }
776
777    #[test]
778    fn test_abbreviation_with_underscore() {
779        assert_eq!(
780            str_err!(abbreviation_only::<Error>(mkspan(" IEEE_J_CAD "))),
781            Ok(("", StringValueType::Abbreviation("IEEE_J_CAD".to_string())))
782        );
783    }
784
785    #[test]
786    fn test_bracketed_string() {
787        assert_eq!(
788            str_err!(bracketed_string::<Error>(mkspan("{ test }"))),
789            Ok(("", "test"))
790        );
791        assert_eq!(
792            str_err!(bracketed_string::<Error>(mkspan("{ test word}"))),
793            Ok(("", "test word"))
794        );
795        assert_eq!(
796            str_err!(bracketed_string::<Error>(mkspan("{ {test} }"))),
797            Ok(("", "{test}"))
798        );
799        // assert!(bracketed_string::<Error>(mkspan("{ @{test} }")).is_err());
800
801        assert_eq!(
802            str_err!(bracketed_string::<Error>(mkspan("{True: love and @jlo}"))),
803            Ok(("", "True: love and @jlo"))
804        );
805
806        assert_eq!(
807            str_err!(bracketed_string::<Error>(mkspan(
808                "{True: love and \"Trump\"}"
809            ))),
810            Ok(("", "True: love and \"Trump\""))
811        );
812    }
813
814    #[test]
815    fn test_bracketed_string_takes_the_correct_amount_of_brackets() {
816        assert_eq!(
817            str_err!(bracketed_string::<Error>(mkspan("{ test }} } }"))),
818            Ok(("} } }", "test"))
819        );
820    }
821
822    #[test]
823    fn test_quoted_string() {
824        assert_eq!(
825            str_err!(quoted_string::<Error>(mkspan("\"test\""))),
826            Ok(("", "test"))
827        );
828        assert_eq!(
829            str_err!(quoted_string::<Error>(mkspan("\"test \""))),
830            Ok(("", "test "))
831        );
832        assert_eq!(
833            str_err!(quoted_string::<Error>(mkspan("\"{\"test\"}\""))),
834            Ok(("", "{\"test\"}"))
835        );
836        assert_eq!(
837            str_err!(quoted_string::<Error>(mkspan(
838                "\"A {bunch {of} braces {in}} title\""
839            ))),
840            Ok(("", "A {bunch {of} braces {in}} title"))
841        );
842        assert_eq!(
843            str_err!(quoted_string::<Error>(mkspan(
844                "\"Simon {\"}the {saint\"} Templar\""
845            ))),
846            Ok(("", "Simon {\"}the {saint\"} Templar"))
847        );
848    }
849
850    #[test]
851    fn test_variable_with_underscore() {
852        let kv1 = KeyValue::new(
853            "IEEE_J_ANNE".to_string(),
854            vec![StringValueType::Str(
855                "{IEEE} Trans. Aeronaut. Navig. Electron.".to_string(),
856            )],
857        );
858
859        assert_eq!(
860            str_err!(variable::<Error>(mkspan(
861                "@string{IEEE_J_ANNE       = \"{IEEE} Trans. Aeronaut. Navig. Electron.\"}"
862            ))),
863            Ok(("", Entry::Variable(kv1)))
864        );
865    }
866
867    #[test]
868    fn test_dashes_in_variables_are_supported() {
869        let kv1 = KeyValue::new(
870            "IEEE_J_B-ME".to_string(),
871            vec![StringValueType::Str(
872                "{IEEE} Trans. Bio-Med. Eng.".to_string(),
873            )],
874        );
875
876        assert_eq!(
877            str_err!(variable::<Error>(mkspan(
878                "@STRING{IEEE_J_B-ME       = \"{IEEE} Trans. Bio-Med. Eng.\"}"
879            ))),
880            Ok(("", Entry::Variable(kv1)))
881        );
882
883        assert_eq!(
884            str_err!(abbreviation_only::<Error>(mkspan(" IEE_j_B-ME "))),
885            Ok(("", StringValueType::Abbreviation("IEE_j_B-ME".to_string())))
886        );
887    }
888
889    #[test]
890    fn malformed_entries_produce_errors() {
891        let bib_str = "
892            @Article{coussy_et_al_word_length_HLS,
893              author    = {Philippe Coussy and Ghizlane Lhairech-Lebreton and Dominique Heller},
894              title     = {Multiple Word-Length High-Level Synthesis},
895              journal   = {{EURASIP} Journal on Embedded Systems},
896              year      = {2008},
897              volume    = {2008},
898              number    = {1},
899              pages     = {916867},
900              month     = jul,
901              issn      = {1687-3963},
902              day       = {29},
903              doi       = {10.1155/2008/916867},
904              publisher = {Springer Nature},
905            }
906
907            @Article{constantinides_word_length_optimization,
908              author     = {Constantinides, George A.},
909              title      = {Word-length Optimization for Differentiable Nonlinear Systems},
910              journal    = {ACM Trans. Des. Autom. Electron. Syst.},
911              year       = {2006},
912              volume     = {11},
913              number     = {1},
914              pages      = {26--43},
915              month      = jan,
916              issn       = {1084-4309},
917              acmid      = {1124716},
918              address    = {New York, NY, USA},
919              doi        = {http://dx.doi.org/10.1145/1124713.1124716},
920              issue_d
921              keywords   = {Signal processing, bitwidth, synthesis,
922              numpages   = {18},
923              publisher  = {ACM},
924            }";
925
926        assert!(
927            entries::<Error>(mkspan(bib_str)).is_err(),
928            "Malformed entries list parsed correctly"
929        );
930    }
931
932    #[test]
933    fn months_file_parses_without_error() {
934        let file = "
935            @STRING{ dec = \"December\" }
936            ";
937        entries::<Error>(mkspan(file)).unwrap();
938    }
939}