plotnik_compiler/query/
source_map.rs

1//! Source storage for query compilation.
2//!
3//! Stores sources as owned strings, providing a simple interface for
4//! multi-source compilation sessions.
5
6/// Lightweight handle to a source in a compilation session.
7#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
8pub struct SourceId(pub(crate) u32);
9
10/// Describes the origin of a source.
11#[derive(Clone, PartialEq, Eq, Debug)]
12pub enum SourceKind {
13    /// A one-liner query passed directly (e.g., CLI `-q` argument).
14    OneLiner,
15    /// Input read from stdin.
16    Stdin,
17    /// A file with its path.
18    File(String),
19}
20
21impl SourceKind {
22    /// Returns the display name for diagnostics.
23    pub fn display_name(&self) -> &str {
24        match self {
25            SourceKind::OneLiner => "<query>",
26            SourceKind::Stdin => "<stdin>",
27            SourceKind::File(path) => path,
28        }
29    }
30}
31
32/// A borrowed view of a source: id, kind, and content.
33#[derive(Clone, Debug)]
34pub struct Source<'q> {
35    pub id: SourceId,
36    pub kind: &'q SourceKind,
37    pub content: &'q str,
38}
39
40impl<'q> Source<'q> {
41    /// Returns the content string.
42    pub fn as_str(&self) -> &'q str {
43        self.content
44    }
45}
46
47/// Metadata for a source.
48#[derive(Clone, Debug)]
49struct SourceEntry {
50    kind: SourceKind,
51    content: String,
52}
53
54/// Registry of all sources.
55#[derive(Clone, Debug, Default)]
56pub struct SourceMap {
57    entries: Vec<SourceEntry>,
58}
59
60impl SourceMap {
61    pub fn new() -> Self {
62        Self::default()
63    }
64
65    /// Add a one-liner source (CLI `-q` argument, REPL, tests).
66    pub fn add_one_liner(&mut self, content: &str) -> SourceId {
67        self.push_entry(SourceKind::OneLiner, content)
68    }
69
70    /// Add a source read from stdin.
71    pub fn add_stdin(&mut self, content: &str) -> SourceId {
72        self.push_entry(SourceKind::Stdin, content)
73    }
74
75    /// Add a file source with its path.
76    pub fn add_file(&mut self, path: &str, content: &str) -> SourceId {
77        self.push_entry(SourceKind::File(path.to_owned()), content)
78    }
79
80    /// Create a SourceMap with a single one-liner source.
81    /// Convenience for single-source use cases (CLI, REPL, tests).
82    pub fn one_liner(content: &str) -> Self {
83        let mut map = Self::new();
84        map.add_one_liner(content);
85        map
86    }
87
88    /// Get the content of a source by ID.
89    pub fn content(&self, id: SourceId) -> &str {
90        self.entries
91            .get(id.0 as usize)
92            .map(|e| e.content.as_str())
93            .expect("invalid SourceId")
94    }
95
96    /// Get the kind of a source by ID.
97    pub fn kind(&self, id: SourceId) -> &SourceKind {
98        self.entries
99            .get(id.0 as usize)
100            .map(|e| &e.kind)
101            .expect("invalid SourceId")
102    }
103
104    /// Get the file path if this source is a file, None otherwise.
105    pub fn path(&self, id: SourceId) -> Option<&str> {
106        let entry = self.entries.get(id.0 as usize).expect("invalid SourceId");
107        match &entry.kind {
108            SourceKind::File(path) => Some(path),
109            _ => None,
110        }
111    }
112
113    /// Number of sources in the map.
114    pub fn len(&self) -> usize {
115        self.entries.len()
116    }
117
118    /// Check if the map is empty.
119    pub fn is_empty(&self) -> bool {
120        self.entries.is_empty()
121    }
122
123    /// Get a source by ID, returning a `Source` view.
124    pub fn get(&self, id: SourceId) -> Source<'_> {
125        let entry = self.entries.get(id.0 as usize).expect("invalid SourceId");
126        Source {
127            id,
128            kind: &entry.kind,
129            content: &entry.content,
130        }
131    }
132
133    /// Iterate over all sources as `Source` views.
134    pub fn iter(&self) -> impl Iterator<Item = Source<'_>> {
135        self.entries.iter().enumerate().map(|(idx, entry)| Source {
136            id: SourceId(idx as u32),
137            kind: &entry.kind,
138            content: &entry.content,
139        })
140    }
141
142    fn push_entry(&mut self, kind: SourceKind, content: &str) -> SourceId {
143        let id = SourceId(self.entries.len() as u32);
144        self.entries.push(SourceEntry {
145            kind,
146            content: content.to_owned(),
147        });
148        id
149    }
150}