1use std::{
2 str::FromStr,
3 sync::{Arc, Mutex, mpsc::Sender},
4};
5
6use dada_ir_ast::{
7 DebugEvent,
8 ast::{AstFunction, AstItem, AstMember, Identifier},
9 diagnostic::Diagnostic,
10 inputs::{CompilationRoot, Krate, SourceFile},
11 span::AbsoluteSpan,
12};
13use dada_util::{Fallible, FromImpls, Map, Set, bail, debug};
14use salsa::{Database as _, Durability, Event, EventKind, Setter};
15use url::Url;
16
17mod fork;
18pub use fork::Fork;
19mod realfs;
20pub use realfs::RealFs;
21mod vfs;
22pub use vfs::VirtualFileSystem;
23use vfs::{ToUrl, UrlPath};
24
25use dada_parser::prelude::*;
26
27#[salsa::db]
28#[derive(Clone)]
29pub struct Compiler {
30 storage: salsa::Storage<Self>,
31
32 inputs: Arc<Mutex<Inputs>>,
38
39 vfs: Arc<dyn VirtualFileSystem>,
41
42 debug_tx: Option<Sender<DebugEvent>>,
44}
45
46impl Compiler {
47 pub fn new(vfs: impl VirtualFileSystem, debug_tx: Option<Sender<DebugEvent>>) -> Self {
48 Self {
49 storage: Default::default(),
50 inputs: Default::default(),
51 vfs: Arc::new(vfs),
52 debug_tx,
53 }
54 }
55
56 pub fn fork(&self) -> Fork<Self> {
59 Fork::from(Self {
60 storage: self.storage.clone(),
61 inputs: self.inputs.clone(),
62 vfs: self.vfs.clone(),
63 debug_tx: self.debug_tx.clone(),
64 })
65 }
66
67 pub fn load_source_file(&mut self, source_url: &(impl ToUrl + ?Sized)) -> Fallible<SourceFile> {
69 let source_url = &source_url.to_url(&*self.vfs)?;
70 let contents = match self.vfs.contents(source_url) {
71 Ok(s) => Ok(s),
72 Err(e) => Err(e.to_string()),
73 };
74
75 self.open_source_file(source_url, contents)
76 }
77
78 pub fn open_source_file(
82 &mut self,
83 source_url: &(impl ToUrl + ?Sized),
84 contents: Result<String, String>,
85 ) -> Fallible<SourceFile> {
86 let source_url = &source_url.to_url(&*self.vfs)?;
87 let source_file = match self.get_source_file(source_url) {
88 Some(v) => v,
89 None => {
90 self.add_crate_containing_source_file(source_url)?;
91 self.get_or_create_source_file(source_url)
92 }
93 };
94
95 let _ = source_file.set_contents(self).to(contents);
96 Ok(source_file)
97 }
98
99 pub fn get_previously_opened_source_file(
102 &mut self,
103 source_url: &(impl ToUrl + ?Sized),
104 ) -> Fallible<SourceFile> {
105 let source_url = source_url.to_url(&*self.vfs)?;
106 match self.get_source_file(&source_url) {
107 Some(v) => Ok(v),
108 None => {
109 bail!("no source file `{source_url}`")
110 }
111 }
112 }
113
114 pub fn add_crate_containing_source_file(&mut self, source_url: &Url) -> Fallible<Krate> {
118 let url_path = UrlPath::from(source_url.clone());
119
120 if !url_path.is_dada_file() {
121 bail!("source URL not a `.dada` file: `{source_url}`");
122 }
123
124 let mut krate_path = url_path.clone();
127 while !krate_path.is_empty() {
128 krate_path = krate_path.pop();
129
130 if !self.vfs.exists(&krate_path.dada_url()) {
131 break;
132 }
133 }
134
135 self.add_crate_with_root_path(&krate_path.dada_url())
136 }
137
138 pub fn add_crate_with_root_path(&mut self, root_url: &Url) -> Fallible<Krate> {
141 let url_path = UrlPath::from(root_url.clone());
142
143 if !url_path.is_dada_file() {
144 bail!("crate root path should have `.dada` extension: `{root_url}`");
145 }
146
147 let crate_name = url_path.final_module_name().to_string();
148
149 let root_dir_path = url_path.make_directory();
153
154 self.add_crate(crate_name.to_string(), root_dir_path.url())
155 }
156
157 pub fn codegen_main_fn(&self, source_file: SourceFile) -> &Option<Vec<u8>> {
159 dada_codegen::codegen_main_fn(self, source_file)
160 }
161
162 pub fn check_all(&self, source_file: SourceFile) -> Vec<&Diagnostic> {
164 Self::deduplicated(check_all::accumulated::<Diagnostic>(self, source_file))
165 }
166
167 pub fn probe_variable_type(&self, span: AbsoluteSpan) -> Option<String> {
169 self.attach(|db| dada_probe::probe_variable_type(db, span))
170 }
171
172 pub fn probe_expression_type(&self, span: AbsoluteSpan) -> Option<String> {
174 self.attach(|db| dada_probe::probe_expression_type(db, span))
175 }
176
177 pub fn probe_ast(&self, span: AbsoluteSpan) -> Option<String> {
179 self.attach(|db| dada_probe::probe_ast(db, span))
180 }
181
182 fn deduplicated(mut diagnostics: Vec<&Diagnostic>) -> Vec<&Diagnostic> {
183 let mut new = Set::default();
184 diagnostics.retain(|&d| new.insert(d));
185 diagnostics
186 }
187
188 pub fn fn_asts(&self, source_file: SourceFile) -> String {
189 use std::fmt::Write;
190
191 let mut output = String::new();
192
193 self.attach(|_db| {
194 let source = source_file.url_display(self);
195 writeln!(output, "# fn parse tree from {source}").unwrap();
196 writeln!(output).unwrap();
197
198 writeln!(output, "{}", fn_asts(self, source_file)).unwrap();
199 });
200
201 output
202 }
203
204 pub fn root(&self) -> CompilationRoot {
206 let mut inputs = self.inputs.lock().unwrap();
207 if let Some(root) = inputs.root {
208 return root;
209 }
210
211 let libdada = Krate::new(self, "dada".to_string());
213 inputs.directories.insert(libdada, KrateSource::Libdada);
214
215 let root = CompilationRoot::new(self, vec![libdada]);
216 inputs.root = Some(root);
217 root
218 }
219
220 fn add_crate(
226 &mut self,
227 crate_name: String,
228 new_source: impl Into<KrateSource>,
229 ) -> Fallible<Krate> {
230 let new_source: KrateSource = new_source.into();
231 let root = self.root();
232 let mut crates = root.crates(self).clone();
233
234 if let Some(&krate) = crates.iter().find(|c| *c.name(self) == crate_name) {
235 let inputs = self.inputs.lock().unwrap();
236 let krate_source = inputs.directories.get(&krate).unwrap();
237 if *krate_source == new_source {
238 return Ok(krate);
239 }
240 bail!("crate `{crate_name}` already exists: {krate_source}");
241 }
242
243 let krate = Krate::new(self, crate_name);
244
245 self.inputs
246 .lock()
247 .unwrap()
248 .directories
249 .insert(krate, new_source);
250
251 crates.push(krate);
252 root.set_crates(self)
253 .with_durability(Durability::HIGH)
254 .to(crates);
255
256 Ok(krate)
257 }
258
259 fn get_source_file(&self, url: &Url) -> Option<SourceFile> {
262 self.inputs.lock().unwrap().source_files.get(url).copied()
263 }
264
265 fn get_or_create_source_file(&self, url: &Url) -> SourceFile {
267 let mut inputs = self.inputs.lock().unwrap();
268
269 if let Some(&opt_source_file) = inputs.source_files.get(url) {
270 return opt_source_file;
271 }
272
273 let contents = match self.vfs.contents(url) {
274 Ok(data) => Ok(data),
275 Err(e) => Err(format!("error reading `{url}`: {e}")),
276 };
277
278 let result = SourceFile::new(self, url.clone(), contents);
279
280 inputs.source_files.insert(url.clone(), result);
281
282 result
283 }
284}
285
286#[salsa::db]
287pub trait Db: dada_check::Db {}
288
289#[salsa::db]
290impl salsa::Database for Compiler {
291 fn salsa_event(&self, event: &dyn Fn() -> Event) {
292 if dada_util::log::is_enabled() {
293 let event = event();
294 match event.kind {
295 EventKind::DidValidateMemoizedValue { database_key } => {
296 debug!("did_validate_memoized_value", database_key);
297 }
298 EventKind::WillBlockOn {
299 other_thread_id,
300 database_key,
301 } => {
302 debug!("will_block_on", other_thread_id, database_key);
303 }
304 EventKind::WillExecute { database_key } => {
305 debug!("will_execute", database_key);
306 }
307 EventKind::DidSetCancellationFlag => {
308 debug!("did_set_cancellation_flag");
309 }
310 EventKind::WillCheckCancellation
311 | EventKind::WillDiscardStaleOutput { .. }
312 | EventKind::DidDiscard { .. }
313 | EventKind::DidDiscardAccumulated { .. }
314 | EventKind::DidInternValue { .. }
315 | EventKind::DidReinternValue { .. } => {}
316 }
317 }
318 }
319}
320
321#[salsa::db]
322impl dada_ir_ast::Db for Compiler {
323 fn url_display(&self, url: &Url) -> String {
324 self.vfs.url_display(url)
325 }
326
327 fn root(&self) -> CompilationRoot {
328 Compiler::root(self)
329 }
330
331 fn source_file<'db>(&'db self, krate: Krate, modules: &[Identifier<'db>]) -> SourceFile {
332 let source = self.inputs.lock().unwrap().directories[&krate].clone();
333 match source {
334 KrateSource::Url(url) => {
335 let mut url_path = UrlPath::from(url);
336 assert!(!url_path.is_dada_file());
337 for module in modules {
338 url_path.push(module.text(self));
339 }
340 self.get_or_create_source_file(&url_path.make_dada_file().url())
341 }
342
343 KrateSource::Libdada => {
344 let mut path = String::new();
345 for module in modules {
346 path.push('/');
347 path.push_str(module.text(self));
348 }
349 path.push_str(".dada");
350
351 if let Some(libdada_source) =
352 self.inputs.lock().unwrap().libdada_source_files.get(&path)
353 {
354 return *libdada_source;
355 }
356
357 let contents = match LibDadaAsset::get(&path[1..]) {
358 Some(embedded) => match String::from_utf8(embedded.data.into_owned()) {
359 Ok(data) => Ok(data),
360 Err(e) => Err(format!("libdada file `{path}` is not utf-8: {e}")),
361 },
362 None => Err(format!("no libdada module at `{path}`")),
363 };
364
365 let libdada_url = Url::from_str(format!("libdada:///{path}").as_str()).unwrap();
366 let result = SourceFile::new(self, libdada_url, contents);
367 self.inputs
368 .lock()
369 .unwrap()
370 .libdada_source_files
371 .insert(path, result);
372
373 result
374 }
375 }
376 }
377
378 fn debug_tx(&self) -> Option<Sender<DebugEvent>> {
379 self.debug_tx.clone()
380 }
381}
382
383#[salsa::db]
384impl Db for Compiler {}
385
386#[derive(rust_embed::Embed)]
387#[folder = "../../libdada"]
388struct LibDadaAsset;
389
390#[derive(Default)]
391struct Inputs {
392 root: Option<CompilationRoot>,
393 source_files: Map<Url, SourceFile>,
394 libdada_source_files: Map<String, SourceFile>,
395 directories: Map<Krate, KrateSource>,
396}
397
398#[derive(FromImpls, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
399pub enum KrateSource {
400 Url(Url),
401
402 #[no_from_impl]
403 Libdada,
404}
405
406impl std::fmt::Display for KrateSource {
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 match self {
409 Self::Url(url) => write!(f, "rooted at `{url}`"),
410 Self::Libdada => write!(f, "built-in libdada"),
411 }
412 }
413}
414
415#[salsa::tracked]
416fn check_all(db: &dyn Db, source_file: SourceFile) {
417 use dada_check::Check;
418 source_file.check(db);
419}
420
421fn fn_asts(db: &dyn Db, source_file: SourceFile) -> String {
422 use std::fmt::Write;
423
424 let mut output = String::new();
425
426 let module = source_file.parse(db);
427
428 for item in module.items(db) {
429 match *item {
430 AstItem::SourceFile(_source_file) => (),
431 AstItem::Use(_use_item) => (),
432 AstItem::Aggregate(class_item) => {
433 writeln!(output, "## class `{}`", class_item.name(db)).unwrap();
434 for member in class_item.members(db) {
435 match member {
436 AstMember::Field(_field_decl) => (),
437 AstMember::Function(function) => {
438 writeln!(output, "### fn `{}`", function.name(db).id).unwrap();
439 writeln!(output).unwrap();
440 writeln!(output, "{}", fn_asts_fn(db, *function)).unwrap();
441 }
442 }
443 }
444 }
445 AstItem::Function(function) => {
446 writeln!(output, "## fn `{}`", function.name(db).id).unwrap();
447 writeln!(output).unwrap();
448 writeln!(output, "{}", fn_asts_fn(db, function)).unwrap();
449 }
450 AstItem::MainFunction(ast_main_function) => {
451 writeln!(output, "## main fn").unwrap();
452 writeln!(output).unwrap();
453 for statement in ast_main_function.statements(db) {
454 writeln!(output, "{statement:?}").unwrap();
455 }
456 }
457 }
458 }
459
460 return output;
461
462 fn fn_asts_fn<'db>(db: &'db dyn Db, function: AstFunction<'db>) -> String {
463 if let Some(block) = function.body_block(db) {
464 format!("{block:#?}")
465 } else {
466 "None".to_string()
467 }
468 }
469}