1use 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
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 = ( $( $parser ),* );
101 let (
102 $rest,
103 ( $( optional_ident!($($name)?) ),* )
104 ) = parser.parse($input)?;
105 };
106}
107
108fn span_to_str(span: Span<'_>) -> &str {
110 span.fragment()
111}
112
113def_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
121def_parser!(abbreviation_only(input) -> StringValueType; {
123 map(
124 dws!(ident),
125 |v| StringValueType::Abbreviation(v.into())
126 ).parse(input)
127});
128
129def_parser!(bracketed_string(input) -> &'a 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[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
210def_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
222def_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
239def_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
255def_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
266def_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
278def_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
299def_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
317def_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
327def_parser!(peeked_entry_type(input) -> &'a str; {
330 peek(entry_type).parse(input)
331});
332
333def_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
345def_parser!(no_type_comment(input) -> &'a str; {
347 map(is_not("@"), span_to_str).parse(input)
348});
349
350def_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
362def_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 use super::*;
381
382 use nom::error::ErrorKind;
383
384 type Error<'a> = (Span<'a>, ErrorKind);
385
386 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_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}