use crate::{span, LineCol, ProgramId, SourceId, Span};
use parking_lot::RwLock;
use std::{
collections::{BTreeSet, HashMap},
path::{Path, PathBuf},
str::FromStr,
};
use toml::Table;
#[derive(Debug, Default)]
pub struct SourceEngine {
next_source_id: RwLock<u32>,
path_to_source_map: RwLock<HashMap<PathBuf, SourceId>>,
source_to_path_map: RwLock<HashMap<SourceId, PathBuf>>,
source_to_buffer_map: RwLock<HashMap<SourceId, span::Source>>,
next_program_id: RwLock<u16>,
manifest_path_to_program_map: RwLock<HashMap<PathBuf, ProgramId>>,
manifest_path_to_package_info: RwLock<HashMap<PathBuf, (String, String)>>,
module_to_sources_map: RwLock<HashMap<ProgramId, BTreeSet<SourceId>>>,
}
impl Clone for SourceEngine {
fn clone(&self) -> Self {
SourceEngine {
next_source_id: RwLock::new(*self.next_source_id.read()),
path_to_source_map: RwLock::new(self.path_to_source_map.read().clone()),
source_to_path_map: RwLock::new(self.source_to_path_map.read().clone()),
source_to_buffer_map: RwLock::new(self.source_to_buffer_map.read().clone()),
next_program_id: RwLock::new(*self.next_program_id.read()),
manifest_path_to_program_map: RwLock::new(
self.manifest_path_to_program_map.read().clone(),
),
manifest_path_to_package_info: RwLock::new(
self.manifest_path_to_package_info.read().clone(),
),
module_to_sources_map: RwLock::new(self.module_to_sources_map.read().clone()),
}
}
}
impl SourceEngine {
const AUTOGENERATED_PATH: &'static str = "<autogenerated>";
pub fn is_span_in_autogenerated(&self, span: &crate::Span) -> Option<bool> {
span.source_id().map(|s| self.is_source_id_autogenerated(s))
}
pub fn is_source_id_autogenerated(&self, source_id: &SourceId) -> bool {
self.get_path(source_id)
.display()
.to_string()
.contains("<autogenerated>")
}
pub fn get_or_create_source_buffer(
&self,
source_id: &SourceId,
source: span::Source,
) -> span::Source {
let mut map = self.source_to_buffer_map.write();
if let Some(existing) = map.get_mut(source_id) {
if existing.text != source.text {
*existing = source;
}
existing.clone()
} else {
map.insert(*source_id, source.clone());
source
}
}
pub fn get_source_id(&self, path: &PathBuf) -> SourceId {
{
let source_map = self.path_to_source_map.read();
if source_map.contains_key(path) {
return source_map.get(path).copied().unwrap();
}
}
let program_id = self.get_or_create_program_id_from_manifest_path(path);
self.get_source_id_with_program_id(path, program_id)
}
pub fn get_source_id_with_program_id(&self, path: &PathBuf, program_id: ProgramId) -> SourceId {
{
let source_map = self.path_to_source_map.read();
if source_map.contains_key(path) {
return source_map.get(path).copied().unwrap();
}
}
let source_id = SourceId::new(program_id.0, *self.next_source_id.read());
{
let mut next_id = self.next_source_id.write();
*next_id += 1;
let mut source_map = self.path_to_source_map.write();
source_map.insert(path.clone(), source_id);
let mut path_map = self.source_to_path_map.write();
path_map.insert(source_id, path.clone());
}
let mut module_map = self.module_to_sources_map.write();
module_map.entry(program_id).or_default().insert(source_id);
source_id
}
pub fn get_associated_autogenerated_source_id(&self, source_id: &SourceId) -> Option<SourceId> {
let path = self.get_path(source_id);
let file_name = PathBuf::from_str(path.file_name()?.to_str()?).ok()?;
let path = path.with_file_name(format!(
"{}.{}.{}",
file_name.file_stem()?.to_str()?,
Self::AUTOGENERATED_PATH,
file_name.extension()?.to_str()?
));
Some(self.get_source_id_with_program_id(&path, source_id.program_id()))
}
pub fn get_path(&self, source_id: &SourceId) -> PathBuf {
self.source_to_path_map
.read()
.get(source_id)
.unwrap()
.clone()
}
pub fn get_program_id_from_manifest_path(&self, path: impl AsRef<Path>) -> Option<ProgramId> {
let manifest_path = sway_utils::find_parent_manifest_dir(&path)
.unwrap_or_else(|| path.as_ref().to_path_buf());
self.manifest_path_to_program_map
.read()
.get(&manifest_path)
.copied()
}
pub fn get_or_create_program_id_from_manifest_path(&self, path: &PathBuf) -> ProgramId {
let manifest_path = sway_utils::find_parent_manifest_dir(path).unwrap_or(path.clone());
let mut module_map = self.manifest_path_to_program_map.write();
*module_map.entry(manifest_path.clone()).or_insert_with(|| {
let mut next_id = self.next_program_id.write();
*next_id += 1;
ProgramId::new(*next_id)
})
}
pub fn get_manifest_path_from_program_id(&self, program_id: &ProgramId) -> Option<PathBuf> {
let path_to_module_map = self.manifest_path_to_program_map.read();
path_to_module_map
.iter()
.find(|(_, &id)| id == *program_id)
.map(|(path, _)| path.clone())
}
pub fn get_file_name(&self, source_id: &SourceId) -> Option<String> {
self.get_path(source_id)
.as_path()
.file_name()
.map(|file_name| file_name.to_string_lossy())
.map(|file_name| file_name.to_string())
}
pub fn all_files(&self) -> Vec<PathBuf> {
let s = self.source_to_path_map.read();
let mut v = s.values().cloned().collect::<Vec<_>>();
v.sort();
v
}
pub fn get_source_ids_from_program_id(
&self,
program_id: ProgramId,
) -> Option<BTreeSet<SourceId>> {
let s = self.module_to_sources_map.read();
s.get(&program_id).cloned()
}
fn get_package_name_and_version(&self, manifest_path: &Path) -> (String, String) {
fn get_fallback_package_name_and_version(manifest_path: &Path) -> (String, String) {
let package_dir_name = manifest_path
.iter()
.next_back()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| "<unknown>".to_string());
(package_dir_name, String::new())
}
fn get_project_field(toml: &Table, field_name: &str) -> Option<String> {
toml.get("project")
.and_then(|v| v.get(field_name))
.and_then(|field| field.as_str())
.map(|value| value.to_string())
}
let forc_toml_path = manifest_path.join("Forc.toml");
if !forc_toml_path.exists() {
return get_fallback_package_name_and_version(manifest_path);
}
let content = match std::fs::read_to_string(&forc_toml_path) {
Ok(content) => content,
Err(_) => return get_fallback_package_name_and_version(manifest_path),
};
let toml = match content.parse::<Table>() {
Ok(toml) => toml,
Err(_) => return get_fallback_package_name_and_version(manifest_path),
};
let package_name = get_project_field(&toml, "name").unwrap_or("<unknown>".to_string());
let package_version = get_project_field(&toml, "version").unwrap_or_default();
(package_name, package_version)
}
pub fn get_source_location(&self, span: &Span) -> SourceLocation {
let Some(source_id) = span.source_id() else {
return SourceLocation::unknown();
};
let source_file = self.get_path(source_id);
let program_id = self
.get_program_id_from_manifest_path(&source_file)
.expect("the `source_file` is retrieved from the `SourceEngine::get_path` function so the manifest path and program id must exist");
let manifest_path = self.get_manifest_path_from_program_id(&program_id).expect(
"the `program_id` is retrieved from the `SourceEngine` so the manifest path must exist",
);
let mut package_infos = self.manifest_path_to_package_info.write();
let (package_name, package_version) = &package_infos
.entry(manifest_path.clone())
.or_insert_with(|| self.get_package_name_and_version(&manifest_path));
let pkg = if package_version.is_empty() {
package_name.clone()
} else {
format!("{package_name}@{package_version}")
};
let source_file = source_file
.strip_prefix(&manifest_path)
.expect("the `manifest_path` is a parent of the `source_file`")
.to_string_lossy();
let source_file = if let Some(source_file_no_leading_slash) = source_file.strip_prefix("/")
{
source_file_no_leading_slash.to_string()
} else {
source_file.to_string()
};
SourceLocation {
pkg,
file: source_file,
loc: span.start_line_col_one_index(),
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourceLocation {
pub pkg: String,
pub file: String,
pub loc: LineCol,
}
impl SourceLocation {
pub fn unknown() -> Self {
Self {
pkg: "<unknown>".to_string(),
file: "<unknown>".to_string(),
loc: LineCol::default(),
}
}
}