Skip to main content

tensorlogic_ir/
metadata.rs

1//! Metadata and source location tracking.
2
3use serde::{Deserialize, Serialize};
4
5/// Source code location information
6#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct SourceLocation {
8    pub file: String,
9    pub line: usize,
10    pub column: usize,
11}
12
13impl SourceLocation {
14    pub fn new(file: impl Into<String>, line: usize, column: usize) -> Self {
15        SourceLocation {
16            file: file.into(),
17            line,
18            column,
19        }
20    }
21
22    pub fn unknown() -> Self {
23        SourceLocation {
24            file: "<unknown>".to_string(),
25            line: 0,
26            column: 0,
27        }
28    }
29}
30
31impl std::fmt::Display for SourceLocation {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{}:{}:{}", self.file, self.line, self.column)
34    }
35}
36
37/// Span information covering a range in source code
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
39pub struct SourceSpan {
40    pub start: SourceLocation,
41    pub end: SourceLocation,
42}
43
44impl SourceSpan {
45    pub fn new(start: SourceLocation, end: SourceLocation) -> Self {
46        SourceSpan { start, end }
47    }
48
49    pub fn single(location: SourceLocation) -> Self {
50        SourceSpan {
51            start: location.clone(),
52            end: location,
53        }
54    }
55
56    pub fn unknown() -> Self {
57        SourceSpan {
58            start: SourceLocation::unknown(),
59            end: SourceLocation::unknown(),
60        }
61    }
62}
63
64impl std::fmt::Display for SourceSpan {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        if self.start.file == self.end.file {
67            if self.start.line == self.end.line {
68                write!(
69                    f,
70                    "{}:{}:{}-{}",
71                    self.start.file, self.start.line, self.start.column, self.end.column
72                )
73            } else {
74                write!(
75                    f,
76                    "{} (lines {}-{})",
77                    self.start.file, self.start.line, self.end.line
78                )
79            }
80        } else {
81            write!(f, "{} to {}", self.start, self.end)
82        }
83    }
84}
85
86/// Provenance information tracking the origin of an IR node
87#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
88pub struct Provenance {
89    /// Source rule ID or identifier
90    pub rule_id: Option<String>,
91    /// Source file information
92    pub source_file: Option<String>,
93    /// Source span
94    pub span: Option<SourceSpan>,
95    /// Additional metadata as key-value pairs
96    pub attributes: Vec<(String, String)>,
97}
98
99impl Provenance {
100    pub fn new() -> Self {
101        Provenance {
102            rule_id: None,
103            source_file: None,
104            span: None,
105            attributes: Vec::new(),
106        }
107    }
108
109    pub fn with_rule_id(mut self, rule_id: impl Into<String>) -> Self {
110        self.rule_id = Some(rule_id.into());
111        self
112    }
113
114    pub fn with_source_file(mut self, source_file: impl Into<String>) -> Self {
115        self.source_file = Some(source_file.into());
116        self
117    }
118
119    pub fn with_span(mut self, span: SourceSpan) -> Self {
120        self.span = Some(span);
121        self
122    }
123
124    pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
125        self.attributes.push((key.into(), value.into()));
126        self
127    }
128
129    pub fn get_attribute(&self, key: &str) -> Option<&str> {
130        self.attributes
131            .iter()
132            .find(|(k, _)| k == key)
133            .map(|(_, v)| v.as_str())
134    }
135}
136
137impl Default for Provenance {
138    fn default() -> Self {
139        Self::new()
140    }
141}
142
143/// Metadata container that can be attached to IR nodes
144#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
145pub struct Metadata {
146    /// Human-readable name or comment
147    pub name: Option<String>,
148    /// Source location information
149    pub span: Option<SourceSpan>,
150    /// Provenance tracking
151    pub provenance: Option<Provenance>,
152    /// Additional custom attributes
153    pub attributes: Vec<(String, String)>,
154}
155
156impl Metadata {
157    pub fn new() -> Self {
158        Self::default()
159    }
160
161    pub fn with_name(mut self, name: impl Into<String>) -> Self {
162        self.name = Some(name.into());
163        self
164    }
165
166    pub fn with_span(mut self, span: SourceSpan) -> Self {
167        self.span = Some(span);
168        self
169    }
170
171    pub fn with_provenance(mut self, provenance: Provenance) -> Self {
172        self.provenance = Some(provenance);
173        self
174    }
175
176    pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
177        self.attributes.push((key.into(), value.into()));
178        self
179    }
180
181    pub fn get_attribute(&self, key: &str) -> Option<&str> {
182        self.attributes
183            .iter()
184            .find(|(k, _)| k == key)
185            .map(|(_, v)| v.as_str())
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn test_source_location() {
195        let loc = SourceLocation::new("test.tl", 10, 5);
196        assert_eq!(loc.line, 10);
197        assert_eq!(loc.column, 5);
198        assert_eq!(loc.to_string(), "test.tl:10:5");
199    }
200
201    #[test]
202    fn test_source_span() {
203        let start = SourceLocation::new("test.tl", 10, 5);
204        let end = SourceLocation::new("test.tl", 10, 20);
205        let span = SourceSpan::new(start, end);
206        assert_eq!(span.to_string(), "test.tl:10:5-20");
207    }
208
209    #[test]
210    fn test_provenance() {
211        let prov = Provenance::new()
212            .with_rule_id("rule_1")
213            .with_source_file("test.tl")
214            .with_attribute("author", "system");
215
216        assert_eq!(prov.rule_id, Some("rule_1".to_string()));
217        assert_eq!(prov.get_attribute("author"), Some("system"));
218    }
219
220    #[test]
221    fn test_metadata() {
222        let span = SourceSpan::single(SourceLocation::new("test.tl", 10, 5));
223        let prov = Provenance::new().with_rule_id("rule_1");
224
225        let meta = Metadata::new()
226            .with_name("transitivity")
227            .with_span(span.clone())
228            .with_provenance(prov.clone())
229            .with_attribute("version", "1.0");
230
231        assert_eq!(meta.name, Some("transitivity".to_string()));
232        assert_eq!(meta.span, Some(span));
233        assert_eq!(meta.provenance, Some(prov));
234        assert_eq!(meta.get_attribute("version"), Some("1.0"));
235    }
236}