1use itertools::Itertools;
2use std::{
3 collections::HashMap,
4 fmt::Display,
5 fs::File,
6 io::Read,
7 num::{NonZero, TryFromIntError},
8 path::PathBuf,
9};
10use thiserror::Error;
11
12type Word = u32;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct FileId(Word);
17
18impl Display for FileId {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 self.0.fmt(f)
21 }
22}
23
24impl FileId {
25 pub fn new(id: Word) -> Self {
26 Self(id)
27 }
28}
29
30impl From<Word> for FileId {
31 fn from(value: Word) -> Self {
32 Self(value)
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub struct PositionId(Word);
39
40impl PositionId {
41 pub fn new(id: Word) -> Self {
42 Self(id)
43 }
44
45 pub fn value(&self) -> Word {
46 self.0
47 }
48}
49
50impl From<Word> for PositionId {
51 fn from(value: Word) -> Self {
52 Self(value)
53 }
54}
55
56impl TryFrom<u64> for PositionId {
57 type Error = TryFromIntError;
58
59 fn try_from(value: u64) -> Result<Self, Self::Error> {
60 let v: u32 = value.try_into()?;
61 Ok(Self(v))
62 }
63}
64
65impl Display for PositionId {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 self.0.fmt(f)
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct LineNum(NonZero<Word>);
74
75impl LineNum {
76 pub fn new(line: Word) -> Self {
77 Self(NonZero::new(line).expect("Line number must be non-zero"))
78 }
79 pub fn as_usize(&self) -> usize {
80 self.0.get() as usize
81 }
82}
83
84impl Display for LineNum {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 self.0.fmt(f)
87 }
88}
89
90#[derive(Error)]
91#[error("Line number cannot be zero")]
92pub struct LineNumCreationError;
93
94impl std::fmt::Debug for LineNumCreationError {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 std::fmt::Display::fmt(self, f)
97 }
98}
99
100impl TryFrom<Word> for LineNum {
101 type Error = LineNumCreationError;
102
103 fn try_from(value: Word) -> Result<Self, Self::Error> {
104 if value != 0 {
105 Ok(Self(NonZero::new(value).unwrap()))
106 } else {
107 Err(LineNumCreationError)
108 }
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
114pub struct MemoryLocationId(Word);
115
116impl TryFrom<u64> for MemoryLocationId {
117 type Error = TryFromIntError;
118
119 fn try_from(value: u64) -> Result<Self, Self::Error> {
120 let v: u32 = value.try_into()?;
121 Ok(Self(v))
122 }
123}
124
125impl From<Word> for MemoryLocationId {
126 fn from(value: Word) -> Self {
127 Self(value)
128 }
129}
130
131impl Display for MemoryLocationId {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
133 <Word as Display>::fmt(&self.0, f)
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct MemoryLocation {
139 pub cell: String,
140 pub address: Vec<usize>,
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
145pub struct VariableAssignmentId(Word);
146
147impl Display for VariableAssignmentId {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
149 <Word as Display>::fmt(&self.0, f)
150 }
151}
152impl TryFrom<u64> for VariableAssignmentId {
153 type Error = TryFromIntError;
154
155 fn try_from(value: u64) -> Result<Self, Self::Error> {
156 let v: u32 = value.try_into()?;
157 Ok(Self(v))
158 }
159}
160#[derive(Debug, Clone, PartialEq, Eq, Default)]
161pub struct SourceInfoTable {
162 file_map: HashMap<FileId, PathBuf>,
164 position_map: HashMap<PositionId, SourceLocation>,
166 mem_location_map: HashMap<MemoryLocationId, MemoryLocation>,
168 variable_assignment_map:
170 HashMap<VariableAssignmentId, HashMap<String, MemoryLocationId>>,
171 position_state_map: HashMap<PositionId, VariableAssignmentId>,
174}
175
176impl SourceInfoTable {
177 const HEADER: &str = "sourceinfo";
178
179 pub fn lookup_file_path(&self, file: FileId) -> &PathBuf {
184 &self.file_map[&file]
185 }
186
187 pub fn lookup_position(&self, pos: PositionId) -> &SourceLocation {
192 &self.position_map[&pos]
193 }
194
195 pub fn get_position(&self, pos: PositionId) -> Option<&SourceLocation> {
198 self.position_map.get(&pos)
199 }
200
201 pub fn iter_file_map(&self) -> impl Iterator<Item = (&FileId, &PathBuf)> {
204 self.file_map.iter()
205 }
206
207 pub fn iter_file_paths(&self) -> impl Iterator<Item = &PathBuf> {
209 self.file_map.values()
210 }
211
212 pub fn iter_file_ids(&self) -> impl Iterator<Item = FileId> + '_ {
214 self.file_map.keys().copied()
215 }
216
217 pub fn iter_position_map(
220 &self,
221 ) -> impl Iterator<Item = (&PositionId, &SourceLocation)> {
222 self.position_map.iter()
223 }
224
225 pub fn iter_positions(&self) -> impl Iterator<Item = PositionId> + '_ {
227 self.position_map.keys().copied()
228 }
229
230 pub fn iter_source_locations(
232 &self,
233 ) -> impl Iterator<Item = &SourceLocation> {
234 self.position_map.values()
235 }
236
237 pub fn add_file(&mut self, file: FileId, path: PathBuf) {
239 self.file_map.insert(file, path);
240 }
241
242 pub fn push_file(&mut self, path: PathBuf) -> FileId {
246 let max = self.iter_file_ids().max().unwrap_or(0.into());
248 let new = FileId(max.0 + 1);
249
250 self.add_file(new, path);
251 new
252 }
253 pub fn add_position(
254 &mut self,
255 pos: PositionId,
256 file: FileId,
257 line: LineNum,
258 ) {
259 self.position_map
260 .insert(pos, SourceLocation::new(file, line));
261 }
262
263 pub fn push_position(&mut self, file: FileId, line: LineNum) -> PositionId {
267 let max = self.iter_positions().max().unwrap_or(0.into());
269 let new = PositionId(max.0 + 1);
270
271 self.add_position(new, file, line);
272 new
273 }
274
275 pub fn add_location(&mut self, id: MemoryLocationId, info: MemoryLocation) {
276 self.mem_location_map.insert(id, info);
277 }
278
279 pub fn get_variable_mapping(
282 &self,
283 pos: PositionId,
284 ) -> Option<&HashMap<String, MemoryLocationId>> {
285 self.position_state_map
286 .get(&pos)
287 .and_then(|x| self.variable_assignment_map.get(x))
288 }
289
290 pub fn get_memory_location(
291 &self,
292 loc: &MemoryLocationId,
293 ) -> &MemoryLocation {
294 &self.mem_location_map[loc]
295 }
296
297 pub fn new_empty() -> Self {
299 Self {
300 file_map: HashMap::new(),
301 position_map: HashMap::new(),
302 mem_location_map: HashMap::new(),
303 variable_assignment_map: HashMap::new(),
304 position_state_map: HashMap::new(),
305 }
306 }
307
308 pub fn new_minimal(
311 files: impl IntoIterator<Item = (FileId, PathBuf)>,
312 positions: impl IntoIterator<Item = (PositionId, FileId, LineNum)>,
313 ) -> SourceInfoResult<Self> {
314 let loc: Vec<(MemoryLocationId, MemoryLocation)> = vec![];
317 let states: Vec<(PositionId, VariableAssignmentId)> = vec![];
318 let variable_assigns: Vec<(
319 VariableAssignmentId,
320 Vec<(String, MemoryLocationId)>,
321 )> = vec![];
322
323 Self::new(files, positions, loc, variable_assigns, states)
324 }
325
326 pub fn new(
328 files: impl IntoIterator<Item = (FileId, PathBuf)>,
329 positions: impl IntoIterator<Item = (PositionId, FileId, LineNum)>,
330 locations: impl IntoIterator<Item = (MemoryLocationId, MemoryLocation)>,
331 variable_assigns: impl IntoIterator<
332 Item = (
333 VariableAssignmentId,
334 impl IntoIterator<Item = (String, MemoryLocationId)>,
335 ),
336 >,
337 states: impl IntoIterator<Item = (PositionId, VariableAssignmentId)>,
338 ) -> SourceInfoResult<Self> {
339 let files = files.into_iter();
340 let positions = positions.into_iter();
341 let locations = locations.into_iter();
342 let vars = variable_assigns.into_iter();
343 let states = states.into_iter();
344
345 let mut file_map = HashMap::with_capacity(
346 files.size_hint().1.unwrap_or(files.size_hint().0),
347 );
348 let mut position_map = HashMap::with_capacity(
349 positions.size_hint().1.unwrap_or(positions.size_hint().0),
350 );
351
352 let mut memory_location_map: HashMap<MemoryLocationId, MemoryLocation> =
353 HashMap::new();
354
355 let mut variable_map = HashMap::new();
356 let mut state_map = HashMap::new();
357
358 for (file, path) in files {
359 if let Some(first_path) = file_map.insert(file, path) {
360 let inserted_path = &file_map[&file];
361 if &first_path != inserted_path {
362 return Err(SourceInfoTableError::DuplicateFiles {
363 id1: file,
364 path1: first_path,
365 path2: inserted_path.clone(),
366 });
367 }
368 }
369 }
370
371 for (pos, file, line) in positions {
372 let source = SourceLocation::new(file, line);
373 if let Some(first_pos) = position_map.insert(pos, source) {
374 let inserted_position = &position_map[&pos];
375 if inserted_position != &first_pos {
376 return Err(SourceInfoTableError::DuplicatePositions {
377 pos,
378 s1: first_pos,
379 s2: position_map[&pos].clone(),
380 });
381 }
382 }
383 }
384
385 for (id, loc) in locations {
386 if memory_location_map.insert(id, loc).is_some() {
387 return Err(SourceInfoTableError::DuplicateMemoryIdentifiers {
389 id,
390 });
391 }
392 }
393
394 for (assign_label, assigns) in vars {
395 let mut mapping = HashMap::new();
396 for (name, location) in assigns {
397 #[allow(clippy::map_entry)]
404 if !memory_location_map.contains_key(&location) {
405 return Err(SourceInfoTableError::UnknownMemoryId {
407 id: location,
408 });
409 } else if mapping.contains_key(&name) {
410 return Err(
412 SourceInfoTableError::DuplicateVariableAssignments {
413 id: assign_label,
414 var: name,
415 },
416 );
417 } else {
418 mapping.insert(name, location);
419 }
420 }
421 if variable_map.insert(assign_label, mapping).is_some() {
422 return Err(SourceInfoTableError::DuplicateVariableMappings {
424 id: assign_label,
425 });
426 };
427 }
428
429 for (pos_id, var_id) in states {
430 if !variable_map.contains_key(&var_id) {
431 return Err(SourceInfoTableError::UnknownVariableMapping {
433 id: var_id,
434 });
435 }
436 if state_map.insert(pos_id, var_id).is_some() {
437 return Err(SourceInfoTableError::DuplicatePosStateMappings {
439 id: pos_id,
440 });
441 }
442 }
443
444 Ok(SourceInfoTable {
445 file_map,
446 position_map,
447 mem_location_map: memory_location_map,
448 variable_assignment_map: variable_map,
449 position_state_map: state_map,
450 })
451 }
452
453 pub fn serialize<W: std::io::Write>(
454 &self,
455 mut f: W,
456 ) -> Result<(), std::io::Error> {
457 writeln!(f, "{} #{{", Self::HEADER)?;
458
459 writeln!(f, "FILES")?;
461 for (file, path) in self.file_map.iter().sorted_by_key(|(k, _)| **k) {
462 writeln!(f, " {file}: {}", path.display())?;
463 }
464
465 writeln!(f, "POSITIONS")?;
467 for (position, SourceLocation { line, file }) in
468 self.position_map.iter().sorted_by_key(|(k, _)| **k)
469 {
470 writeln!(f, " {position}: {file} {line}")?;
471 }
472 if !(self.mem_location_map.is_empty()
473 && self.variable_assignment_map.is_empty()
474 && self.position_state_map.is_empty())
475 {
476 writeln!(f, "MEMORY_LOCATIONS")?;
478 for (loc, MemoryLocation { cell, address }) in
479 self.mem_location_map.iter().sorted_by_key(|(k, _)| **k)
480 {
481 write!(f, " {loc}: {cell}")?;
482 if !address.is_empty() {
483 write!(f, "[{}]", address.iter().join(","))?;
484 }
485 writeln!(f)?;
486 }
487
488 writeln!(f, "VARIABLE_ASSIGNMENTS")?;
489 for (id, map) in self
490 .variable_assignment_map
491 .iter()
492 .sorted_by_key(|(k, _)| **k)
493 {
494 writeln!(f, " {id}: {{")?;
495 for (var, loc) in map.iter().sorted() {
496 writeln!(f, " {var}: {loc}")?;
497 }
498 writeln!(f, " }}")?;
499 }
500
501 writeln!(f, "POSITION_STATE_MAP")?;
502 for (pos, var) in
503 self.position_state_map.iter().sorted_by_key(|(k, _)| **k)
504 {
505 writeln!(f, " {pos}: {var}")?;
506 }
507 }
508
509 writeln!(f, "}}#")
510 }
511
512 pub fn get_position_string(
516 &self,
517 pos: PositionId,
518 ) -> Result<String, SourceLookupError> {
519 let Some(src_loc) = self.get_position(pos) else {
520 return Err(SourceLookupError::MissingPosition(pos));
521 };
522 let file_path = self.lookup_file_path(src_loc.file);
525
526 let Ok(mut file) = File::open(file_path) else {
527 return Err(SourceLookupError::MissingFile(file_path));
528 };
529
530 let mut file_contents = String::new();
531
532 match file.read_to_string(&mut file_contents) {
533 Ok(_) => {}
534 Err(_) => {
535 return Err(SourceLookupError::MissingFile(file_path));
536 }
537 }
538
539 let Some(line) = file_contents.lines().nth(src_loc.line.as_usize() - 1)
540 else {
541 return Err(SourceLookupError::MissingLine {
542 file: file_path,
543 line: src_loc.line.as_usize(),
544 });
545 };
546
547 Ok(String::from(line))
548 }
549}
550
551#[derive(Debug, Clone, PartialEq, Eq)]
552pub struct SourceLocation {
553 pub file: FileId,
554 pub line: LineNum,
555}
556
557impl SourceLocation {
558 pub fn new(file: FileId, line: LineNum) -> Self {
559 Self { line, file }
560 }
561}
562#[derive(Error)]
563pub enum SourceInfoTableError {
564 #[error("Duplicate positions found in the metadata table. Position {pos} is defined multiple times:
565 1. file {}, line {}
566 2. file {}, line {}\n", s1.file, s1.line, s2.file, s2.line)]
567 DuplicatePositions {
568 pos: PositionId,
569 s1: SourceLocation,
570 s2: SourceLocation,
571 },
572
573 #[error("Duplicate files found in the metadata table. File id {id1} is defined multiple times:
574 1. {path1}
575 2. {path2}\n")]
576 DuplicateFiles {
577 id1: FileId,
578 path1: PathBuf,
579 path2: PathBuf,
580 },
581
582 #[error("Duplicate definitions for memory location {id}")]
583 DuplicateMemoryIdentifiers { id: MemoryLocationId },
584
585 #[error("Memory location {id} is referenced but never defined")]
586 UnknownMemoryId { id: MemoryLocationId },
587
588 #[error("Variable mapping {id} is referenced but never defined")]
589 UnknownVariableMapping { id: VariableAssignmentId },
590
591 #[error("Duplicate definitions for variable mapping {id}")]
592 DuplicateVariableMappings { id: VariableAssignmentId },
593
594 #[error(
595 "Duplicate definitions for variable mapping associated with position {id}"
596 )]
597 DuplicatePosStateMappings { id: PositionId },
598
599 #[error(
600 "In variable mapping {id} the variable '{var}' has multiple definitions"
601 )]
602 DuplicateVariableAssignments {
603 id: VariableAssignmentId,
604 var: String,
605 },
606}
607
608#[derive(Error, Debug)]
611pub enum SourceLookupError<'a> {
612 #[error("unable to open file {0}")]
613 MissingFile(&'a PathBuf),
614 #[error("file {file} does not have a line {line}")]
615 MissingLine { file: &'a PathBuf, line: usize },
616 #[error("position id {0} does not exist")]
617 MissingPosition(PositionId),
618}
619
620impl std::fmt::Debug for SourceInfoTableError {
621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622 std::fmt::Display::fmt(&self, f)
623 }
624}
625
626pub type SourceInfoResult<T> = Result<T, SourceInfoTableError>;
627
628#[cfg(test)]
629mod tests {
630 use std::path::PathBuf;
631
632 use crate::{
633 parser::CalyxParser,
634 source_info::{
635 FileId, LineNum, MemoryLocationId, PositionId,
636 SourceInfoTableError, VariableAssignmentId,
637 },
638 };
639
640 use super::SourceInfoTable;
641
642 #[test]
643 fn test_parse_metadata() {
644 let input_str = r#"sourceinfo #{
645 FILES
646 0: test.calyx
647 1: test2.calyx
648 2: test3.calyx
649 POSITIONS
650 0: 0 5
651 1: 0 1
652 2: 0 2
653 MEMORY_LOCATIONS
654 0: main.reg1
655 1: main.reg2
656 2: main.mem1 [1,4]
657 VARIABLE_ASSIGNMENTS
658 0: {
659 x: 0
660 y: 1
661 z: 2
662 }
663 1: {
664 q: 0
665 }
666 POSITION_STATE_MAP
667 0: 0
668 2: 1
669}#"#;
670
671 let metadata = CalyxParser::parse_source_info_table(input_str)
672 .unwrap()
673 .unwrap();
674 let file = metadata.lookup_file_path(1.into());
675 assert_eq!(file, &PathBuf::from("test2.calyx"));
676
677 let pos = metadata.lookup_position(1.into());
678 assert_eq!(pos.file, 0.into());
679 assert_eq!(pos.line, LineNum::new(1));
680 }
681
682 #[test]
683 fn test_undefined_mem_loc() {
684 let input_str = r#"sourceinfo #{
685 FILES
686 0: test.calyx
687 POSITIONS
688 0: 0 5
689 1: 0 1
690 2: 0 2
691 MEMORY_LOCATIONS
692 0: main.reg1
693 2: main.mem1 [1,4]
694 VARIABLE_ASSIGNMENTS
695 0: {
696 x: 0
697 y: 1
698 z: 2
699 }
700 1: {
701 q: 0
702 }
703 POSITION_STATE_MAP
704 0: 0
705 2: 1
706}#"#;
707 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
708 assert!(metadata.is_err());
709 let err = metadata.unwrap_err();
710 assert!(matches!(
711 &err,
712 SourceInfoTableError::UnknownMemoryId {
713 id: MemoryLocationId(1)
714 }
715 ));
716 }
717
718 #[test]
719 fn test_undefined_variable() {
720 let input_str = r#"sourceinfo #{
721 FILES
722 0: test.calyx
723 POSITIONS
724 0: 0 5
725 1: 0 1
726 2: 0 2
727 MEMORY_LOCATIONS
728 0: main.reg1
729 1: main.reg2
730 2: main.mem1 [1,4]
731 VARIABLE_ASSIGNMENTS
732 0: {
733 x: 0
734 y: 1
735 z: 2
736 }
737 1: {
738 q: 0
739 }
740 POSITION_STATE_MAP
741 0: 0
742 2: 2
743}#"#;
744 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
745 assert!(metadata.is_err());
746 let err = metadata.unwrap_err();
747
748 assert!(matches!(
749 &err,
750 SourceInfoTableError::UnknownVariableMapping {
751 id: VariableAssignmentId(2)
752 }
753 ));
754 }
755
756 #[test]
757 fn test_duplicate_variable_maps() {
758 let input_str = r#"sourceinfo #{
759 FILES
760 0: test.calyx
761 POSITIONS
762 0: 0 5
763 1: 0 1
764 2: 0 2
765 MEMORY_LOCATIONS
766 0: main.reg1
767 1: main.reg2
768 2: main.mem1 [1,4]
769 VARIABLE_ASSIGNMENTS
770 0: {
771 x: 0
772 y: 1
773 z: 2
774 }
775 1: {
776 q: 0
777 }
778 1: {
779 a: 0
780 }
781 POSITION_STATE_MAP
782 0: 0
783 2: 1
784}#"#;
785 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
786 assert!(metadata.is_err());
787 let err = metadata.unwrap_err();
788 assert!(matches!(
789 &err,
790 SourceInfoTableError::DuplicateVariableMappings {
791 id: VariableAssignmentId(1)
792 }
793 ));
794 }
795
796 #[test]
797 fn test_duplicate_variable_assignment() {
798 let input_str = r#"sourceinfo #{
799 FILES
800 0: test.calyx
801 POSITIONS
802 0: 0 5
803 1: 0 1
804 2: 0 2
805 MEMORY_LOCATIONS
806 0: main.reg1
807 1: main.reg2
808 2: main.mem1 [1,4]
809 VARIABLE_ASSIGNMENTS
810 0: {
811 x: 0
812 y: 1
813 z: 2
814 }
815 1: {
816 q: 0
817 q: 1
818 }
819 POSITION_STATE_MAP
820 0: 0
821 2: 1
822}#"#;
823 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
824 assert!(metadata.is_err());
825 let err = metadata.unwrap_err();
826 assert!(matches!(
827 &err,
828 SourceInfoTableError::DuplicateVariableAssignments {
829 id: VariableAssignmentId(1),
830 var
831 } if var == "q"
832 ));
833 }
834
835 #[test]
836 fn test_duplicate_mem_def() {
837 let input_str = r#"sourceinfo #{
838 FILES
839 0: test.calyx
840 POSITIONS
841 0: 0 5
842 1: 0 1
843 2: 0 2
844 MEMORY_LOCATIONS
845 0: main.reg1
846 1: main.reg2
847 1: main.mem1 [1,4]
848 VARIABLE_ASSIGNMENTS
849 0: {
850 x: 0
851 y: 1
852 z: 2
853 }
854 1: {
855 q: 0
856 }
857 POSITION_STATE_MAP
858 0: 0
859 2: 1
860}#"#;
861 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
862 assert!(metadata.is_err());
863 let err = metadata.unwrap_err();
864 assert!(matches!(
865 &err,
866 SourceInfoTableError::DuplicateMemoryIdentifiers {
867 id: MemoryLocationId(1)
868 }
869 ));
870 }
871
872 #[test]
873 fn test_duplicate_pos_state() {
874 let input_str = r#"sourceinfo #{
875 FILES
876 0: test.calyx
877 POSITIONS
878 0: 0 5
879 1: 0 1
880 2: 0 2
881 MEMORY_LOCATIONS
882 0: main.reg1
883 1: main.reg2
884 2: main.mem1 [1,4]
885 VARIABLE_ASSIGNMENTS
886 0: {
887 x: 0
888 y: 1
889 z: 2
890 }
891 1: {
892 q: 0
893 }
894 POSITION_STATE_MAP
895 0: 0
896 0: 1
897}#"#;
898 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
899 assert!(metadata.is_err());
900 let err = metadata.unwrap_err();
901 assert!(matches!(
902 &err,
903 SourceInfoTableError::DuplicatePosStateMappings {
904 id: PositionId(0)
905 }
906 ));
907 }
908
909 #[test]
910 fn test_duplicate_file_parse() {
911 let input_str = r#"sourceinfo #{
912 FILES
913 0: test.calyx
914 0: test2.calyx
915 2: test3.calyx
916 POSITIONS
917 0: 0 5
918 1: 0 1
919 2: 0 2
920 }#"#;
921 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
922
923 assert!(metadata.is_err());
924 let err = metadata.unwrap_err();
925 assert!(matches!(&err, SourceInfoTableError::DuplicateFiles { .. }));
926 if let SourceInfoTableError::DuplicateFiles { id1, .. } = &err {
927 assert_eq!(id1, &FileId::new(0))
928 } else {
929 unreachable!()
930 }
931 }
932
933 #[test]
934 fn test_duplicate_position_parse() {
935 let input_str = r#"sourceinfo #{
936 FILES
937 0: test.calyx
938 1: test2.calyx
939 2: test3.calyx
940 POSITIONS
941 0: 0 5
942 0: 0 1
943 2: 0 2
944 }#"#;
945 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
946
947 assert!(metadata.is_err());
948 let err = metadata.unwrap_err();
949 assert!(matches!(
950 &err,
951 SourceInfoTableError::DuplicatePositions { .. }
952 ));
953 if let SourceInfoTableError::DuplicatePositions { pos, .. } = err {
954 assert_eq!(pos, PositionId::new(0))
955 } else {
956 unreachable!()
957 }
958 }
959
960 #[test]
961 fn test_serialize() {
962 let mut metadata = SourceInfoTable::new_empty();
963 metadata.add_file(0.into(), "test.calyx".into());
964 metadata.add_file(1.into(), "test2.calyx".into());
965 metadata.add_file(2.into(), "test3.calyx".into());
966
967 metadata.add_position(0.into(), 0.into(), LineNum::new(1));
968 metadata.add_position(1.into(), 1.into(), LineNum::new(2));
969 metadata.add_position(150.into(), 2.into(), LineNum::new(148));
970
971 let mut serialized_str = vec![];
972 metadata.serialize(&mut serialized_str).unwrap();
973 let serialized_str = String::from_utf8(serialized_str).unwrap();
974
975 let parsed_metadata =
976 CalyxParser::parse_source_info_table(&serialized_str)
977 .unwrap()
978 .unwrap();
979
980 assert_eq!(metadata, parsed_metadata)
981 }
982}