calyx_frontend/
source_info.rs

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/// An identifier representing a given file path
15#[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/// An identifier representing a location in the Calyx source code
37#[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/// A newtype wrapping a line number
72#[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/// An ID in the source map labelling some memory location
116#[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/// An ID in the source map labelling a set of mappings from variable names to memory locations
147#[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    /// map file ids to the file path, note that this does not contain file content
166    file_map: HashMap<FileId, PathBuf>,
167    /// maps position ids to their source locations.
168    position_map: HashMap<PositionId, SourceLocation>,
169    /// assigns ids to locations in memories and registers
170    mem_location_map: HashMap<MemoryLocationId, MemoryLocation>,
171    /// assigns ids to collections of variable -> location mappings
172    variable_assignment_map:
173        HashMap<VariableAssignmentId, HashMap<String, MemoryLocationId>>,
174    /// collects the mapping from positions representing a point in the control
175    /// program to the set of variable assignments for that position
176    position_state_map: HashMap<PositionId, VariableAssignmentId>,
177}
178
179impl SourceInfoTable {
180    const HEADER: &str = "sourceinfo";
181
182    /// Looks up the path of the file with the given id.
183    ///
184    /// # Panics
185    /// Panics if the file id does not exist in the file map
186    pub fn lookup_file_path(&self, file: FileId) -> &PathBuf {
187        &self.file_map[&file]
188    }
189
190    /// Looks up the source location of the position with the given id.
191    ///
192    /// # Panics
193    /// Panics if the position id does not exist in the position map
194    pub fn lookup_position(&self, pos: PositionId) -> &SourceLocation {
195        &self.position_map[&pos]
196    }
197
198    /// Looks up the source location of the position with the given id. If no
199    /// such position exists, returns `None`
200    pub fn get_position(&self, pos: PositionId) -> Option<&SourceLocation> {
201        self.position_map.get(&pos)
202    }
203
204    /// Iterate over the stored file map, returning a tuple of references to the
205    /// file id and the path
206    pub fn iter_file_map(&self) -> impl Iterator<Item = (&FileId, &PathBuf)> {
207        self.file_map.iter()
208    }
209
210    /// Iterate over the paths of all files in the file map
211    pub fn iter_file_paths(&self) -> impl Iterator<Item = &PathBuf> {
212        self.file_map.values()
213    }
214
215    /// Iterate over all file ids in the file map
216    pub fn iter_file_ids(&self) -> impl Iterator<Item = FileId> + '_ {
217        self.file_map.keys().copied()
218    }
219
220    /// Iterate over the stored position map, returning a tuple of references to
221    /// the position id and the source location
222    pub fn iter_position_map(
223        &self,
224    ) -> impl Iterator<Item = (&PositionId, &SourceLocation)> {
225        self.position_map.iter()
226    }
227
228    /// Iterate over all position ids in the position map
229    pub fn iter_positions(&self) -> impl Iterator<Item = PositionId> + '_ {
230        self.position_map.keys().copied()
231    }
232
233    /// Iterate over the source locations in the position map
234    pub fn iter_source_locations(
235        &self,
236    ) -> impl Iterator<Item = &SourceLocation> {
237        self.position_map.values()
238    }
239
240    /// Adds a file to the file map with the given id
241    pub fn add_file(&mut self, file: FileId, path: PathBuf) {
242        self.file_map.insert(file, path);
243    }
244
245    /// Adds a file to the file map and generates a new file id
246    /// for it. If you want to add a file with a specific id, use
247    /// [`SourceInfoTable::add_file`]
248    pub fn push_file(&mut self, path: PathBuf) -> FileId {
249        // find the largest file id in the map
250        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    /// Adds a position to the position map and generates a new position id
268    /// for it. If you want to add a position with a specific id, use
269    /// [`SourceInfoTable::add_position`]
270    pub fn push_position(
271        &mut self,
272        file: FileId,
273        line: LineNum,
274        endline: Option<LineNum>,
275    ) -> PositionId {
276        // find the largest position id in the map
277        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    /// Attempts to look up the variable mapping associated with a given
289    /// position, if such a mapping exists
290    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    /// Creates a new empty source info table
307    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    /// A wrapper function to construct a source a source map containing only
318    /// files and positions. If an empty map is needed use [SourceInfoTable::new_empty]
319    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        // the compiler needs some concrete types here even though the input is
326        // all empty
327        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    // this is awful
338    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                    // unknown memory location
413                    return Err(SourceInfoTableError::InvalidTable(format!(
414                        "Memory location {location} is referenced but never defined"
415                    )));
416                }
417                // this is to avoid copying the string in all cases since we
418                // would only need it when emitting the error. Clippy doesn't
419                // like this for good reasons and while I suspect it may be
420                // possible using the entry api, I think this is clearer so I'm
421                // just suppressing the warning and writing this very long
422                // comment about it instead.
423                #[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        // mandatory entries
468        self.write_file_table(&mut f)?;
469        self.write_pos_table(&mut f)?;
470
471        // optional entries
472        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    /// Attempt to lookup the line that a given position points to. Returns an error in
563    /// cases when the position does not exist, the file is unavailable, or the file
564    /// does not contain the indicated line.
565    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        // this will panic if the file doesn't exist but that would imply the table has
575        // incorrect information in it
576        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    /// Write out the source location string
639    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    /// A fatal error representing a malformed table
653    #[error("source info is malformed. {0}")]
654    InvalidTable(String),
655    /// A non-fatal error representing a failed lookup
656    #[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}