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