plotnik_core/
lib.rs

1#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
2
3//! Core data structures for Plotnik node type information.
4//!
5//! Two layers:
6//! - **Deserialization layer**: 1:1 mapping to `node-types.json`
7//! - **Analysis layer**: ID-indexed structures for efficient lookups
8//!
9//! Two implementations:
10//! - **Dynamic** (`DynamicNodeTypes`): HashMap-based, for runtime construction
11//! - **Static** (`StaticNodeTypes`): Array-based, zero runtime init
12
13use std::collections::HashMap;
14use std::num::NonZeroU16;
15
16pub mod grammar;
17mod interner;
18mod invariants;
19pub mod utils;
20
21#[cfg(test)]
22mod interner_tests;
23#[cfg(test)]
24mod lib_tests;
25#[cfg(test)]
26mod utils_tests;
27
28pub use interner::{Interner, Symbol};
29
30/// Raw node definition from `node-types.json`.
31#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
32pub struct RawNode {
33    #[serde(rename = "type")]
34    pub type_name: String,
35    pub named: bool,
36    #[serde(default)]
37    pub root: bool,
38    #[serde(default)]
39    pub extra: bool,
40    #[serde(default)]
41    pub fields: HashMap<String, RawCardinality>,
42    pub children: Option<RawCardinality>,
43    pub subtypes: Option<Vec<RawTypeRef>>,
44}
45
46/// Cardinality constraints for a field or children slot.
47#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
48pub struct RawCardinality {
49    pub multiple: bool,
50    pub required: bool,
51    pub types: Vec<RawTypeRef>,
52}
53
54/// Reference to a node type.
55#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
56pub struct RawTypeRef {
57    #[serde(rename = "type")]
58    pub type_name: String,
59    pub named: bool,
60}
61
62/// Parse `node-types.json` content into raw nodes.
63pub fn parse_node_types(json: &str) -> Result<Vec<RawNode>, serde_json::Error> {
64    serde_json::from_str(json)
65}
66
67/// Node type ID (tree-sitter uses u16, but 0 is internal-only).
68pub type NodeTypeId = NonZeroU16;
69
70/// Field ID (tree-sitter uses NonZeroU16).
71pub type NodeFieldId = NonZeroU16;
72
73/// Cardinality info for a field or children slot.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct Cardinality {
76    pub multiple: bool,
77    pub required: bool,
78}
79
80/// Trait for node type constraint lookups.
81///
82/// Provides only what tree-sitter's `Language` API doesn't:
83/// - Root node identification
84/// - Extra nodes (comments, whitespace)
85/// - Field constraints per node type
86/// - Children constraints per node type
87///
88/// For name↔ID resolution and supertype info, use `Language` directly.
89pub trait NodeTypes {
90    fn root(&self) -> Option<NodeTypeId>;
91    fn is_extra(&self, node_type_id: NodeTypeId) -> bool;
92
93    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool;
94    fn field_cardinality(
95        &self,
96        node_type_id: NodeTypeId,
97        node_field_id: NodeFieldId,
98    ) -> Option<Cardinality>;
99    fn valid_field_types(
100        &self,
101        node_type_id: NodeTypeId,
102        node_field_id: NodeFieldId,
103    ) -> &[NodeTypeId];
104    fn is_valid_field_type(
105        &self,
106        node_type_id: NodeTypeId,
107        node_field_id: NodeFieldId,
108        child: NodeTypeId,
109    ) -> bool;
110
111    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality>;
112    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId];
113    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool;
114}
115
116impl<T: NodeTypes + ?Sized> NodeTypes for &T {
117    fn root(&self) -> Option<NodeTypeId> {
118        (*self).root()
119    }
120    fn is_extra(&self, node_type_id: NodeTypeId) -> bool {
121        (*self).is_extra(node_type_id)
122    }
123    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool {
124        (*self).has_field(node_type_id, node_field_id)
125    }
126    fn field_cardinality(
127        &self,
128        node_type_id: NodeTypeId,
129        node_field_id: NodeFieldId,
130    ) -> Option<Cardinality> {
131        (*self).field_cardinality(node_type_id, node_field_id)
132    }
133    fn valid_field_types(
134        &self,
135        node_type_id: NodeTypeId,
136        node_field_id: NodeFieldId,
137    ) -> &[NodeTypeId] {
138        (*self).valid_field_types(node_type_id, node_field_id)
139    }
140    fn is_valid_field_type(
141        &self,
142        node_type_id: NodeTypeId,
143        node_field_id: NodeFieldId,
144        child: NodeTypeId,
145    ) -> bool {
146        (*self).is_valid_field_type(node_type_id, node_field_id, child)
147    }
148    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality> {
149        (*self).children_cardinality(node_type_id)
150    }
151    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId] {
152        (*self).valid_child_types(node_type_id)
153    }
154    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool {
155        (*self).is_valid_child_type(node_type_id, child)
156    }
157}
158
159/// Field info for static storage.
160#[derive(Debug, Clone, Copy)]
161pub struct StaticFieldInfo {
162    pub cardinality: Cardinality,
163    pub valid_types: &'static [NodeTypeId],
164}
165
166/// Children info for static storage.
167#[derive(Debug, Clone, Copy)]
168pub struct StaticChildrenInfo {
169    pub cardinality: Cardinality,
170    pub valid_types: &'static [NodeTypeId],
171}
172
173/// Complete node type information for static storage.
174///
175/// Note: supertype/subtype info is NOT stored here - use `Language::node_kind_is_supertype()`
176/// and `Language::subtypes_for_supertype()` from tree-sitter instead.
177#[derive(Debug, Clone, Copy)]
178pub struct StaticNodeTypeInfo {
179    pub name: &'static str,
180    pub named: bool,
181    /// Sorted slice of (field_id, field_info) pairs for binary search.
182    pub fields: &'static [(NodeFieldId, StaticFieldInfo)],
183    pub children: Option<StaticChildrenInfo>,
184}
185
186/// Compiled node type database with static storage.
187///
188/// All data is statically allocated - no runtime initialization needed.
189/// Node lookups use binary search on sorted arrays.
190#[derive(Debug, Clone, Copy)]
191pub struct StaticNodeTypes {
192    /// Sorted slice of (node_id, node_info) pairs.
193    nodes: &'static [(NodeTypeId, StaticNodeTypeInfo)],
194    /// Slice of extra node type IDs.
195    extras: &'static [NodeTypeId],
196    root: Option<NodeTypeId>,
197}
198
199impl StaticNodeTypes {
200    pub const fn new(
201        nodes: &'static [(NodeTypeId, StaticNodeTypeInfo)],
202        extras: &'static [NodeTypeId],
203        root: Option<NodeTypeId>,
204    ) -> Self {
205        Self {
206            nodes,
207            extras,
208            root,
209        }
210    }
211
212    /// Get info for a node type by ID (binary search).
213    pub fn get(&self, node_type_id: NodeTypeId) -> Option<&'static StaticNodeTypeInfo> {
214        self.nodes
215            .binary_search_by_key(&node_type_id, |(node_id, _)| *node_id)
216            .ok()
217            .map(|idx| &self.nodes[idx].1)
218    }
219
220    /// Check if node type exists.
221    pub fn contains(&self, node_type_id: NodeTypeId) -> bool {
222        self.nodes
223            .binary_search_by_key(&node_type_id, |(node_id, _)| *node_id)
224            .is_ok()
225    }
226
227    /// Get field info for a node type (binary search for node, then field).
228    pub fn field(
229        &self,
230        node_type_id: NodeTypeId,
231        field_id: NodeFieldId,
232    ) -> Option<&'static StaticFieldInfo> {
233        let info = self.ensure_node(node_type_id);
234        info.fields
235            .binary_search_by_key(&field_id, |(fid, _)| *fid)
236            .ok()
237            .map(|idx| &info.fields[idx].1)
238    }
239
240    /// Get children info for a node type.
241    pub fn children(&self, node_type_id: NodeTypeId) -> Option<StaticChildrenInfo> {
242        self.ensure_node(node_type_id).children
243    }
244
245    /// Get all extra node type IDs.
246    pub fn extras(&self) -> &'static [NodeTypeId] {
247        self.extras
248    }
249
250    pub fn len(&self) -> usize {
251        self.nodes.len()
252    }
253
254    pub fn is_empty(&self) -> bool {
255        self.nodes.is_empty()
256    }
257
258    pub fn iter(&self) -> impl Iterator<Item = (NodeTypeId, &'static StaticNodeTypeInfo)> {
259        self.nodes.iter().map(|(id, info)| (*id, info))
260    }
261}
262
263impl NodeTypes for StaticNodeTypes {
264    fn root(&self) -> Option<NodeTypeId> {
265        self.root
266    }
267
268    fn is_extra(&self, node_type_id: NodeTypeId) -> bool {
269        self.extras.contains(&node_type_id)
270    }
271
272    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool {
273        self.get(node_type_id).is_some_and(|info| {
274            info.fields
275                .binary_search_by_key(&node_field_id, |(fid, _)| *fid)
276                .is_ok()
277        })
278    }
279
280    fn field_cardinality(
281        &self,
282        node_type_id: NodeTypeId,
283        node_field_id: NodeFieldId,
284    ) -> Option<Cardinality> {
285        self.field(node_type_id, node_field_id)
286            .map(|f| f.cardinality)
287    }
288
289    fn valid_field_types(
290        &self,
291        node_type_id: NodeTypeId,
292        node_field_id: NodeFieldId,
293    ) -> &[NodeTypeId] {
294        self.field(node_type_id, node_field_id)
295            .map(|f| f.valid_types)
296            .unwrap_or(&[])
297    }
298
299    fn is_valid_field_type(
300        &self,
301        node_type_id: NodeTypeId,
302        node_field_id: NodeFieldId,
303        child: NodeTypeId,
304    ) -> bool {
305        self.valid_field_types(node_type_id, node_field_id)
306            .contains(&child)
307    }
308
309    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality> {
310        self.children(node_type_id).map(|c| c.cardinality)
311    }
312
313    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId] {
314        self.children(node_type_id)
315            .map(|c| c.valid_types)
316            .unwrap_or(&[])
317    }
318
319    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool {
320        self.valid_child_types(node_type_id).contains(&child)
321    }
322}
323
324/// Information about a single field on a node type.
325#[derive(Debug, Clone)]
326pub struct FieldInfo {
327    pub cardinality: Cardinality,
328    pub valid_types: Vec<NodeTypeId>,
329}
330
331/// Information about a node type's children (non-field children).
332#[derive(Debug, Clone)]
333pub struct ChildrenInfo {
334    pub cardinality: Cardinality,
335    pub valid_types: Vec<NodeTypeId>,
336}
337
338/// Complete node type information.
339///
340/// Note: supertype/subtype info is NOT stored here - use tree-sitter's Language API.
341#[derive(Debug, Clone)]
342pub struct NodeTypeInfo {
343    pub name: String,
344    pub named: bool,
345    pub fields: HashMap<NodeFieldId, FieldInfo>,
346    pub children: Option<ChildrenInfo>,
347}
348
349/// Compiled node type database for a language (dynamic/heap-allocated).
350///
351/// Use this for runtime construction or as reference implementation.
352/// For zero-init static data, use `StaticNodeTypes`.
353#[derive(Debug, Clone)]
354pub struct DynamicNodeTypes {
355    nodes: HashMap<NodeTypeId, NodeTypeInfo>,
356    extras: Vec<NodeTypeId>,
357    root: Option<NodeTypeId>,
358}
359
360impl DynamicNodeTypes {
361    pub fn from_raw(
362        nodes: HashMap<NodeTypeId, NodeTypeInfo>,
363        extras: Vec<NodeTypeId>,
364        root: Option<NodeTypeId>,
365    ) -> Self {
366        Self {
367            nodes,
368            extras,
369            root,
370        }
371    }
372
373    /// Build from raw nodes and ID resolution functions.
374    pub fn build<F, G>(raw_nodes: &[RawNode], node_id_for_name: F, field_id_for_name: G) -> Self
375    where
376        F: Fn(&str, bool) -> Option<NodeTypeId>,
377        G: Fn(&str) -> Option<NodeFieldId>,
378    {
379        let mut nodes = HashMap::new();
380        let mut extras = Vec::new();
381        let mut root = None;
382
383        for raw in raw_nodes {
384            let Some(node_id) = node_id_for_name(&raw.type_name, raw.named) else {
385                continue;
386            };
387
388            if raw.root {
389                root = Some(node_id);
390            }
391
392            if raw.extra {
393                extras.push(node_id);
394            }
395
396            let mut fields = HashMap::new();
397            for (field_name, raw_card) in &raw.fields {
398                let Some(field_id) = field_id_for_name(field_name) else {
399                    continue;
400                };
401
402                let valid_types = raw_card
403                    .types
404                    .iter()
405                    .filter_map(|t| node_id_for_name(&t.type_name, t.named))
406                    .collect();
407
408                fields.insert(
409                    field_id,
410                    FieldInfo {
411                        cardinality: Cardinality {
412                            multiple: raw_card.multiple,
413                            required: raw_card.required,
414                        },
415                        valid_types,
416                    },
417                );
418            }
419
420            let children = raw.children.as_ref().map(|raw_card| {
421                let valid_types = raw_card
422                    .types
423                    .iter()
424                    .filter_map(|t| node_id_for_name(&t.type_name, t.named))
425                    .collect();
426
427                ChildrenInfo {
428                    cardinality: Cardinality {
429                        multiple: raw_card.multiple,
430                        required: raw_card.required,
431                    },
432                    valid_types,
433                }
434            });
435
436            nodes.insert(
437                node_id,
438                NodeTypeInfo {
439                    name: raw.type_name.clone(),
440                    named: raw.named,
441                    fields,
442                    children,
443                },
444            );
445        }
446
447        Self {
448            nodes,
449            extras,
450            root,
451        }
452    }
453
454    pub fn get(&self, node_type_id: NodeTypeId) -> Option<&NodeTypeInfo> {
455        self.nodes.get(&node_type_id)
456    }
457
458    pub fn contains(&self, node_type_id: NodeTypeId) -> bool {
459        self.nodes.contains_key(&node_type_id)
460    }
461
462    pub fn field(&self, node_type_id: NodeTypeId, field_id: NodeFieldId) -> Option<&FieldInfo> {
463        self.ensure_node(node_type_id).fields.get(&field_id)
464    }
465
466    pub fn children(&self, node_type_id: NodeTypeId) -> Option<&ChildrenInfo> {
467        self.ensure_node(node_type_id).children.as_ref()
468    }
469
470    pub fn extras(&self) -> &[NodeTypeId] {
471        &self.extras
472    }
473
474    pub fn len(&self) -> usize {
475        self.nodes.len()
476    }
477
478    pub fn is_empty(&self) -> bool {
479        self.nodes.is_empty()
480    }
481
482    pub fn iter(&self) -> impl Iterator<Item = (NodeTypeId, &NodeTypeInfo)> {
483        self.nodes.iter().map(|(&id, info)| (id, info))
484    }
485
486    /// Get sorted vec of all node IDs (for conversion to static).
487    pub fn sorted_node_ids(&self) -> Vec<NodeTypeId> {
488        let mut ids: Vec<_> = self.nodes.keys().copied().collect();
489        ids.sort_unstable();
490        ids
491    }
492
493    /// Get sorted vec of extra IDs (for conversion to static).
494    pub fn sorted_extras(&self) -> Vec<NodeTypeId> {
495        let mut ids = self.extras.clone();
496        ids.sort_unstable();
497        ids
498    }
499}
500
501impl NodeTypes for DynamicNodeTypes {
502    fn root(&self) -> Option<NodeTypeId> {
503        self.root
504    }
505
506    fn is_extra(&self, node_type_id: NodeTypeId) -> bool {
507        self.extras.contains(&node_type_id)
508    }
509
510    fn has_field(&self, node_type_id: NodeTypeId, node_field_id: NodeFieldId) -> bool {
511        self.nodes
512            .get(&node_type_id)
513            .is_some_and(|n| n.fields.contains_key(&node_field_id))
514    }
515
516    fn field_cardinality(
517        &self,
518        node_type_id: NodeTypeId,
519        node_field_id: NodeFieldId,
520    ) -> Option<Cardinality> {
521        self.field(node_type_id, node_field_id)
522            .map(|f| f.cardinality)
523    }
524
525    fn valid_field_types(
526        &self,
527        node_type_id: NodeTypeId,
528        node_field_id: NodeFieldId,
529    ) -> &[NodeTypeId] {
530        self.field(node_type_id, node_field_id)
531            .map(|f| f.valid_types.as_slice())
532            .unwrap_or(&[])
533    }
534
535    fn is_valid_field_type(
536        &self,
537        node_type_id: NodeTypeId,
538        node_field_id: NodeFieldId,
539        child: NodeTypeId,
540    ) -> bool {
541        self.valid_field_types(node_type_id, node_field_id)
542            .contains(&child)
543    }
544
545    fn children_cardinality(&self, node_type_id: NodeTypeId) -> Option<Cardinality> {
546        self.children(node_type_id).map(|c| c.cardinality)
547    }
548
549    fn valid_child_types(&self, node_type_id: NodeTypeId) -> &[NodeTypeId] {
550        self.children(node_type_id)
551            .map(|c| c.valid_types.as_slice())
552            .unwrap_or(&[])
553    }
554
555    fn is_valid_child_type(&self, node_type_id: NodeTypeId, child: NodeTypeId) -> bool {
556        self.valid_child_types(node_type_id).contains(&child)
557    }
558}