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