Skip to main content

oak_d2/parser/
mod.rs

1/// Element type definitions for D2.
2pub mod element_type;
3
4use crate::{D2TokenType, language::D2Language, lexer::D2Lexer};
5use oak_core::{
6    GreenNode, Parser,
7    parser::{ParseCache, ParseOutput, ParserState, parse_with_lexer},
8    source::{Source, TextEdit},
9};
10
11/// Parser for D2 diagram language.
12pub struct D2Parser<'config> {
13    config: &'config D2Language,
14}
15
16impl<'config> D2Parser<'config> {
17    /// Creates a new D2Parser with the given language configuration.
18    pub fn new(config: &'config D2Language) -> Self {
19        Self { config }
20    }
21}
22
23impl<'config> Parser<D2Language> for D2Parser<'config> {
24    fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<D2Language>) -> ParseOutput<'a, D2Language> {
25        let lexer = D2Lexer::new(self.config);
26        parse_with_lexer(&lexer, text, edits, cache, |state| {
27            while state.not_at_end() {
28                if let Some(_) = self.parse_element(state) {
29                    // Element was parsed and added to the sink
30                }
31                else {
32                    // Skip unexpected tokens
33                    state.advance();
34                }
35            }
36
37            // The root node is automatically created by the parser framework
38            Ok(state.sink.finish_node(0, element_type::D2ElementType::Root))
39        })
40    }
41}
42
43impl<'config> D2Parser<'config> {
44    fn parse_element<'a, S: Source + ?Sized>(&self, state: &mut ParserState<'a, D2Language, S>) -> Option<&'a GreenNode<'a, D2Language>> {
45        // Try to parse a shape
46        if let Some(shape) = self.parse_shape(state) {
47            return Some(shape);
48        }
49
50        // Try to parse a connection
51        if let Some(connection) = self.parse_connection(state) {
52            return Some(connection);
53        }
54
55        None
56    }
57
58    fn parse_shape<'a, S: Source + ?Sized>(&self, state: &mut ParserState<'a, D2Language, S>) -> Option<&'a GreenNode<'a, D2Language>> {
59        // Check for identifier
60        if !state.at(D2TokenType::Id) {
61            return None;
62        }
63
64        let checkpoint = state.checkpoint();
65
66        // Consume identifier
67        state.bump();
68
69        // Check for colon
70        if state.at(D2TokenType::Colon) {
71            state.bump();
72
73            // Check for label
74            if state.at(D2TokenType::Label) {
75                state.bump();
76            }
77        }
78
79        Some(state.finish_at(checkpoint, element_type::D2ElementType::Shape))
80    }
81
82    fn parse_connection<'a, S: Source + ?Sized>(&self, state: &mut ParserState<'a, D2Language, S>) -> Option<&'a GreenNode<'a, D2Language>> {
83        // Check for first identifier
84        if !state.at(D2TokenType::Id) {
85            return None;
86        }
87
88        let checkpoint = state.checkpoint();
89
90        // Consume first identifier
91        state.bump();
92
93        // Check for arrow
94        if !state.at(D2TokenType::Arrow) {
95            return None;
96        }
97
98        // Consume arrow
99        state.bump();
100
101        // Check for second identifier
102        if !state.at(D2TokenType::Id) {
103            return None;
104        }
105
106        // Consume second identifier
107        state.bump();
108
109        Some(state.finish_at(checkpoint, element_type::D2ElementType::Connection))
110    }
111}