1use std::collections::BTreeMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use cairo_lang_utils::{Intern, LookupIntern, define_short_id};
6use path_clean::PathClean;
7use smol_str::SmolStr;
8
9use crate::db::{CORELIB_CRATE_NAME, FilesGroup};
10use crate::span::{TextOffset, TextSpan};
11
12pub const CAIRO_FILE_EXTENSION: &str = "cairo";
13
14#[derive(Clone, Debug, Hash, PartialEq, Eq)]
16pub enum CrateLongId {
17 Real { name: SmolStr, discriminator: Option<SmolStr> },
19 Virtual { name: SmolStr, file_id: FileId, settings: String },
21}
22impl CrateLongId {
23 pub fn name(&self) -> SmolStr {
24 match self {
25 CrateLongId::Real { name, .. } | CrateLongId::Virtual { name, .. } => name.clone(),
26 }
27 }
28}
29define_short_id!(CrateId, CrateLongId, FilesGroup, lookup_intern_crate, intern_crate);
30impl CrateId {
31 pub fn plain(
33 db: &(impl cairo_lang_utils::Upcast<dyn FilesGroup> + ?Sized),
34 name: &str,
35 ) -> Self {
36 CrateLongId::Real { name: name.into(), discriminator: None }.intern(db)
37 }
38
39 pub fn core(db: &(impl cairo_lang_utils::Upcast<dyn FilesGroup> + ?Sized)) -> Self {
41 CrateLongId::Real { name: CORELIB_CRATE_NAME.into(), discriminator: None }.intern(db)
42 }
43
44 pub fn name(&self, db: &dyn FilesGroup) -> SmolStr {
45 self.lookup_intern(db).name()
46 }
47}
48
49pub trait UnstableSalsaId {
54 fn get_internal_id(&self) -> &salsa::InternId;
55}
56impl UnstableSalsaId for CrateId {
57 fn get_internal_id(&self) -> &salsa::InternId {
58 &self.0
59 }
60}
61
62#[derive(Clone, Debug, Hash, PartialEq, Eq)]
64pub struct FlagLongId(pub SmolStr);
65define_short_id!(FlagId, FlagLongId, FilesGroup, lookup_intern_flag, intern_flag);
66impl FlagId {
67 pub fn new(db: &dyn FilesGroup, name: &str) -> Self {
68 FlagLongId(name.into()).intern(db)
69 }
70}
71
72#[derive(Clone, Debug, Hash, PartialEq, Eq)]
75pub enum FileLongId {
76 OnDisk(PathBuf),
77 Virtual(VirtualFile),
78 External(salsa::InternId),
79}
80#[derive(Clone, Debug, Hash, PartialEq, Eq)]
82pub enum FileKind {
83 Module,
84 Expr,
85}
86
87#[derive(Clone, Debug, Hash, PartialEq, Eq)]
89pub struct CodeMapping {
90 pub span: TextSpan,
91 pub origin: CodeOrigin,
92}
93impl CodeMapping {
94 pub fn translate(&self, span: TextSpan) -> Option<TextSpan> {
95 if self.span.contains(span) {
96 Some(match self.origin {
97 CodeOrigin::Start(origin_start) => {
98 let start = origin_start.add_width(span.start - self.span.start);
99 TextSpan { start, end: start.add_width(span.width()) }
100 }
101 CodeOrigin::Span(span) => span,
102 })
103 } else {
104 None
105 }
106 }
107}
108
109#[derive(Clone, Debug, Hash, PartialEq, Eq)]
111pub enum CodeOrigin {
112 Start(TextOffset),
114 Span(TextSpan),
116}
117
118#[derive(Clone, Debug, Hash, PartialEq, Eq)]
119pub struct VirtualFile {
120 pub parent: Option<FileId>,
121 pub name: SmolStr,
122 pub content: Arc<str>,
123 pub code_mappings: Arc<[CodeMapping]>,
124 pub kind: FileKind,
125}
126impl VirtualFile {
127 fn full_path(&self, db: &dyn FilesGroup) -> String {
128 if let Some(parent) = self.parent {
129 format!("{}[{}]", parent.full_path(db), self.name)
131 } else {
132 self.name.clone().into()
133 }
134 }
135}
136
137define_short_id!(FileId, FileLongId, FilesGroup, lookup_intern_file, intern_file);
138impl FileId {
139 pub fn new(db: &dyn FilesGroup, path: PathBuf) -> FileId {
140 FileLongId::OnDisk(path.clean()).intern(db)
141 }
142 pub fn file_name(self, db: &dyn FilesGroup) -> String {
143 match self.lookup_intern(db) {
144 FileLongId::OnDisk(path) => {
145 path.file_name().and_then(|x| x.to_str()).unwrap_or("<unknown>").to_string()
146 }
147 FileLongId::Virtual(vf) => vf.name.to_string(),
148 FileLongId::External(external_id) => db.ext_as_virtual(external_id).name.to_string(),
149 }
150 }
151 pub fn full_path(self, db: &dyn FilesGroup) -> String {
152 match self.lookup_intern(db) {
153 FileLongId::OnDisk(path) => path.to_str().unwrap_or("<unknown>").to_string(),
154 FileLongId::Virtual(vf) => vf.full_path(db),
155 FileLongId::External(external_id) => db.ext_as_virtual(external_id).full_path(db),
156 }
157 }
158 pub fn kind(self, db: &dyn FilesGroup) -> FileKind {
159 match self.lookup_intern(db) {
160 FileLongId::OnDisk(_) => FileKind::Module,
161 FileLongId::Virtual(vf) => vf.kind.clone(),
162 FileLongId::External(_) => FileKind::Module,
163 }
164 }
165}
166
167#[derive(Clone, Debug, Hash, PartialEq, Eq)]
168pub enum Directory {
169 Real(PathBuf),
171 Virtual { files: BTreeMap<SmolStr, FileId>, dirs: BTreeMap<SmolStr, Box<Directory>> },
173}
174
175impl Directory {
176 pub fn file(&self, db: &dyn FilesGroup, name: SmolStr) -> FileId {
179 match self {
180 Directory::Real(path) => FileId::new(db, path.join(name.to_string())),
181 Directory::Virtual { files, dirs: _ } => files
182 .get(&name)
183 .copied()
184 .unwrap_or_else(|| FileId::new(db, PathBuf::from(name.as_str()))),
185 }
186 }
187
188 pub fn subdir(&self, name: SmolStr) -> Directory {
191 match self {
192 Directory::Real(path) => Directory::Real(path.join(name.to_string())),
193 Directory::Virtual { files: _, dirs } => {
194 if let Some(dir) = dirs.get(&name) {
195 dir.as_ref().clone()
196 } else {
197 Directory::Virtual { files: BTreeMap::new(), dirs: BTreeMap::new() }
198 }
199 }
200 }
201 }
202}