Skip to main content

m2_syn/
treesitter.rs

1//! Tree-sitter parser adapter.
2//!
3//! Tree-sitter nodes remain backend-local. [`TreeSitterNode`] projects them
4//! through the temporary untyped [`CstNode`] reconstruction seam; no
5//! Tree-sitter identity or child iterator is stored in the typed graph.
6
7use std::borrow::Cow;
8use std::error::Error;
9use std::fmt::{Display, Formatter};
10use std::vec::IntoIter;
11
12use crate::{
13    CSTNodeClassLabel, CellStream, ExternalCstChild, ExternalCstNode, LexError, Parser,
14    ReconstructError, SourceFile, SourceId, Span, Spanned, TextPoint, TextRange, TokenStream,
15    lex_str, reconstruct,
16};
17
18#[derive(Debug)]
19pub enum ParseError {
20    Lex(LexError),
21    Language(tree_sitter::LanguageError),
22    Cancelled,
23    InvalidSyntax(Span),
24    Reconstruct(ReconstructError),
25}
26
27impl Display for ParseError {
28    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Self::Lex(error) => error.fmt(formatter),
31            Self::Language(error) => error.fmt(formatter),
32            Self::Cancelled => formatter.write_str("Tree-sitter parsing was cancelled"),
33            Self::InvalidSyntax(span) => match span.range() {
34                Ok(range) => write!(
35                    formatter,
36                    "invalid Macaulay2 syntax at byte {}",
37                    range.start().map_or(0, |point| point.byte)
38                ),
39                Err(_) => formatter.write_str("invalid Macaulay2 syntax"),
40            },
41            Self::Reconstruct(error) => error.fmt(formatter),
42        }
43    }
44}
45
46impl Error for ParseError {
47    fn source(&self) -> Option<&(dyn Error + 'static)> {
48        match self {
49            Self::Lex(error) => Some(error),
50            Self::Language(error) => Some(error),
51            Self::Reconstruct(error) => Some(error),
52            Self::Cancelled | Self::InvalidSyntax(_) => None,
53        }
54    }
55}
56
57impl From<ReconstructError> for ParseError {
58    fn from(error: ReconstructError) -> Self {
59        Self::Reconstruct(error)
60    }
61}
62
63impl From<LexError> for ParseError {
64    fn from(error: LexError) -> Self {
65        Self::Lex(error)
66    }
67}
68
69pub struct TreeSitterParser {
70    parser: tree_sitter::Parser,
71}
72
73impl TreeSitterParser {
74    pub fn new() -> Result<Self, ParseError> {
75        let mut parser = tree_sitter::Parser::new();
76        parser
77            .set_language(&tree_sitter_macaulay2::language())
78            .map_err(ParseError::Language)?;
79        Ok(Self { parser })
80    }
81}
82
83impl Parser for TreeSitterParser {
84    type Error = ParseError;
85
86    fn parse_cells(&mut self, tokens: CellStream) -> Result<SourceFile, Self::Error> {
87        let source_id = tokens.source_id();
88        let source = tokens.to_string();
89        let tree = self
90            .parser
91            .parse(&source, None)
92            .ok_or(ParseError::Cancelled)?;
93        if let Some(error) = first_error(tree.root_node()) {
94            return Err(ParseError::InvalidSyntax(
95                TreeSitterNode::new(error, source.as_bytes(), source_id).span(),
96            ));
97        }
98        reconstruct(TreeSitterNode::new(
99            tree.root_node(),
100            source.as_bytes(),
101            source_id,
102        ))
103        .map_err(Into::into)
104    }
105}
106
107pub fn parse_file(source: &str, source_id: SourceId) -> Result<SourceFile, ParseError> {
108    let mut parser = TreeSitterParser::new()?;
109    parser.parse_cells(lex_str(source, source_id)?)
110}
111
112/// Parses an emitted M2 token stream into the complete typed source file.
113pub fn parse_tokens(tokens: &TokenStream, source_id: SourceId) -> Result<SourceFile, ParseError> {
114    parse_file(&tokens.to_string(), source_id)
115}
116
117fn first_error(root: tree_sitter::Node<'_>) -> Option<tree_sitter::Node<'_>> {
118    let mut pending = vec![root];
119    while let Some(node) = pending.pop() {
120        if node.is_error() || node.is_missing() {
121            return Some(node);
122        }
123        pending.extend((0..node.child_count()).rev().filter_map(|index| {
124            node.child(u32::try_from(index).expect("Tree-sitter child count fits u32"))
125        }));
126    }
127    None
128}
129
130#[derive(Clone, Copy)]
131pub struct TreeSitterNode<'tree, 'source> {
132    node: tree_sitter::Node<'tree>,
133    source: &'source [u8],
134    source_id: SourceId,
135}
136
137impl<'tree, 'source> TreeSitterNode<'tree, 'source> {
138    pub fn new(node: tree_sitter::Node<'tree>, source: &'source [u8], source_id: SourceId) -> Self {
139        Self {
140            node,
141            source,
142            source_id,
143        }
144    }
145
146    pub fn raw(self) -> tree_sitter::Node<'tree> {
147        self.node
148    }
149
150    fn normalized_node(self) -> tree_sitter::Node<'tree> {
151        if self.node.kind() != "cell" {
152            return self.node;
153        }
154
155        // Grammar 5 represented a global muted form as `cell(muted(...))`.
156        // The current grammar exposes that `muted` node directly under the
157        // source file, so erase the obsolete adapter-only wrapper here.
158        let mut named_children = (0..self.node.named_child_count())
159            .filter_map(|index| self.node.named_child(index as u32));
160        match (named_children.next(), named_children.next()) {
161            (Some(child), None) if child.kind() == "muted" => child,
162            _ => self.node,
163        }
164    }
165}
166
167impl ExternalCstNode for TreeSitterNode<'_, '_> {
168    type Children<'syntax>
169        = IntoIter<ExternalCstChild<Self>>
170    where
171        Self: 'syntax;
172
173    fn identity(&self) -> CSTNodeClassLabel<'_> {
174        let node = self.normalized_node();
175        CSTNodeClassLabel::new(node.kind(), node.is_named())
176    }
177
178    fn children(&self) -> Self::Children<'_> {
179        let parent = self.normalized_node();
180        (0..parent.child_count())
181            .filter_map(|index| {
182                let index = u32::try_from(index).expect("Tree-sitter child count fits u32");
183                parent.child(index).map(|node| ExternalCstChild {
184                    field: parent.field_name_for_child(index),
185                    node: Self::new(node, self.source, self.source_id),
186                })
187            })
188            .collect::<Vec<_>>()
189            .into_iter()
190    }
191
192    fn text(&self) -> Cow<'_, str> {
193        String::from_utf8_lossy(&self.source[self.normalized_node().byte_range()])
194    }
195
196    fn is_extra(&self) -> bool {
197        self.node.is_extra()
198    }
199}
200
201impl Spanned for TreeSitterNode<'_, '_> {
202    fn span(&self) -> Span {
203        let node = self.normalized_node();
204        let range = TextRange::new(
205            TextPoint::new(
206                node.start_position().row as u32,
207                node.start_position().column as u32,
208                node.start_byte(),
209            ),
210            TextPoint::new(
211                node.end_position().row as u32,
212                node.end_position().column as u32,
213                node.end_byte(),
214            ),
215        );
216        Span::new(self.source_id, range)
217    }
218}