1use crate::parsing::ast::Span;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
8pub struct Source {
9 pub attribute: String,
11
12 pub span: Span,
14}
15
16impl Source {
17 #[must_use]
18 pub fn new(attribute: impl Into<String>, span: Span) -> Self {
19 Self {
20 attribute: attribute.into(),
21 span,
22 }
23 }
24
25 #[must_use]
27 pub fn text_from<'a>(
28 &self,
29 sources: &'a HashMap<String, String>,
30 ) -> Option<std::borrow::Cow<'a, str>> {
31 let s = sources.get(&self.attribute)?;
32 s.get(self.span.start..self.span.end)
33 .map(std::borrow::Cow::Borrowed)
34 }
35
36 #[must_use]
40 pub fn extract_text(&self, source: &str) -> Option<String> {
41 let bytes = source.as_bytes();
42 if self.span.start < bytes.len() && self.span.end <= bytes.len() {
43 Some(String::from_utf8_lossy(&bytes[self.span.start..self.span.end]).to_string())
44 } else {
45 None
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use std::collections::HashMap;
54
55 #[test]
56 fn test_extract_text_valid() {
57 let source = "hello world";
58 let span = Span {
59 start: 0,
60 end: 5,
61 line: 1,
62 col: 0,
63 };
64 let loc = Source::new("test.lemma", span);
65 assert_eq!(loc.extract_text(source), Some("hello".to_string()));
66 }
67
68 #[test]
69 fn test_extract_text_middle() {
70 let source = "hello world";
71 let span = Span {
72 start: 6,
73 end: 11,
74 line: 1,
75 col: 6,
76 };
77 let loc = Source::new("test.lemma", span);
78 assert_eq!(loc.extract_text(source), Some("world".to_string()));
79 }
80
81 #[test]
82 fn test_extract_text_full_string() {
83 let source = "hello world";
84 let span = Span {
85 start: 0,
86 end: 11,
87 line: 1,
88 col: 0,
89 };
90 let loc = Source::new("test.lemma", span);
91 assert_eq!(loc.extract_text(source), Some("hello world".to_string()));
92 }
93
94 #[test]
95 fn test_extract_text_empty() {
96 let source = "hello world";
97 let span = Span {
98 start: 5,
99 end: 5,
100 line: 1,
101 col: 5,
102 };
103 let loc = Source::new("test.lemma", span);
104 assert_eq!(loc.extract_text(source), Some("".to_string()));
105 }
106
107 #[test]
108 fn test_extract_text_out_of_bounds_start() {
109 let source = "hello";
110 let span = Span {
111 start: 10,
112 end: 15,
113 line: 1,
114 col: 10,
115 };
116 let loc = Source::new("test.lemma", span);
117 assert_eq!(loc.extract_text(source), None);
118 }
119
120 #[test]
121 fn test_extract_text_out_of_bounds_end() {
122 let source = "hello";
123 let span = Span {
124 start: 0,
125 end: 10,
126 line: 1,
127 col: 0,
128 };
129 let loc = Source::new("test.lemma", span);
130 assert_eq!(loc.extract_text(source), None);
131 }
132
133 #[test]
134 fn test_extract_text_unicode() {
135 let source = "hello 世界";
136 let span = Span {
137 start: 6,
138 end: 12,
139 line: 1,
140 col: 6,
141 };
142 let loc = Source::new("test.lemma", span);
143 assert_eq!(loc.extract_text(source), Some("世界".to_string()));
144 }
145
146 #[test]
147 fn test_new() {
148 let span = Span {
149 start: 0,
150 end: 5,
151 line: 1,
152 col: 0,
153 };
154 let loc = Source::new("test.lemma", span);
155 assert_eq!(loc.attribute, "test.lemma");
156 }
157
158 #[test]
159 fn test_text_from() {
160 let mut sources = HashMap::new();
161 sources.insert("test.lemma".to_string(), "hello world".to_string());
162 let loc = Source::new(
163 "test.lemma",
164 Span {
165 start: 0,
166 end: 5,
167 line: 1,
168 col: 0,
169 },
170 );
171 assert_eq!(loc.text_from(&sources).as_deref(), Some("hello"));
172 }
173}