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