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