plotnik_compiler/query/
stages.rs

1use std::ops::{Deref, DerefMut};
2
3use indexmap::IndexMap;
4
5use plotnik_core::{Interner, NodeFieldId, NodeTypeId, Symbol};
6use plotnik_langs::Lang;
7
8use super::{SourceId, SourceMap};
9use crate::Diagnostics;
10use crate::analyze::link;
11use crate::analyze::symbol_table::{SymbolTable, resolve_names};
12use crate::analyze::type_check::{self, Arity, TypeContext};
13use crate::analyze::validation::{
14    validate_alt_kinds, validate_anchors, validate_empty_constructs, validate_predicates,
15};
16use crate::analyze::{dependencies, validate_recursion};
17use crate::parser::{Parser, Root, SyntaxNode, lex};
18
19const DEFAULT_QUERY_PARSE_FUEL: u32 = 1_000_000;
20const DEFAULT_QUERY_PARSE_MAX_DEPTH: u32 = 4096;
21
22pub type AstMap = IndexMap<SourceId, Root>;
23
24pub struct QueryConfig {
25    pub query_parse_fuel: u32,
26    pub query_parse_max_depth: u32,
27}
28
29pub struct QueryBuilder {
30    source_map: SourceMap,
31    config: QueryConfig,
32}
33
34impl QueryBuilder {
35    pub fn new(source_map: SourceMap) -> Self {
36        let config = QueryConfig {
37            query_parse_fuel: DEFAULT_QUERY_PARSE_FUEL,
38            query_parse_max_depth: DEFAULT_QUERY_PARSE_MAX_DEPTH,
39        };
40
41        Self { source_map, config }
42    }
43
44    pub fn one_liner(src: &str) -> Self {
45        let source_map = SourceMap::one_liner(src);
46        Self::new(source_map)
47    }
48
49    pub fn with_query_parse_fuel(mut self, fuel: u32) -> Self {
50        self.config.query_parse_fuel = fuel;
51        self
52    }
53
54    pub fn with_query_parse_recursion_limit(mut self, limit: u32) -> Self {
55        self.config.query_parse_max_depth = limit;
56        self
57    }
58
59    pub fn parse(self) -> crate::Result<QueryParsed> {
60        let mut ast = IndexMap::new();
61        let mut diag = Diagnostics::new();
62        let mut total_fuel_consumed = 0u32;
63
64        for source in self.source_map.iter() {
65            let tokens = lex(source.content);
66            let parser = Parser::new(
67                source.content,
68                source.id,
69                tokens,
70                &mut diag,
71                self.config.query_parse_fuel,
72                self.config.query_parse_max_depth,
73            );
74
75            let res = parser.parse()?;
76
77            validate_alt_kinds(source.id, &res.ast, &mut diag);
78            validate_anchors(source.id, &res.ast, &mut diag);
79            validate_empty_constructs(source.id, &res.ast, &mut diag);
80            validate_predicates(source.id, source.content, &res.ast, &mut diag);
81            total_fuel_consumed = total_fuel_consumed.saturating_add(res.fuel_consumed);
82            ast.insert(source.id, res.ast);
83        }
84
85        Ok(QueryParsed {
86            source_map: self.source_map,
87            diag,
88            ast_map: ast,
89            fuel_consumed: total_fuel_consumed,
90        })
91    }
92}
93
94#[derive(Debug)]
95pub struct QueryParsed {
96    source_map: SourceMap,
97    ast_map: AstMap,
98    diag: Diagnostics,
99    fuel_consumed: u32,
100}
101
102impl QueryParsed {
103    pub fn query_parser_fuel_consumed(&self) -> u32 {
104        self.fuel_consumed
105    }
106}
107
108impl QueryParsed {
109    pub fn analyze(mut self) -> QueryAnalyzed {
110        // Create shared interner for all phases
111        let mut interner = Interner::new();
112
113        // Use reference-based structures for processing
114        let symbol_table = resolve_names(&self.source_map, &self.ast_map, &mut self.diag);
115
116        let dependency_analysis = dependencies::analyze_dependencies(&symbol_table, &mut interner);
117        validate_recursion(
118            &dependency_analysis,
119            &self.ast_map,
120            &symbol_table,
121            &mut self.diag,
122        );
123
124        // Unified type checking pass
125        let type_context = type_check::infer_types(
126            &mut interner,
127            &self.ast_map,
128            &symbol_table,
129            &dependency_analysis,
130            &mut self.diag,
131        );
132
133        QueryAnalyzed {
134            query_parsed: self,
135            interner,
136            symbol_table,
137            type_context,
138        }
139    }
140
141    pub fn source_map(&self) -> &SourceMap {
142        &self.source_map
143    }
144
145    pub fn diagnostics(&self) -> Diagnostics {
146        self.diag.clone()
147    }
148
149    pub fn asts(&self) -> &AstMap {
150        &self.ast_map
151    }
152}
153
154pub type Query = QueryAnalyzed;
155
156/// A unified view of the core analysis context.
157///
158/// Bundles references to the three main analysis artifacts that downstream
159/// modules (compile, emit) commonly need together.
160#[derive(Clone, Copy)]
161pub struct QueryContext<'q> {
162    pub interner: &'q Interner,
163    pub type_ctx: &'q TypeContext,
164    pub symbol_table: &'q SymbolTable,
165}
166
167pub struct QueryAnalyzed {
168    query_parsed: QueryParsed,
169    interner: Interner,
170    pub symbol_table: SymbolTable,
171    type_context: TypeContext,
172}
173
174impl QueryAnalyzed {
175    pub fn is_valid(&self) -> bool {
176        !self.diag.has_errors()
177    }
178
179    /// Returns a unified context view for downstream modules.
180    pub fn context(&self) -> QueryContext<'_> {
181        QueryContext {
182            interner: &self.interner,
183            type_ctx: &self.type_context,
184            symbol_table: &self.symbol_table,
185        }
186    }
187
188    pub fn get_arity(&self, node: &SyntaxNode) -> Option<Arity> {
189        use crate::parser::ast;
190
191        // Try casting to Expr first as it's the most common query
192        if let Some(expr) = ast::Expr::cast(node.clone()) {
193            return self.type_context.get_arity(&expr);
194        }
195
196        // Root: arity based on definition count
197        if let Some(root) = ast::Root::cast(node.clone()) {
198            return Some(if root.defs().nth(1).is_some() {
199                Arity::Many
200            } else {
201                Arity::One
202            });
203        }
204
205        // Def: delegate to body's arity
206        if let Some(def) = ast::Def::cast(node.clone()) {
207            return def.body().and_then(|b| self.type_context.get_arity(&b));
208        }
209
210        // Branch: delegate to body's arity
211        if let Some(branch) = ast::Branch::cast(node.clone()) {
212            return branch.body().and_then(|b| self.type_context.get_arity(&b));
213        }
214
215        None
216    }
217
218    pub fn type_context(&self) -> &TypeContext {
219        &self.type_context
220    }
221
222    pub fn interner(&self) -> &Interner {
223        &self.interner
224    }
225
226    pub fn link(mut self, lang: &Lang) -> LinkedQuery {
227        let mut output = link::LinkOutput::default();
228
229        link::link(
230            &mut self.interner,
231            lang,
232            &self.query_parsed.source_map,
233            &self.query_parsed.ast_map,
234            &self.symbol_table,
235            &mut output,
236            &mut self.query_parsed.diag,
237        );
238
239        LinkedQuery {
240            inner: self,
241            linking: output,
242        }
243    }
244}
245
246impl Deref for QueryAnalyzed {
247    type Target = QueryParsed;
248
249    fn deref(&self) -> &Self::Target {
250        &self.query_parsed
251    }
252}
253
254impl DerefMut for QueryAnalyzed {
255    fn deref_mut(&mut self) -> &mut Self::Target {
256        &mut self.query_parsed
257    }
258}
259
260impl TryFrom<&str> for QueryAnalyzed {
261    type Error = crate::Error;
262
263    fn try_from(src: &str) -> crate::Result<Self> {
264        Ok(QueryBuilder::new(SourceMap::one_liner(src))
265            .parse()?
266            .analyze())
267    }
268}
269
270pub struct LinkedQuery {
271    inner: QueryAnalyzed,
272    linking: link::LinkOutput,
273}
274
275impl LinkedQuery {
276    pub fn interner(&self) -> &Interner {
277        &self.inner.interner
278    }
279
280    pub fn node_type_ids(&self) -> &IndexMap<Symbol, NodeTypeId> {
281        &self.linking.node_type_ids
282    }
283
284    pub fn node_field_ids(&self) -> &IndexMap<Symbol, NodeFieldId> {
285        &self.linking.node_field_ids
286    }
287
288    /// Emit bytecode. Returns `Err(EmitError::InvalidQuery)` if the query has errors.
289    pub fn emit(&self) -> Result<Vec<u8>, crate::emit::EmitError> {
290        if !self.is_valid() {
291            return Err(crate::emit::EmitError::InvalidQuery);
292        }
293        crate::emit::emit(self)
294    }
295}
296
297impl Deref for LinkedQuery {
298    type Target = QueryAnalyzed;
299
300    fn deref(&self) -> &Self::Target {
301        &self.inner
302    }
303}
304
305impl DerefMut for LinkedQuery {
306    fn deref_mut(&mut self) -> &mut Self::Target {
307        &mut self.inner
308    }
309}