use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use cairo_lang_utils::{Intern, LookupIntern, define_short_id};
use path_clean::PathClean;
use smol_str::SmolStr;
use crate::db::{CORELIB_CRATE_NAME, FilesGroup};
use crate::span::{TextOffset, TextSpan};
pub const CAIRO_FILE_EXTENSION: &str = "cairo";
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum CrateLongId {
Real { name: SmolStr, discriminator: Option<SmolStr> },
Virtual { name: SmolStr, file_id: FileId, settings: String },
}
impl CrateLongId {
pub fn name(&self) -> SmolStr {
match self {
CrateLongId::Real { name, .. } | CrateLongId::Virtual { name, .. } => name.clone(),
}
}
}
define_short_id!(CrateId, CrateLongId, FilesGroup, lookup_intern_crate, intern_crate);
impl CrateId {
pub fn plain(
db: &(impl cairo_lang_utils::Upcast<dyn FilesGroup> + ?Sized),
name: &str,
) -> Self {
CrateLongId::Real { name: name.into(), discriminator: None }.intern(db)
}
pub fn core(db: &(impl cairo_lang_utils::Upcast<dyn FilesGroup> + ?Sized)) -> Self {
CrateLongId::Real { name: CORELIB_CRATE_NAME.into(), discriminator: None }.intern(db)
}
pub fn name(&self, db: &dyn FilesGroup) -> SmolStr {
self.lookup_intern(db).name()
}
}
pub trait UnstableSalsaId {
fn get_internal_id(&self) -> &salsa::InternId;
}
impl UnstableSalsaId for CrateId {
fn get_internal_id(&self) -> &salsa::InternId {
&self.0
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct FlagLongId(pub SmolStr);
define_short_id!(FlagId, FlagLongId, FilesGroup, lookup_intern_flag, intern_flag);
impl FlagId {
pub fn new(db: &dyn FilesGroup, name: &str) -> Self {
FlagLongId(name.into()).intern(db)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum FileLongId {
OnDisk(PathBuf),
Virtual(VirtualFile),
External(salsa::InternId),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum FileKind {
Module,
Expr,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct CodeMapping {
pub span: TextSpan,
pub origin: CodeOrigin,
}
impl CodeMapping {
pub fn translate(&self, span: TextSpan) -> Option<TextSpan> {
if self.span.contains(span) {
Some(match self.origin {
CodeOrigin::Start(origin_start) => {
let start = origin_start.add_width(span.start - self.span.start);
TextSpan { start, end: start.add_width(span.width()) }
}
CodeOrigin::Span(span) => span,
})
} else {
None
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum CodeOrigin {
Start(TextOffset),
Span(TextSpan),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct VirtualFile {
pub parent: Option<FileId>,
pub name: SmolStr,
pub content: Arc<str>,
pub code_mappings: Arc<[CodeMapping]>,
pub kind: FileKind,
}
impl VirtualFile {
fn full_path(&self, db: &dyn FilesGroup) -> String {
if let Some(parent) = self.parent {
format!("{}[{}]", parent.full_path(db), self.name)
} else {
self.name.clone().into()
}
}
}
define_short_id!(FileId, FileLongId, FilesGroup, lookup_intern_file, intern_file);
impl FileId {
pub fn new(db: &dyn FilesGroup, path: PathBuf) -> FileId {
FileLongId::OnDisk(path.clean()).intern(db)
}
pub fn file_name(self, db: &dyn FilesGroup) -> String {
match self.lookup_intern(db) {
FileLongId::OnDisk(path) => {
path.file_name().and_then(|x| x.to_str()).unwrap_or("<unknown>").to_string()
}
FileLongId::Virtual(vf) => vf.name.to_string(),
FileLongId::External(external_id) => db.ext_as_virtual(external_id).name.to_string(),
}
}
pub fn full_path(self, db: &dyn FilesGroup) -> String {
match self.lookup_intern(db) {
FileLongId::OnDisk(path) => path.to_str().unwrap_or("<unknown>").to_string(),
FileLongId::Virtual(vf) => vf.full_path(db),
FileLongId::External(external_id) => db.ext_as_virtual(external_id).full_path(db),
}
}
pub fn kind(self, db: &dyn FilesGroup) -> FileKind {
match self.lookup_intern(db) {
FileLongId::OnDisk(_) => FileKind::Module,
FileLongId::Virtual(vf) => vf.kind.clone(),
FileLongId::External(_) => FileKind::Module,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum Directory {
Real(PathBuf),
Virtual { files: BTreeMap<SmolStr, FileId>, dirs: BTreeMap<SmolStr, Box<Directory>> },
}
impl Directory {
pub fn file(&self, db: &dyn FilesGroup, name: SmolStr) -> FileId {
match self {
Directory::Real(path) => FileId::new(db, path.join(name.to_string())),
Directory::Virtual { files, dirs: _ } => files
.get(&name)
.copied()
.unwrap_or_else(|| FileId::new(db, PathBuf::from(name.as_str()))),
}
}
pub fn subdir(&self, name: SmolStr) -> Directory {
match self {
Directory::Real(path) => Directory::Real(path.join(name.to_string())),
Directory::Virtual { files: _, dirs } => {
if let Some(dir) = dirs.get(&name) {
dir.as_ref().clone()
} else {
Directory::Virtual { files: BTreeMap::new(), dirs: BTreeMap::new() }
}
}
}
}
}