Skip to main content

dada_ir_ast/
inputs.rs

1use std::ops::Range;
2
3use dada_util::SalsaSerialize;
4use serde::Serialize;
5use url::Url;
6
7use crate::{
8    ast::Identifier,
9    span::{AbsoluteOffset, AbsoluteSpan, Anchor, Offset, Span, Spanned, ZeroColumn, ZeroLine},
10};
11
12#[derive(SalsaSerialize)]
13#[salsa::input]
14pub struct CompilationRoot {
15    #[return_ref]
16    pub crates: Vec<Krate>,
17}
18
19impl CompilationRoot {
20    pub fn crate_source<'db>(
21        self,
22        db: &'db dyn crate::Db,
23        crate_name: Identifier<'db>,
24    ) -> Option<Krate> {
25        #[salsa::tracked]
26        fn inner<'db>(
27            db: &'db dyn crate::Db,
28            root: CompilationRoot,
29            crate_name: Identifier<'db>,
30        ) -> Option<Krate> {
31            let crate_name = crate_name.text(db);
32            root.crates(db)
33                .iter()
34                .find(|c| c.name(db) == crate_name)
35                .copied()
36        }
37
38        inner(db, self, crate_name)
39    }
40
41    /// Returns the [`Krate`][] for the `libdada` crate.
42    /// The creator of the [`CompilationRoot`][] is responsible for ensuring that this crate is present.
43    pub fn libdada_crate(self, db: &dyn crate::Db) -> Krate {
44        #[salsa::tracked]
45        fn inner<'db>(db: &'db dyn crate::Db, root: CompilationRoot) -> Krate {
46            root.crate_source(db, Identifier::dada(db))
47                .expect("libdada crate not found")
48        }
49
50        inner(db, self)
51    }
52}
53
54#[derive(SalsaSerialize)]
55#[salsa::input]
56pub struct Krate {
57    #[return_ref]
58    pub name: String,
59}
60
61#[salsa::input(debug)]
62pub struct SourceFile {
63    #[return_ref]
64    pub url: Url,
65
66    /// Contents of the source file or an error message if it was not possible to read it.
67    #[return_ref]
68    pub contents: Result<String, String>,
69}
70
71impl serde::Serialize for SourceFile {
72    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73    where
74        S: serde::Serializer,
75    {
76        salsa::with_attached_database(|db| {
77            #[derive(Serialize)]
78            struct SourceFileExport<'a> {
79                url: &'a Url,
80            }
81
82            serde::Serialize::serialize(&SourceFileExport { url: self.url(db) }, serializer)
83        })
84        .unwrap_or_else(|| panic!("cannot serialize without an attached database"))
85    }
86}
87
88impl<'db> Spanned<'db> for SourceFile {
89    fn span(&self, db: &'db dyn crate::Db) -> crate::span::Span<'db> {
90        Span {
91            anchor: Anchor::SourceFile(*self),
92            start: Offset::ZERO,
93            end: Offset::from(self.contents_if_ok(db).len()),
94        }
95    }
96}
97
98#[salsa::tracked]
99impl SourceFile {
100    /// Returns the contents of this file or an empty string if it couldn't be read.
101    pub fn contents_if_ok(self, db: &dyn crate::Db) -> &str {
102        match self.contents(db) {
103            Ok(s) => s,
104            Err(_) => "",
105        }
106    }
107
108    /// Returns an absolute span representing the entire source file.
109    pub fn absolute_span(self, db: &dyn crate::Db) -> AbsoluteSpan {
110        AbsoluteSpan {
111            source_file: self,
112            start: AbsoluteOffset::ZERO,
113            end: AbsoluteOffset::from(self.contents_if_ok(db).len()),
114        }
115    }
116
117    pub fn module_name(self, db: &dyn crate::Db) -> Identifier<'_> {
118        let url = self.url(db);
119        let module_name = url
120            .path_segments()
121            .and_then(|mut segments| segments.next_back())
122            .unwrap_or("<input>");
123        Identifier::new(db, module_name)
124    }
125
126    pub fn url_display(self, db: &dyn crate::Db) -> String {
127        db.url_display(self.url(db))
128    }
129
130    /// A vector containing the start indices of each (0-based) line
131    /// plus one final entry with the total document length
132    /// (effectively an imaginary N+1 line that starts, and ends, at the end).
133    #[salsa::tracked(return_ref)]
134    pub fn line_starts(self, db: &dyn crate::Db) -> Vec<AbsoluteOffset> {
135        std::iter::once(0)
136            .chain(
137                self.contents_if_ok(db)
138                    .char_indices()
139                    .filter(|&(_, ch)| ch == '\n')
140                    .map(|(index, _)| index + 1),
141            )
142            .chain(std::iter::once(self.contents_if_ok(db).len()))
143            .map(AbsoluteOffset::from)
144            .collect()
145    }
146
147    pub fn line_range(self, db: &dyn crate::Db, line: ZeroLine) -> Range<AbsoluteOffset> {
148        let line_starts = self.line_starts(db);
149        line_starts[line.as_usize()]..line_starts[line.as_usize() + 1]
150    }
151
152    pub fn line_col(self, db: &dyn crate::Db, offset: AbsoluteOffset) -> (ZeroLine, ZeroColumn) {
153        let line_starts = self.line_starts(db);
154        match line_starts.iter().position(|&s| s > offset) {
155            Some(next_line) => {
156                assert!(next_line > 0);
157                let line_index = next_line - 1;
158                let line_start = line_starts[line_index];
159                (
160                    ZeroLine::from(line_index),
161                    ZeroColumn::from(offset - line_start),
162                )
163            }
164            None => {
165                // This must be the end of the document. We will return the last column on the last line.
166                let last_line = self.line_starts(db).len() - 1;
167                let last_line_start = line_starts[last_line];
168                (
169                    ZeroLine::from(last_line),
170                    ZeroColumn::from(offset - last_line_start),
171                )
172            }
173        }
174    }
175}