logicaffeine_compile/
sourcemap.rs1use crate::intern::Symbol;
25use crate::token::Span;
26use std::collections::HashMap;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum OwnershipRole {
31 GiveObject,
33 GiveRecipient,
35 ShowObject,
37 ShowRecipient,
39 LetBinding,
41 SetTarget,
43 ZoneLocal,
45}
46
47#[derive(Debug, Clone)]
49pub struct VarOrigin {
50 pub logos_name: Symbol,
51 pub span: Span,
52 pub role: OwnershipRole,
53}
54
55#[derive(Debug, Clone, Default)]
57pub struct SourceMap {
58 line_to_span: HashMap<u32, Span>,
60
61 var_origins: HashMap<String, VarOrigin>,
63
64 logos_source: String,
66}
67
68impl SourceMap {
69 pub fn new(logos_source: String) -> Self {
71 Self {
72 line_to_span: HashMap::new(),
73 var_origins: HashMap::new(),
74 logos_source,
75 }
76 }
77
78 pub fn get_span_for_line(&self, line: u32) -> Option<Span> {
80 self.line_to_span.get(&line).copied()
81 }
82
83 pub fn get_var_origin(&self, rust_var: &str) -> Option<&VarOrigin> {
85 self.var_origins.get(rust_var)
86 }
87
88 pub fn logos_source(&self) -> &str {
90 &self.logos_source
91 }
92
93 pub fn find_nearest_span(&self, rust_line: u32) -> Option<Span> {
95 if let Some(span) = self.line_to_span.get(&rust_line) {
97 return Some(*span);
98 }
99
100 for offset in 1..=5 {
102 if rust_line > offset {
103 if let Some(span) = self.line_to_span.get(&(rust_line - offset)) {
104 return Some(*span);
105 }
106 }
107 if let Some(span) = self.line_to_span.get(&(rust_line + offset)) {
108 return Some(*span);
109 }
110 }
111
112 None
113 }
114}
115
116#[derive(Debug)]
118pub struct SourceMapBuilder {
119 current_line: u32,
120 map: SourceMap,
121}
122
123impl SourceMapBuilder {
124 pub fn new(logos_source: &str) -> Self {
126 Self {
127 current_line: 1,
128 map: SourceMap::new(logos_source.to_string()),
129 }
130 }
131
132 pub fn record_line(&mut self, logos_span: Span) {
134 self.map.line_to_span.insert(self.current_line, logos_span);
135 }
136
137 pub fn record_var(&mut self, rust_name: &str, logos_name: Symbol, span: Span, role: OwnershipRole) {
139 self.map.var_origins.insert(
140 rust_name.to_string(),
141 VarOrigin {
142 logos_name,
143 span,
144 role,
145 },
146 );
147 }
148
149 pub fn newline(&mut self) {
151 self.current_line += 1;
152 }
153
154 pub fn add_lines(&mut self, count: u32) {
156 self.current_line += count;
157 }
158
159 pub fn current_line(&self) -> u32 {
161 self.current_line
162 }
163
164 pub fn build(self) -> SourceMap {
166 self.map
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn source_map_stores_line_mappings() {
176 let mut map = SourceMap::new("Let x be 5.".to_string());
177 map.line_to_span.insert(1, Span::new(0, 11));
178
179 assert_eq!(map.get_span_for_line(1), Some(Span::new(0, 11)));
180 assert_eq!(map.get_span_for_line(2), None);
181 }
182
183 #[test]
184 fn source_map_builder_tracks_lines() {
185 let mut builder = SourceMapBuilder::new("test source");
186 assert_eq!(builder.current_line(), 1);
187
188 builder.newline();
189 assert_eq!(builder.current_line(), 2);
190
191 builder.add_lines(3);
192 assert_eq!(builder.current_line(), 5);
193 }
194
195 #[test]
196 fn source_map_builder_records_spans() {
197 let mut builder = SourceMapBuilder::new("Let x be 5.\nLet y be 10.");
198 builder.record_line(Span::new(0, 11));
199 builder.newline();
200 builder.record_line(Span::new(12, 24));
201
202 let map = builder.build();
203 assert_eq!(map.get_span_for_line(1), Some(Span::new(0, 11)));
204 assert_eq!(map.get_span_for_line(2), Some(Span::new(12, 24)));
205 }
206
207 #[test]
208 fn find_nearest_span_searches_nearby() {
209 let mut builder = SourceMapBuilder::new("source");
210 builder.record_line(Span::new(0, 10));
211 builder.add_lines(5);
212 let map = builder.build();
215 assert_eq!(map.find_nearest_span(3), Some(Span::new(0, 10)));
217 }
218}