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