1use 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
43macro_rules! def_parser {
45 ($vis:vis $name:ident(
46 $input_name:ident$(,)? $($arg:ident, $type:ty),*
47 ) -> $ret:ty; $body:tt) => {
48 #[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
61macro_rules! pws {
63 ($inner:expr) => {
64 preceded(multispace0, $inner)
65 };
66}
67macro_rules! dws {
69 ($inner:expr) => {
70 delimited(multispace0, $inner, multispace0)
71 };
72}
73
74macro_rules! optional_ident {
76 () => {
77 _
78 };
79 ($name:ident) => {
80 $name
81 };
82}
83macro_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
108fn span_to_str(span: Span<'_>) -> &str {
110 span.fragment()
111}
112
113def_parser!(ident(input) -> &str; {
115 map(
116 take_while1(|c: char| c.is_alphanum() || c == '_' || c == '-'),
117 span_to_str
118 )(input)
119});
120
121def_parser!(abbreviation_only(input) -> StringValueType; {
123 map(
124 dws!(ident),
125 |v| StringValueType::Abbreviation(v.into())
126 )(input)
127});
128
129def_parser!(bracketed_string(input) -> &str; {
131 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
209def_parser!(entry_type(input) -> &str; {
214 delimited(
215 pws!(_char('@')),
216 pws!(ident),
217 pws!(peek(alt((_char('{'), _char('(')))))
218 )(input)
219});
220
221def_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
238def_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
254def_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
265def_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
277def_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
298def_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
316def_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
326def_parser!(peeked_entry_type(input) -> &str; {
329 peek(entry_type)(input)
330});
331
332def_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
344def_parser!(no_type_comment(input) -> &str; {
346 map(is_not("@"), span_to_str)(input)
347});
348
349def_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
361def_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 rest_entries.insert(0, new_entry);
371 Ok((remaining_slice, rest_entries))
372 }
373});
374
375#[cfg(test)]
376mod tests {
377 use super::*;
382
383 use nom::error::ErrorKind;
384
385 type Error<'a> = (Span<'a>, ErrorKind);
386
387 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_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}