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