1use itertools::Itertools;
2use std::{
3 collections::{HashMap, HashSet},
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: VariableMap,
173 position_state_map: HashMap<PositionId, VariableAssignmentId>,
176 type_table: HashMap<TypeId, SourceType>,
178}
179
180impl SourceInfoTable {
181 const HEADER: &str = "sourceinfo";
182
183 pub fn lookup_file_path(&self, file: FileId) -> &PathBuf {
188 &self.file_map[&file]
189 }
190
191 pub fn lookup_position(&self, pos: PositionId) -> &SourceLocation {
196 &self.position_map[&pos]
197 }
198
199 pub fn get_position(&self, pos: PositionId) -> Option<&SourceLocation> {
202 self.position_map.get(&pos)
203 }
204
205 pub fn iter_file_map(&self) -> impl Iterator<Item = (&FileId, &PathBuf)> {
208 self.file_map.iter()
209 }
210
211 pub fn iter_file_paths(&self) -> impl Iterator<Item = &PathBuf> {
213 self.file_map.values()
214 }
215
216 pub fn iter_file_ids(&self) -> impl Iterator<Item = FileId> + '_ {
218 self.file_map.keys().copied()
219 }
220
221 pub fn iter_position_map(
224 &self,
225 ) -> impl Iterator<Item = (&PositionId, &SourceLocation)> {
226 self.position_map.iter()
227 }
228
229 pub fn iter_positions(&self) -> impl Iterator<Item = PositionId> + '_ {
231 self.position_map.keys().copied()
232 }
233
234 pub fn iter_source_locations(
236 &self,
237 ) -> impl Iterator<Item = &SourceLocation> {
238 self.position_map.values()
239 }
240
241 pub fn add_file(&mut self, file: FileId, path: PathBuf) {
243 self.file_map.insert(file, path);
244 }
245
246 pub fn push_file(&mut self, path: PathBuf) -> FileId {
250 let max = self.iter_file_ids().max().unwrap_or(0.into());
252 let new = FileId(max.0 + 1);
253
254 self.add_file(new, path);
255 new
256 }
257 pub fn add_position(
258 &mut self,
259 pos: PositionId,
260 file: FileId,
261 line: LineNum,
262 endline: Option<LineNum>,
263 ) {
264 self.position_map
265 .insert(pos, SourceLocation::new(file, line, endline));
266 }
267
268 pub fn push_position(
272 &mut self,
273 file: FileId,
274 line: LineNum,
275 endline: Option<LineNum>,
276 ) -> PositionId {
277 let max = self.iter_positions().max().unwrap_or(0.into());
279 let new = PositionId(max.0 + 1);
280
281 self.add_position(new, file, line, endline);
282 new
283 }
284
285 pub fn add_location(&mut self, id: MemoryLocationId, info: MemoryLocation) {
286 self.mem_location_map.insert(id, info);
287 }
288
289 pub fn get_variable_mapping(
292 &self,
293 pos: PositionId,
294 ) -> Option<&HashMap<String, VariableLayout>> {
295 self.position_state_map
296 .get(&pos)
297 .and_then(|x| self.variable_assignment_map.get(x))
298 }
299
300 pub fn get_memory_location(
301 &self,
302 loc: &MemoryLocationId,
303 ) -> &MemoryLocation {
304 &self.mem_location_map[loc]
305 }
306
307 pub fn new_empty() -> Self {
309 Self {
310 file_map: HashMap::new(),
311 position_map: HashMap::new(),
312 mem_location_map: HashMap::new(),
313 variable_assignment_map: VariableMap::default(),
314 position_state_map: HashMap::new(),
315 type_table: HashMap::new(),
316 }
317 }
318
319 pub fn new_minimal(
322 files: impl IntoIterator<Item = (FileId, PathBuf)>,
323 positions: impl IntoIterator<
324 Item = (PositionId, FileId, LineNum, Option<LineNum>),
325 >,
326 ) -> SourceInfoResult<Self> {
327 let loc: Vec<(MemoryLocationId, MemoryLocation)> = vec![];
330 let states: Vec<(PositionId, VariableAssignmentId)> = vec![];
331 #[allow(clippy::type_complexity)]
332 let variable_assigns: Vec<(
333 VariableAssignmentId,
334 Vec<(VariableName<String>, VariableLayout)>,
335 )> = vec![];
336 let types: Vec<(TypeId, SourceType)> = vec![];
337
338 Self::new(files, positions, loc, variable_assigns, states, types)
339 }
340
341 pub fn new(
343 files: impl IntoIterator<Item = (FileId, PathBuf)>,
344 positions: impl IntoIterator<
345 Item = (PositionId, FileId, LineNum, Option<LineNum>),
346 >,
347 locations: impl IntoIterator<Item = (MemoryLocationId, MemoryLocation)>,
348 variable_assigns: impl IntoIterator<
349 Item = (
350 VariableAssignmentId,
351 impl IntoIterator<Item = (VariableName<String>, VariableLayout)>,
352 ),
353 >,
354 states: impl IntoIterator<Item = (PositionId, VariableAssignmentId)>,
355 types: impl IntoIterator<Item = (TypeId, SourceType)>,
356 ) -> SourceInfoResult<Self> {
357 let files = files.into_iter();
358 let positions = positions.into_iter();
359 let locations = locations.into_iter();
360 let states = states.into_iter();
361 let types = types.into_iter();
362
363 let mut file_map = HashMap::with_capacity(
364 files.size_hint().1.unwrap_or(files.size_hint().0),
365 );
366 let mut position_map = HashMap::with_capacity(
367 positions.size_hint().1.unwrap_or(positions.size_hint().0),
368 );
369
370 let mut memory_location_map: HashMap<MemoryLocationId, MemoryLocation> =
371 HashMap::new();
372
373 let mut state_map = HashMap::new();
374 let mut type_map = HashMap::new();
375
376 for (file, path) in files {
377 if let Some(first_path) = file_map.insert(file, path) {
378 let inserted_path = &file_map[&file];
379 if &first_path != inserted_path {
380 return Err(SourceInfoTableError::InvalidTable(format!(
381 "File id {file} is defined multiple times:\n 1. {}\n 2. {}\n",
382 first_path.display(),
383 inserted_path.display()
384 )));
385 }
386 }
387 }
388
389 for (pos, file, line, end_line) in positions {
390 let source = SourceLocation::new(file, line, end_line);
391 if let Some(first_pos) = position_map.insert(pos, source) {
392 let inserted_position = &position_map[&pos];
393 if inserted_position != &first_pos {
394 return Err(SourceInfoTableError::InvalidTable(format!(
395 "Duplicate positions found in the metadata table. Position {pos} is defined multiple times:\n 1. file {}, line {}\n 2. file {}, line {}\n",
396 first_pos.file,
397 first_pos.line,
398 inserted_position.file,
399 inserted_position.line
400 )));
401 }
402 }
403 }
404
405 for (id, loc) in locations {
406 if memory_location_map.insert(id, loc).is_some() {
407 return Err(SourceInfoTableError::InvalidTable(format!(
408 "Multiple definitions for memory location {id}"
409 )));
410 }
411 }
412
413 let mut types_referenced: HashSet<TypeId> = HashSet::new();
414
415 let variable_map = VariableMap::build(
416 variable_assigns,
417 &memory_location_map,
418 &mut types_referenced,
419 )?;
420
421 for (pos_id, var_id) in states {
422 if !variable_map.contains_key(&var_id) {
423 return Err(SourceInfoTableError::InvalidTable(format!(
424 "Variable mapping {var_id} is referenced but never defined"
425 )));
426 }
427 if state_map.insert(pos_id, var_id).is_some() {
428 return Err(SourceInfoTableError::InvalidTable(format!(
429 "Multiple variable maps have been assigned to position {pos_id}"
430 )));
431 }
432 }
433
434 for (id, source_type) in types {
435 types_referenced.extend(source_type.types_referenced());
436
437 if type_map.insert(id, source_type).is_some() {
438 return Err(SourceInfoTableError::InvalidTable(format!(
439 "multiple definitions for type id {id}"
440 )));
441 }
442 }
443
444 for ty in types_referenced {
445 if !type_map.contains_key(&ty) {
446 return Err(SourceInfoTableError::InvalidTable(format!(
447 "type id {ty} is referenced but never defined"
448 )));
449 }
450 }
451
452 for (name, def) in variable_map.iter().flat_map(|(_, v)| v) {
454 if let VariableLayout {
455 type_info,
456 layout_fn: LayoutFunction::Split,
457 layout_args,
458 } = def
459 {
460 let expected_count = type_info.entry_count(&type_map);
461 if expected_count != layout_args.len() {
462 return Err(SourceInfoTableError::InvalidTable(format!(
463 "Variable '{name}' of type '{type_info}' was given {} arguments to the split layout function when {expected_count} were required.",
464 layout_args.len()
465 )));
466 }
467 }
468 }
469
470 Ok(SourceInfoTable {
471 file_map,
472 position_map,
473 mem_location_map: memory_location_map,
474 variable_assignment_map: variable_map,
475 position_state_map: state_map,
476 type_table: type_map,
477 })
478 }
479
480 pub fn serialize<W: std::io::Write>(
481 &self,
482 mut f: W,
483 ) -> Result<(), std::io::Error> {
484 Self::write_header(&mut f)?;
485
486 self.write_file_table(&mut f)?;
488 self.write_pos_table(&mut f)?;
489
490 if !(self.mem_location_map.is_empty()
492 && self.variable_assignment_map.is_empty()
493 && self.position_state_map.is_empty()
494 && self.type_table.is_empty())
495 {
496 self.write_memory_table(&mut f)?;
497 self.write_var_assigns(&mut f)?;
498 self.write_pos_state_table(&mut f)?;
499 self.write_type_table(&mut f)?;
500 }
501
502 Self::write_footer(&mut f)
503 }
504
505 fn write_pos_state_table<W: std::io::Write>(
506 &self,
507 f: &mut W,
508 ) -> Result<(), std::io::Error> {
509 writeln!(f, "POSITION_STATE_MAP")?;
510
511 for (pos, var) in
512 self.position_state_map.iter().sorted_by_key(|(k, _)| **k)
513 {
514 writeln!(f, " {pos}: {var}")?;
515 }
516 Ok(())
517 }
518
519 fn write_var_assigns<W: std::io::Write>(
520 &self,
521 f: &mut W,
522 ) -> Result<(), std::io::Error> {
523 writeln!(f, "VARIABLE_ASSIGNMENTS")?;
524
525 for (id, map) in self
526 .variable_assignment_map
527 .iter_serialize()
528 .sorted_by_key(|(k, _)| **k)
529 {
530 writeln!(f, " {id}: {{")?;
531 for (var, variable_layout) in map.sorted_by_key(|(v, _)| *v) {
532 write!(f, " {var}:")?;
533 write!(
534 f,
535 " ty {}, {}",
536 variable_layout.type_info, variable_layout.layout_fn
537 )?;
538 for loc in variable_layout.layout_args.iter() {
539 write!(f, " {loc}")?;
540 }
541 writeln!(f)?;
542 }
543 writeln!(f, " }}")?;
544 }
545 Ok(())
546 }
547
548 fn write_pos_table<W: std::io::Write>(
549 &self,
550 f: &mut W,
551 ) -> Result<(), std::io::Error> {
552 writeln!(f, "POSITIONS")?;
553 for (position, source_loc) in
554 self.position_map.iter().sorted_by_key(|(k, _)| **k)
555 {
556 write!(f, " {position}: ")?;
557 source_loc.serialize(f)?;
558 writeln!(f)?;
559 }
560 Ok(())
561 }
562
563 fn write_memory_table<W: std::io::Write>(
564 &self,
565 f: &mut W,
566 ) -> Result<(), std::io::Error> {
567 writeln!(f, "MEMORY_LOCATIONS")?;
568
569 for (loc, MemoryLocation { cell, address }) in
570 self.mem_location_map.iter().sorted_by_key(|(k, _)| **k)
571 {
572 write!(f, " {loc}: {cell}")?;
573 if !address.is_empty() {
574 write!(f, "[{}]", address.iter().join(","))?;
575 }
576 writeln!(f)?;
577 }
578 Ok(())
579 }
580
581 fn write_file_table<W: std::io::Write>(
582 &self,
583 f: &mut W,
584 ) -> Result<(), std::io::Error> {
585 writeln!(f, "FILES")?;
586 for (file, path) in self.file_map.iter().sorted_by_key(|(k, _)| **k) {
587 writeln!(f, " {file}: {}", path.display())?;
588 }
589 Ok(())
590 }
591
592 fn write_type_table<W: std::io::Write>(
593 &self,
594 f: &mut W,
595 ) -> Result<(), std::io::Error> {
596 writeln!(f, "TYPES")?;
597 for (id, source_type) in
598 self.type_table.iter().sorted_by_key(|(k, _)| **k)
599 {
600 write!(f, " {id}: {{ ",)?;
601 match source_type {
602 SourceType::Array { ty, length } => {
603 write!(f, "{ty}; {length}")?;
604 }
605 SourceType::Struct { fields } => {
606 if let Some((name, ty)) = fields.first() {
607 write!(f, "{name}: {ty}")?;
608 }
609
610 for (name, ty) in fields.iter().skip(1) {
611 write!(f, ", {name}: {ty}")?;
612 }
613 }
614 }
615
616 writeln!(f, " }}")?;
617 }
618 Ok(())
619 }
620
621 pub fn get_position_string(
625 &self,
626 pos: PositionId,
627 ) -> Result<String, SourceInfoTableError> {
628 let Some(src_loc) = self.get_position(pos) else {
629 return Err(SourceInfoTableError::LookupFailure(format!(
630 "position {pos} does not exist"
631 )));
632 };
633 let file_path = self.lookup_file_path(src_loc.file);
636
637 let Ok(mut file) = File::open(file_path) else {
638 return Err(SourceInfoTableError::LookupFailure(format!(
639 "unable to open file '{}'",
640 file_path.display()
641 )));
642 };
643
644 let mut file_contents = String::new();
645
646 match file.read_to_string(&mut file_contents) {
647 Ok(_) => {}
648 Err(e) => {
649 return Err(SourceInfoTableError::LookupFailure(format!(
650 "read of file '{}' failed with error {e}",
651 file_path.display()
652 )));
653 }
654 }
655
656 let Some(line) = file_contents.lines().nth(src_loc.line.as_usize() - 1)
657 else {
658 return Err(SourceInfoTableError::LookupFailure(format!(
659 "file '{}' does not contain a line {}",
660 file_path.display(),
661 src_loc.line.as_usize()
662 )));
663 };
664
665 Ok(String::from(line))
666 }
667
668 fn write_header<W: std::io::Write>(
669 f: &mut W,
670 ) -> Result<(), std::io::Error> {
671 writeln!(f, "{} #{{", SourceInfoTable::HEADER)
672 }
673
674 fn write_footer<W: std::io::Write>(
675 f: &mut W,
676 ) -> Result<(), std::io::Error> {
677 writeln!(f, "}}#")
678 }
679}
680
681#[derive(Debug, Clone, PartialEq, Eq)]
682pub struct SourceLocation {
683 pub file: FileId,
684 pub line: LineNum,
685 pub end_line: Option<LineNum>,
686}
687
688impl SourceLocation {
689 pub fn new(file: FileId, line: LineNum, end_line: Option<LineNum>) -> Self {
690 Self {
691 line,
692 file,
693 end_line,
694 }
695 }
696
697 pub fn serialize(
699 &self,
700 out: &mut impl std::io::Write,
701 ) -> Result<(), std::io::Error> {
702 if let Some(endline) = self.end_line {
703 write!(out, "{} {}:{}", self.file, self.line, endline)
704 } else {
705 write!(out, "{} {}", self.file, self.line)
706 }
707 }
708}
709
710#[derive(Debug, Clone, Hash, PartialEq, Eq)]
711pub enum PrimitiveType {
712 Uint(u32),
713 Sint(u32),
714 Bool,
715 Bitfield(u32),
716}
717
718impl Display for PrimitiveType {
719 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
720 match self {
721 PrimitiveType::Uint(x) => write!(f, "u{x}"),
722 PrimitiveType::Sint(x) => write!(f, "i{x}"),
723 PrimitiveType::Bool => write!(f, "bool"),
724 PrimitiveType::Bitfield(x) => write!(f, "b{x}"),
725 }
726 }
727}
728
729impl PrimitiveType {
730 pub fn type_size(&self) -> usize {
731 match self {
732 PrimitiveType::Uint(width) => *width as usize,
733 PrimitiveType::Sint(width) => *width as usize,
734 PrimitiveType::Bool => 1_usize,
735 PrimitiveType::Bitfield(width) => *width as usize,
736 }
737 }
738}
739
740#[derive(Debug, Clone, PartialEq, Eq)]
741pub enum FieldType {
742 Primitive(PrimitiveType),
743 Composite(TypeId),
744}
745
746impl Display for FieldType {
747 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
748 match self {
749 FieldType::Primitive(primitive_type) => primitive_type.fmt(f),
750 FieldType::Composite(type_id) => type_id.fmt(f),
751 }
752 }
753}
754
755impl FieldType {
756 pub fn type_size(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
757 match self {
758 FieldType::Primitive(primitive_type) => primitive_type.type_size(),
759 FieldType::Composite(type_id) => {
760 type_map[type_id].type_size(type_map)
761 }
762 }
763 }
764
765 pub fn entry_count(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
767 match self {
768 FieldType::Primitive(_) => 1,
769 FieldType::Composite(type_id) => {
770 type_map[type_id].entry_count(type_map)
771 }
772 }
773 }
774}
775
776#[derive(Debug, Clone, PartialEq, Eq)]
777pub enum SourceType {
778 Array {
779 ty: FieldType,
780 length: u32,
781 },
782 Struct {
783 fields: Vec<(VariableName<String>, FieldType)>,
784 },
785}
786
787impl SourceType {
788 pub fn type_size(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
789 match self {
790 SourceType::Array { ty, length } => {
791 ty.type_size(type_map) * (*length as usize)
792 }
793 SourceType::Struct { fields } => fields
794 .iter()
795 .fold(0, |acc, (_, ty)| acc + ty.type_size(type_map)),
796 }
797 }
798
799 pub fn types_referenced(&self) -> Vec<TypeId> {
800 match self {
801 SourceType::Array { ty, .. } => {
802 if let FieldType::Composite(id) = ty {
803 vec![*id]
804 } else {
805 vec![]
806 }
807 }
808 SourceType::Struct { fields } => fields
809 .iter()
810 .filter_map(|(_, ty)| {
811 if let FieldType::Composite(id) = ty {
812 Some(*id)
813 } else {
814 None
815 }
816 })
817 .collect(),
818 }
819 }
820
821 pub fn entry_count(&self, type_map: &HashMap<TypeId, SourceType>) -> usize {
823 match self {
824 SourceType::Array { ty, length } => {
825 let entries_per_field = ty.entry_count(type_map);
826 entries_per_field * (*length as usize)
827 }
828 SourceType::Struct { fields } => fields
829 .iter()
830 .fold(0, |acc, (_, ty)| acc + ty.entry_count(type_map)),
831 }
832 }
833}
834
835#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
837pub struct TypeId(Word);
838
839impl TypeId {
840 pub fn new(v: Word) -> Self {
841 Self(v)
842 }
843}
844
845impl Display for TypeId {
846 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
847 <Word as Display>::fmt(&self.0, f)
848 }
849}
850
851#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
852pub struct VariableName<S: AsRef<str>> {
854 name: S,
855 name_is_literal: bool,
858}
859
860impl<S: AsRef<str>> Display for VariableName<S> {
861 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
862 if self.name_is_literal {
863 write!(f, "\"{}\"", self.name.as_ref())
864 } else {
865 self.name.as_ref().fmt(f)
866 }
867 }
868}
869
870impl<S: AsRef<str>> VariableName<S> {
871 pub fn new(name: S, name_is_literal: bool) -> Self {
872 Self {
873 name,
874 name_is_literal,
875 }
876 }
877
878 pub fn new_non_literal(name: S) -> Self {
879 Self {
880 name,
881 name_is_literal: false,
882 }
883 }
884
885 pub fn new_literal(name: S) -> Self {
886 Self {
887 name,
888 name_is_literal: true,
889 }
890 }
891
892 pub fn into_inner(self) -> S {
893 self.name
894 }
895
896 pub fn as_str(&self) -> &str {
897 self.name.as_ref()
898 }
899
900 pub fn flip_is_literal(&mut self) {
903 self.name_is_literal = !self.name_is_literal
904 }
905
906 pub fn set_is_literal(&mut self) {
907 self.name_is_literal = true
908 }
909
910 pub fn unset_is_literal(&mut self) {
911 self.name_is_literal = false
912 }
913}
914
915#[derive(Debug, Clone, PartialEq, Eq, Default)]
916pub struct VariableMap {
917 data: HashMap<VariableAssignmentId, HashMap<String, VariableLayout>>,
918 is_literal: HashMap<VariableAssignmentId, HashMap<String, bool>>,
924}
925
926impl VariableMap {
927 pub fn build(
928 stream: impl IntoIterator<
929 Item = (
930 VariableAssignmentId,
931 impl IntoIterator<Item = (VariableName<String>, VariableLayout)>,
932 ),
933 >,
934 memory_location_map: &HashMap<MemoryLocationId, MemoryLocation>,
935 types_referenced: &mut HashSet<TypeId>,
936 ) -> SourceInfoResult<Self> {
937 let mut variable_map = HashMap::new();
938 let mut literal_map = HashMap::new();
939
940 for (assign_label, assigns) in stream {
941 let mut data_mapping = HashMap::new();
942 let mut literal_mapping = HashMap::new();
943 for (name, location) in assigns {
944 let VariableName {
945 name,
946 name_is_literal,
947 } = name;
948
949 for loc in location.layout_args.iter() {
950 if !memory_location_map.contains_key(loc) {
951 return Err(SourceInfoTableError::InvalidTable(
953 format!(
954 "Memory location {loc} is referenced but never defined"
955 ),
956 ));
957 }
958 }
959
960 if let Some(ty) = location.referenced_type() {
961 types_referenced.insert(ty);
962 }
963
964 #[allow(clippy::map_entry)]
971 if data_mapping.contains_key(&name) {
972 return Err(SourceInfoTableError::InvalidTable(format!(
973 "In variable mapping {assign_label} the variable '{name}' has multiple definitions"
974 )));
975 } else {
976 literal_mapping.insert(name.clone(), name_is_literal);
977 data_mapping.insert(name, location);
978 }
979 }
980 if variable_map.insert(assign_label, data_mapping).is_some() {
981 return Err(SourceInfoTableError::InvalidTable(format!(
982 "Duplicate definitions for variable mapping associated with position {assign_label}"
983 )));
984 };
985 literal_map.insert(assign_label, literal_mapping);
986 }
987
988 Ok(Self {
989 data: variable_map,
990 is_literal: literal_map,
991 })
992 }
993
994 pub fn is_empty(&self) -> bool {
995 self.data.is_empty()
996 }
997
998 pub fn iter_serialize(
999 &self,
1000 ) -> impl Iterator<
1001 Item = (
1002 &VariableAssignmentId,
1003 impl Iterator<Item = (VariableName<&str>, &VariableLayout)> + '_,
1004 ),
1005 > {
1006 self.data.iter().map(|(id, data)| {
1007 (
1008 id,
1009 data.iter().map(|(name, dec)| {
1010 (
1011 VariableName::new(
1012 name.as_str(),
1013 self.is_literal[id][name],
1014 ),
1015 dec,
1016 )
1017 }),
1018 )
1019 })
1020 }
1021
1022 pub fn iter(
1023 &self,
1024 ) -> impl Iterator<
1025 Item = (
1026 &VariableAssignmentId,
1027 impl Iterator<Item = (&String, &VariableLayout)>,
1028 ),
1029 > {
1030 self.data.iter().map(|(k, v)| (k, v.iter()))
1031 }
1032
1033 pub fn contains_key(&self, key: &VariableAssignmentId) -> bool {
1034 self.data.contains_key(key)
1035 }
1036
1037 pub fn get(
1038 &self,
1039 k: &VariableAssignmentId,
1040 ) -> Option<&HashMap<String, VariableLayout>> {
1041 self.data.get(k)
1042 }
1043}
1044
1045#[derive(Debug, Clone, PartialEq, Eq)]
1046pub enum LayoutFunction {
1047 Packed,
1050 Split,
1054}
1055
1056impl Display for LayoutFunction {
1057 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1058 match self {
1059 LayoutFunction::Packed => write!(f, "packed"),
1060 LayoutFunction::Split => write!(f, "split"),
1061 }
1062 }
1063}
1064
1065#[derive(Debug, Clone, PartialEq, Eq)]
1066pub struct VariableLayout {
1067 pub type_info: FieldType,
1068 pub layout_fn: LayoutFunction,
1069 pub layout_args: Box<[MemoryLocationId]>,
1070}
1071
1072impl VariableLayout {
1073 pub fn new(
1074 type_info: FieldType,
1075 layout_fn: LayoutFunction,
1076 layout_args: impl IntoIterator<Item = MemoryLocationId>,
1077 ) -> Self {
1078 Self {
1079 type_info,
1080 layout_fn,
1081 layout_args: layout_args.into_iter().collect(),
1082 }
1083 }
1084
1085 pub fn referenced_type(&self) -> Option<TypeId> {
1086 if let FieldType::Composite(c) = self.type_info {
1087 Some(c)
1088 } else {
1089 None
1090 }
1091 }
1092}
1093
1094#[derive(Error)]
1095pub enum SourceInfoTableError {
1096 #[error("source info is malformed. {0}")]
1098 InvalidTable(String),
1099 #[error("source lookup failed. {0}")]
1101 LookupFailure(String),
1102}
1103
1104impl std::fmt::Debug for SourceInfoTableError {
1105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1106 std::fmt::Display::fmt(&self, f)
1107 }
1108}
1109
1110pub type SourceInfoResult<T> = Result<T, SourceInfoTableError>;
1111
1112#[cfg(test)]
1113mod tests {
1114 use super::SourceInfoTable;
1115 use crate::{parser::CalyxParser, source_info::LineNum};
1116 use std::path::PathBuf;
1117
1118 #[test]
1119 fn test_parse_metadata() {
1120 let input_str = r#"sourceinfo #{
1121 FILES
1122 0: test.calyx
1123 1: test2.calyx
1124 2: test3.calyx
1125 POSITIONS
1126 0: 0 5
1127 1: 0 1:12
1128 2: 0 2
1129 MEMORY_LOCATIONS
1130 0: main.reg1
1131 1: main.reg2
1132 2: main.mem1 [1,4]
1133 3: main.mem1 [0,1]
1134 4: main.mem1 [0,2]
1135 VARIABLE_ASSIGNMENTS
1136 0: {
1137 x: ty b12, packed 0
1138 y: ty 1, packed 1
1139 z: ty 0, split 2 3
1140 "q": ty 2, packed 4
1141 }
1142 1: {
1143 q: ty 0, packed 1
1144 }
1145 POSITION_STATE_MAP
1146 0: 0
1147 2: 1
1148 TYPES
1149 0: { 0: u4, 1: i6 }
1150 1: { bool; 15 }
1151 2: { coordinate: 0, "bvec": 1 }
1152
1153}#"#;
1154
1155 let metadata = CalyxParser::parse_source_info_table(input_str)
1156 .unwrap()
1157 .unwrap();
1158 let file = metadata.lookup_file_path(1.into());
1159 assert_eq!(file, &PathBuf::from("test2.calyx"));
1160
1161 let pos = metadata.lookup_position(1.into());
1162 assert_eq!(pos.file, 0.into());
1163 assert_eq!(pos.line, LineNum::new(1));
1164
1165 let mut serialized_str = vec![];
1166 metadata.serialize(&mut serialized_str).unwrap();
1167 let serialized_str = String::from_utf8(serialized_str).unwrap();
1168 eprintln!("{}", &serialized_str);
1169 let parsed_metadata =
1170 CalyxParser::parse_source_info_table(&serialized_str)
1171 .unwrap()
1172 .unwrap();
1173
1174 assert_eq!(metadata, parsed_metadata)
1175 }
1176
1177 #[test]
1178 fn test_undefined_mem_loc() {
1179 let input_str = r#"sourceinfo #{
1180 FILES
1181 0: test.calyx
1182 POSITIONS
1183 0: 0 5
1184 1: 0 1
1185 2: 0 2
1186 MEMORY_LOCATIONS
1187 0: main.reg1
1188 2: main.mem1 [1,4]
1189 VARIABLE_ASSIGNMENTS
1190 0: {
1191 x: ty b10, packed 0
1192 y: ty b10, packed 1
1193 z: ty b10, packed 2
1194 }
1195 1: {
1196 q: ty b14, packed 0
1197 }
1198 POSITION_STATE_MAP
1199 0: 0
1200 2: 1
1201 TYPES
1202}#"#;
1203 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1204 assert!(metadata.is_err());
1205 eprintln!("{}", metadata.unwrap_err());
1206 }
1207
1208 #[test]
1209 fn test_undefined_variable() {
1210 let input_str = r#"sourceinfo #{
1211 FILES
1212 0: test.calyx
1213 POSITIONS
1214 0: 0 5
1215 1: 0 1
1216 2: 0 2
1217 MEMORY_LOCATIONS
1218 0: main.reg1
1219 1: main.reg2
1220 2: main.mem1 [1,4]
1221 VARIABLE_ASSIGNMENTS
1222 0: {
1223 x: ty b10, packed 0
1224 y: ty b10, packed 1
1225 z: ty b10, packed 2
1226 }
1227 1: {
1228 q: ty b14, packed 0
1229 }
1230 POSITION_STATE_MAP
1231 0: 0
1232 2: 2
1233 TYPES
1234}#"#;
1235 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1236 assert!(metadata.is_err());
1237 eprintln!("{}", metadata.unwrap_err());
1238 }
1239
1240 #[test]
1241 fn test_duplicate_variable_maps() {
1242 let input_str = r#"sourceinfo #{
1243 FILES
1244 0: test.calyx
1245 POSITIONS
1246 0: 0 5
1247 1: 0 1
1248 2: 0 2
1249 MEMORY_LOCATIONS
1250 0: main.reg1
1251 1: main.reg2
1252 2: main.mem1 [1,4]
1253 VARIABLE_ASSIGNMENTS
1254 0: {
1255 x: ty b10, packed 0
1256 y: ty b10, packed 1
1257 z: ty b10, packed 2
1258 }
1259 1: {
1260 q: ty b14, packed 0
1261 }
1262 1: {
1263 a: ty i15, packed 0
1264 }
1265 POSITION_STATE_MAP
1266 0: 0
1267 2: 1
1268 TYPES
1269}#"#;
1270 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1271 assert!(metadata.is_err());
1272 eprintln!("{}", metadata.unwrap_err());
1273 }
1274
1275 #[test]
1276 fn test_duplicate_variable_assignment() {
1277 let input_str = r#"sourceinfo #{
1278 FILES
1279 0: test.calyx
1280 POSITIONS
1281 0: 0 5
1282 1: 0 1
1283 2: 0 2
1284 MEMORY_LOCATIONS
1285 0: main.reg1
1286 1: main.reg2
1287 2: main.mem1 [1,4]
1288 VARIABLE_ASSIGNMENTS
1289 0: {
1290 x: ty b10, packed 0
1291 y: ty b10, packed 1
1292 z: ty b10, packed 2
1293 }
1294 1: {
1295 q: ty b14, packed 0
1296 q: ty b10, packed 1
1297
1298 }
1299 POSITION_STATE_MAP
1300 0: 0
1301 2: 1
1302 TYPES
1303}#"#;
1304 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1305 assert!(metadata.is_err());
1306 eprintln!("{}", metadata.unwrap_err());
1307 }
1308
1309 #[test]
1310 fn test_duplicate_mem_def() {
1311 let input_str = r#"sourceinfo #{
1312 FILES
1313 0: test.calyx
1314 POSITIONS
1315 0: 0 5
1316 1: 0 1
1317 2: 0 2
1318 MEMORY_LOCATIONS
1319 0: main.reg1
1320 1: main.reg2
1321 1: main.mem1 [1,4]
1322 VARIABLE_ASSIGNMENTS
1323 0: {
1324 x: ty b10, packed 0
1325 y: ty b10, packed 1
1326 z: ty b10, packed 2
1327 }
1328 1: {
1329 q: ty b14, packed 0
1330 }
1331 POSITION_STATE_MAP
1332 0: 0
1333 2: 1
1334 TYPES
1335}#"#;
1336 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1337 assert!(metadata.is_err());
1338 eprintln!("{}", metadata.unwrap_err());
1339 }
1340
1341 #[test]
1342 fn test_duplicate_pos_state() {
1343 let input_str = r#"sourceinfo #{
1344 FILES
1345 0: test.calyx
1346 POSITIONS
1347 0: 0 5
1348 1: 0 1
1349 2: 0 2
1350 MEMORY_LOCATIONS
1351 0: main.reg1
1352 1: main.reg2
1353 2: main.mem1 [1,4]
1354 VARIABLE_ASSIGNMENTS
1355 0: {
1356 x: ty b10, packed 0
1357 y: ty b10, packed 1
1358 z: ty b10, packed 2
1359 }
1360 1: {
1361 q: ty b14, packed 0
1362 }
1363 POSITION_STATE_MAP
1364 0: 0
1365 0: 1
1366 TYPES
1367}#"#;
1368 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1369 assert!(metadata.is_err());
1370 eprintln!("{}", metadata.unwrap_err());
1371 }
1372
1373 #[test]
1374 fn test_duplicate_file_parse() {
1375 let input_str = r#"sourceinfo #{
1376 FILES
1377 0: test.calyx
1378 0: test2.calyx
1379 2: test3.calyx
1380 POSITIONS
1381 0: 0 5:6
1382 1: 0 1
1383 2: 0 2
1384 }#"#;
1385 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1386 assert!(metadata.is_err());
1387 eprintln!("{}", metadata.unwrap_err());
1388 }
1389
1390 #[test]
1391 fn test_duplicate_position_parse() {
1392 let input_str = r#"sourceinfo #{
1393 FILES
1394 0: test.calyx
1395 1: test2.calyx
1396 2: test3.calyx
1397 POSITIONS
1398 0: 0 5
1399 0: 0 1
1400 2: 0 2
1401 }#"#;
1402 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1403
1404 assert!(metadata.is_err());
1405 eprintln!("{}", metadata.unwrap_err());
1406 }
1407
1408 #[test]
1409 fn test_unknown_type() {
1410 let input_str = r#"sourceinfo #{
1411 FILES
1412 0: test.calyx
1413 POSITIONS
1414 0: 0 5
1415 1: 0 1
1416 2: 0 2
1417 MEMORY_LOCATIONS
1418 0: main.reg1
1419 1: main.reg2
1420 2: main.mem1 [1,4]
1421 VARIABLE_ASSIGNMENTS
1422 0: {
1423 x: ty 2, packed 0
1424 y: ty b12, packed 1
1425 z: ty b12, packed 2
1426 }
1427 1: {
1428 q: ty b10, packed 0
1429 }
1430 POSITION_STATE_MAP
1431 0: 0
1432 1: 1
1433 TYPES
1434}#"#;
1435 let metadata = CalyxParser::parse_source_info_table(input_str).unwrap();
1436 assert!(metadata.is_err());
1437 eprintln!("{}", metadata.unwrap_err());
1438 }
1439
1440 #[test]
1441 fn test_serialize() {
1442 let mut metadata = SourceInfoTable::new_empty();
1443 metadata.add_file(0.into(), "test.calyx".into());
1444 metadata.add_file(1.into(), "test2.calyx".into());
1445 metadata.add_file(2.into(), "test3.calyx".into());
1446
1447 metadata.add_position(0.into(), 0.into(), LineNum::new(1), None);
1448 metadata.add_position(
1449 1.into(),
1450 1.into(),
1451 LineNum::new(2),
1452 Some(LineNum::new(4)),
1453 );
1454 metadata.add_position(150.into(), 2.into(), LineNum::new(148), None);
1455
1456 let mut serialized_str = vec![];
1457 metadata.serialize(&mut serialized_str).unwrap();
1458 let serialized_str = String::from_utf8(serialized_str).unwrap();
1459
1460 let parsed_metadata =
1461 CalyxParser::parse_source_info_table(&serialized_str)
1462 .unwrap()
1463 .unwrap();
1464
1465 assert_eq!(metadata, parsed_metadata)
1466 }
1467}