use crate::intern::Symbol;
use crate::token::Span;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OwnershipRole {
GiveObject,
GiveRecipient,
ShowObject,
ShowRecipient,
LetBinding,
SetTarget,
ZoneLocal,
}
#[derive(Debug, Clone)]
pub struct VarOrigin {
pub logos_name: Symbol,
pub span: Span,
pub role: OwnershipRole,
}
#[derive(Debug, Clone, Default)]
pub struct SourceMap {
line_to_span: HashMap<u32, Span>,
var_origins: HashMap<String, VarOrigin>,
logos_source: String,
}
impl SourceMap {
pub fn new(logos_source: String) -> Self {
Self {
line_to_span: HashMap::new(),
var_origins: HashMap::new(),
logos_source,
}
}
pub fn get_span_for_line(&self, line: u32) -> Option<Span> {
self.line_to_span.get(&line).copied()
}
pub fn get_var_origin(&self, rust_var: &str) -> Option<&VarOrigin> {
self.var_origins.get(rust_var)
}
pub fn logos_source(&self) -> &str {
&self.logos_source
}
pub fn find_nearest_span(&self, rust_line: u32) -> Option<Span> {
if let Some(span) = self.line_to_span.get(&rust_line) {
return Some(*span);
}
for offset in 1..=5 {
if rust_line > offset {
if let Some(span) = self.line_to_span.get(&(rust_line - offset)) {
return Some(*span);
}
}
if let Some(span) = self.line_to_span.get(&(rust_line + offset)) {
return Some(*span);
}
}
None
}
}
#[derive(Debug)]
pub struct SourceMapBuilder {
current_line: u32,
map: SourceMap,
}
impl SourceMapBuilder {
pub fn new(logos_source: &str) -> Self {
Self {
current_line: 1,
map: SourceMap::new(logos_source.to_string()),
}
}
pub fn record_line(&mut self, logos_span: Span) {
self.map.line_to_span.insert(self.current_line, logos_span);
}
pub fn record_var(&mut self, rust_name: &str, logos_name: Symbol, span: Span, role: OwnershipRole) {
self.map.var_origins.insert(
rust_name.to_string(),
VarOrigin {
logos_name,
span,
role,
},
);
}
pub fn newline(&mut self) {
self.current_line += 1;
}
pub fn add_lines(&mut self, count: u32) {
self.current_line += count;
}
pub fn current_line(&self) -> u32 {
self.current_line
}
pub fn build(self) -> SourceMap {
self.map
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_map_stores_line_mappings() {
let mut map = SourceMap::new("Let x be 5.".to_string());
map.line_to_span.insert(1, Span::new(0, 11));
assert_eq!(map.get_span_for_line(1), Some(Span::new(0, 11)));
assert_eq!(map.get_span_for_line(2), None);
}
#[test]
fn source_map_builder_tracks_lines() {
let mut builder = SourceMapBuilder::new("test source");
assert_eq!(builder.current_line(), 1);
builder.newline();
assert_eq!(builder.current_line(), 2);
builder.add_lines(3);
assert_eq!(builder.current_line(), 5);
}
#[test]
fn source_map_builder_records_spans() {
let mut builder = SourceMapBuilder::new("Let x be 5.\nLet y be 10.");
builder.record_line(Span::new(0, 11));
builder.newline();
builder.record_line(Span::new(12, 24));
let map = builder.build();
assert_eq!(map.get_span_for_line(1), Some(Span::new(0, 11)));
assert_eq!(map.get_span_for_line(2), Some(Span::new(12, 24)));
}
#[test]
fn find_nearest_span_searches_nearby() {
let mut builder = SourceMapBuilder::new("source");
builder.record_line(Span::new(0, 10));
builder.add_lines(5);
let map = builder.build();
assert_eq!(map.find_nearest_span(3), Some(Span::new(0, 10)));
}
}