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::InvalidTable(format!(
376 "File id {file} is defined multiple times:\n 1. {}\n 2. {}\n",
377 first_path.display(),
378 inserted_path.display()
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::InvalidTable(format!(
390 "Duplicate positions found in the metadata table. Position {pos} is defined multiple times:\n 1. file {}, line {}\n 2. file {}, line {}\n",
391 first_pos.file,
392 first_pos.line,
393 inserted_position.file,
394 inserted_position.line
395 )));
396 }
397 }
398 }
399
400 for (id, loc) in locations {
401 if memory_location_map.insert(id, loc).is_some() {
402 return Err(SourceInfoTableError::InvalidTable(format!(
403 "Multiple definitions for memory location {id}"
404 )));
405 }
406 }
407
408 for (assign_label, assigns) in vars {
409 let mut mapping = HashMap::new();
410 for (name, location) in assigns {
411 if !memory_location_map.contains_key(&location) {
412 return Err(SourceInfoTableError::InvalidTable(format!(
414 "Memory location {location} is referenced but never defined"
415 )));
416 }
417 #[allow(clippy::map_entry)]
424 if mapping.contains_key(&name) {
425 return Err(SourceInfoTableError::InvalidTable(format!(
426 "In variable mapping {assign_label} the variable '{name}' has multiple definitions"
427 )));
428 } else {
429 mapping.insert(name, location);
430 }
431 }
432 if variable_map.insert(assign_label, mapping).is_some() {
433 return Err(SourceInfoTableError::InvalidTable(format!(
434 "Duplicate definitions for variable mapping associated with position {assign_label}"
435 )));
436 };
437 }
438
439 for (pos_id, var_id) in states {
440 if !variable_map.contains_key(&var_id) {
441 return Err(SourceInfoTableError::InvalidTable(format!(
442 "Variable mapping {var_id} is referenced but never defined"
443 )));
444 }
445 if state_map.insert(pos_id, var_id).is_some() {
446 return Err(SourceInfoTableError::InvalidTable(format!(
447 "Multiple variable maps have been assigned to position {pos_id}"
448 )));
449 }
450 }
451
452 Ok(SourceInfoTable {
453 file_map,
454 position_map,
455 mem_location_map: memory_location_map,
456 variable_assignment_map: variable_map,
457 position_state_map: state_map,
458 })
459 }
460
461 pub fn serialize<W: std::io::Write>(
462 &self,
463 mut f: W,
464 ) -> Result<(), std::io::Error> {
465 Self::write_header(&mut f)?;
466
467 self.write_file_table(&mut f)?;
469 self.write_pos_table(&mut f)?;
470
471 if !(self.mem_location_map.is_empty()
473 && self.variable_assignment_map.is_empty()
474 && self.position_state_map.is_empty())
475 {
476 self.write_memory_table(&mut f)?;
477 self.write_var_assigns(&mut f)?;
478 self.write_pos_state_table(&mut f)?;
479 }
480
481 Self::write_footer(&mut f)
482 }
483
484 fn write_pos_state_table<W: std::io::Write>(
485 &self,
486 f: &mut W,
487 ) -> Result<(), std::io::Error> {
488 writeln!(f, "POSITION_STATE_MAP")?;
489
490 for (pos, var) in
491 self.position_state_map.iter().sorted_by_key(|(k, _)| **k)
492 {
493 writeln!(f, " {pos}: {var}")?;
494 }
495 Ok(())
496 }
497
498 fn write_var_assigns<W: std::io::Write>(
499 &self,
500 f: &mut W,
501 ) -> Result<(), std::io::Error> {
502 writeln!(f, "VARIABLE_ASSIGNMENTS")?;
503
504 for (id, map) in self
505 .variable_assignment_map
506 .iter()
507 .sorted_by_key(|(k, _)| **k)
508 {
509 writeln!(f, " {id}: {{")?;
510 for (var, loc) in map.iter().sorted() {
511 writeln!(f, " {var}: {loc}")?;
512 }
513 writeln!(f, " }}")?;
514 }
515 Ok(())
516 }
517
518 fn write_pos_table<W: std::io::Write>(
519 &self,
520 f: &mut W,
521 ) -> Result<(), std::io::Error> {
522 writeln!(f, "POSITIONS")?;
523 for (position, source_loc) in
524 self.position_map.iter().sorted_by_key(|(k, _)| **k)
525 {
526 write!(f, " {position}: ")?;
527 source_loc.serialize(f)?;
528 writeln!(f)?;
529 }
530 Ok(())
531 }
532
533 fn write_memory_table<W: std::io::Write>(
534 &self,
535 f: &mut W,
536 ) -> Result<(), std::io::Error> {
537 writeln!(f, "MEMORY_LOCATIONS")?;
538
539 for (loc, MemoryLocation { cell, address }) in
540 self.mem_location_map.iter().sorted_by_key(|(k, _)| **k)
541 {
542 write!(f, " {loc}: {cell}")?;
543 if !address.is_empty() {
544 write!(f, "[{}]", address.iter().join(","))?;
545 }
546 writeln!(f)?;
547 }
548 Ok(())
549 }
550
551 fn write_file_table<W: std::io::Write>(
552 &self,
553 f: &mut W,
554 ) -> Result<(), std::io::Error> {
555 writeln!(f, "FILES")?;
556 for (file, path) in self.file_map.iter().sorted_by_key(|(k, _)| **k) {
557 writeln!(f, " {file}: {}", path.display())?;
558 }
559 Ok(())
560 }
561
562 pub fn get_position_string(
566 &self,
567 pos: PositionId,
568 ) -> Result<String, SourceInfoTableError> {
569 let Some(src_loc) = self.get_position(pos) else {
570 return Err(SourceInfoTableError::LookupFailure(format!(
571 "position {pos} does not exist"
572 )));
573 };
574 let file_path = self.lookup_file_path(src_loc.file);
577
578 let Ok(mut file) = File::open(file_path) else {
579 return Err(SourceInfoTableError::LookupFailure(format!(
580 "unable to open file '{}'",
581 file_path.display()
582 )));
583 };
584
585 let mut file_contents = String::new();
586
587 match file.read_to_string(&mut file_contents) {
588 Ok(_) => {}
589 Err(e) => {
590 return Err(SourceInfoTableError::LookupFailure(format!(
591 "read of file '{}' failed with error {e}",
592 file_path.display()
593 )));
594 }
595 }
596
597 let Some(line) = file_contents.lines().nth(src_loc.line.as_usize() - 1)
598 else {
599 return Err(SourceInfoTableError::LookupFailure(format!(
600 "file '{}' does not contain a line {}",
601 file_path.display(),
602 src_loc.line.as_usize()
603 )));
604 };
605
606 Ok(String::from(line))
607 }
608
609 fn write_header<W: std::io::Write>(
610 f: &mut W,
611 ) -> Result<(), std::io::Error> {
612 writeln!(f, "{} #{{", SourceInfoTable::HEADER)
613 }
614
615 fn write_footer<W: std::io::Write>(
616 f: &mut W,
617 ) -> Result<(), std::io::Error> {
618 writeln!(f, "}}#")
619 }
620}
621
622#[derive(Debug, Clone, PartialEq, Eq)]
623pub struct SourceLocation {
624 pub file: FileId,
625 pub line: LineNum,
626 pub end_line: Option<LineNum>,
627}
628
629impl SourceLocation {
630 pub fn new(file: FileId, line: LineNum, end_line: Option<LineNum>) -> Self {
631 Self {
632 line,
633 file,
634 end_line,
635 }
636 }
637
638 pub fn serialize(
640 &self,
641 out: &mut impl std::io::Write,
642 ) -> Result<(), std::io::Error> {
643 if let Some(endline) = self.end_line {
644 write!(out, "{} {}:{}", self.file, self.line, endline)
645 } else {
646 write!(out, "{} {}", self.file, self.line)
647 }
648 }
649}
650#[derive(Error)]
651pub enum SourceInfoTableError {
652 #[error("source info is malformed. {0}")]
654 InvalidTable(String),
655 #[error("source lookup failed. {0}")]
657 LookupFailure(String),
658}
659
660impl std::fmt::Debug for SourceInfoTableError {
661 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
662 std::fmt::Display::fmt(&self, f)
663 }
664}
665
666pub type SourceInfoResult<T> = Result<T, SourceInfoTableError>;
667
668#[cfg(test)]
669mod tests {
670 use super::SourceInfoTable;
671 use crate::{parser::CalyxParser, source_info::LineNum};
672 use std::path::PathBuf;
673
674 #[test]
675 fn test_parse_metadata() {
676 let input_str = r#"sourceinfo #{
677 FILES
678 0: test.calyx
679 1: test2.calyx
680 2: test3.calyx
681 POSITIONS
682 0: 0 5
683 1: 0 1:12
684 2: 0 2
685 MEMORY_LOCATIONS
686 0: main.reg1
687 1: main.reg2
688 2: main.mem1 [1,4]
689 VARIABLE_ASSIGNMENTS
690 0: {
691 x: 0
692 y: 1
693 z: 2
694 }
695 1: {
696 q: 0
697 }
698 POSITION_STATE_MAP
699 0: 0
700 2: 1
701}#"#;
702
703 let metadata = CalyxParser::parse_source_info_table(input_str)
704 .unwrap()
705 .unwrap();
706 let file = metadata.lookup_file_path(1.into());
707 assert_eq!(file, &PathBuf::from("test2.calyx"));
708
709 let pos = metadata.lookup_position(1.into());
710 assert_eq!(pos.file, 0.into());
711 assert_eq!(pos.line, LineNum::new(1));
712 }
713
714 #[test]
715 fn test_undefined_mem_loc() {
716 let input_str = r#"sourceinfo #{
717 FILES
718 0: test.calyx
719 POSITIONS
720 0: 0 5
721 1: 0 1
722 2: 0 2
723 MEMORY_LOCATIONS
724 0: main.reg1
725 2: main.mem1 [1,4]
726 VARIABLE_ASSIGNMENTS
727 0: {
728 x: 0
729 y: 1
730 z: 2
731 }
732 1: {
733 q: 0
734 }
735 POSITION_STATE_MAP
736 0: 0
737 2: 1
738}#"#;
739 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
740 assert!(metadata.is_err());
741 }
742
743 #[test]
744 fn test_undefined_variable() {
745 let input_str = r#"sourceinfo #{
746 FILES
747 0: test.calyx
748 POSITIONS
749 0: 0 5
750 1: 0 1
751 2: 0 2
752 MEMORY_LOCATIONS
753 0: main.reg1
754 1: main.reg2
755 2: main.mem1 [1,4]
756 VARIABLE_ASSIGNMENTS
757 0: {
758 x: 0
759 y: 1
760 z: 2
761 }
762 1: {
763 q: 0
764 }
765 POSITION_STATE_MAP
766 0: 0
767 2: 2
768}#"#;
769 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
770 assert!(metadata.is_err());
771 }
772
773 #[test]
774 fn test_duplicate_variable_maps() {
775 let input_str = r#"sourceinfo #{
776 FILES
777 0: test.calyx
778 POSITIONS
779 0: 0 5
780 1: 0 1
781 2: 0 2
782 MEMORY_LOCATIONS
783 0: main.reg1
784 1: main.reg2
785 2: main.mem1 [1,4]
786 VARIABLE_ASSIGNMENTS
787 0: {
788 x: 0
789 y: 1
790 z: 2
791 }
792 1: {
793 q: 0
794 }
795 1: {
796 a: 0
797 }
798 POSITION_STATE_MAP
799 0: 0
800 2: 1
801}#"#;
802 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
803 assert!(metadata.is_err());
804 }
805
806 #[test]
807 fn test_duplicate_variable_assignment() {
808 let input_str = r#"sourceinfo #{
809 FILES
810 0: test.calyx
811 POSITIONS
812 0: 0 5
813 1: 0 1
814 2: 0 2
815 MEMORY_LOCATIONS
816 0: main.reg1
817 1: main.reg2
818 2: main.mem1 [1,4]
819 VARIABLE_ASSIGNMENTS
820 0: {
821 x: 0
822 y: 1
823 z: 2
824 }
825 1: {
826 q: 0
827 q: 1
828 }
829 POSITION_STATE_MAP
830 0: 0
831 2: 1
832}#"#;
833 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
834 assert!(metadata.is_err());
835 }
836
837 #[test]
838 fn test_duplicate_mem_def() {
839 let input_str = r#"sourceinfo #{
840 FILES
841 0: test.calyx
842 POSITIONS
843 0: 0 5
844 1: 0 1
845 2: 0 2
846 MEMORY_LOCATIONS
847 0: main.reg1
848 1: main.reg2
849 1: main.mem1 [1,4]
850 VARIABLE_ASSIGNMENTS
851 0: {
852 x: 0
853 y: 1
854 z: 2
855 }
856 1: {
857 q: 0
858 }
859 POSITION_STATE_MAP
860 0: 0
861 2: 1
862}#"#;
863 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
864 assert!(metadata.is_err());
865 }
866
867 #[test]
868 fn test_duplicate_pos_state() {
869 let input_str = r#"sourceinfo #{
870 FILES
871 0: test.calyx
872 POSITIONS
873 0: 0 5
874 1: 0 1
875 2: 0 2
876 MEMORY_LOCATIONS
877 0: main.reg1
878 1: main.reg2
879 2: main.mem1 [1,4]
880 VARIABLE_ASSIGNMENTS
881 0: {
882 x: 0
883 y: 1
884 z: 2
885 }
886 1: {
887 q: 0
888 }
889 POSITION_STATE_MAP
890 0: 0
891 0: 1
892}#"#;
893 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
894 assert!(metadata.is_err());
895 }
896
897 #[test]
898 fn test_duplicate_file_parse() {
899 let input_str = r#"sourceinfo #{
900 FILES
901 0: test.calyx
902 0: test2.calyx
903 2: test3.calyx
904 POSITIONS
905 0: 0 5:6
906 1: 0 1
907 2: 0 2
908 }#"#;
909 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
910 assert!(metadata.is_err());
911 }
912
913 #[test]
914 fn test_duplicate_position_parse() {
915 let input_str = r#"sourceinfo #{
916 FILES
917 0: test.calyx
918 1: test2.calyx
919 2: test3.calyx
920 POSITIONS
921 0: 0 5
922 0: 0 1
923 2: 0 2
924 }#"#;
925 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
926
927 assert!(metadata.is_err());
928 }
929
930 #[test]
931 fn test_serialize() {
932 let mut metadata = SourceInfoTable::new_empty();
933 metadata.add_file(0.into(), "test.calyx".into());
934 metadata.add_file(1.into(), "test2.calyx".into());
935 metadata.add_file(2.into(), "test3.calyx".into());
936
937 metadata.add_position(0.into(), 0.into(), LineNum::new(1), None);
938 metadata.add_position(
939 1.into(),
940 1.into(),
941 LineNum::new(2),
942 Some(LineNum::new(4)),
943 );
944 metadata.add_position(150.into(), 2.into(), LineNum::new(148), None);
945
946 let mut serialized_str = vec![];
947 metadata.serialize(&mut serialized_str).unwrap();
948 let serialized_str = String::from_utf8(serialized_str).unwrap();
949
950 let parsed_metadata =
951 CalyxParser::parse_source_info_table(&serialized_str)
952 .unwrap()
953 .unwrap();
954
955 assert_eq!(metadata, parsed_metadata)
956 }
957}