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