Skip to main content

oak_typst/builder/
mod.rs

1use crate::{
2    ast::*,
3    language::TypstLanguage,
4    lexer::token_type::TypstTokenType,
5    parser::{TypstParser, element_type::TypstElementType},
6};
7use oak_core::{
8    Builder, BuilderCache, GreenNode, OakDiagnostics, OakError, Parser, RedNode, RedTree,
9    source::{Source, TextEdit},
10};
11
12/// AST builder for Typst language
13#[derive(Clone)]
14pub struct TypstBuilder<'config> {
15    config: &'config TypstLanguage,
16}
17
18impl<'config> TypstBuilder<'config> {
19    /// Creates a new TypstBuilder with the given language configuration.
20    pub fn new(config: &'config TypstLanguage) -> Self {
21        Self { config }
22    }
23}
24
25impl<'config> Builder<TypstLanguage> for TypstBuilder<'config> {
26    fn build<'a, S: Source + ?Sized>(&self, source: &'a S, edits: &[TextEdit], cache: &'a mut impl BuilderCache<TypstLanguage>) -> OakDiagnostics<TypstRoot> {
27        let parser = TypstParser::new(self.config);
28
29        let parse_result = parser.parse(source, edits, cache);
30
31        match parse_result.result {
32            Ok(green_tree) => match self.build_root(green_tree, source) {
33                Ok(ast_root) => OakDiagnostics { result: Ok(ast_root), diagnostics: parse_result.diagnostics },
34                Err(build_error) => {
35                    let mut diagnostics = parse_result.diagnostics;
36                    diagnostics.push(build_error.clone());
37                    OakDiagnostics { result: Err(build_error), diagnostics }
38                }
39            },
40            Err(parse_error) => OakDiagnostics { result: Err(parse_error), diagnostics: parse_result.diagnostics },
41        }
42    }
43}
44
45impl<'config> TypstBuilder<'config> {
46    pub(crate) fn build_root<S: Source + ?Sized>(&self, green_tree: &GreenNode<TypstLanguage>, source: &S) -> Result<TypstRoot, OakError> {
47        let red_root = RedNode::new(green_tree, 0);
48        let mut root = TypstRoot::new(red_root.span());
49
50        for child in red_root.children() {
51            if let Some(item) = self.build_tree(child, source)? {
52                root.items.push(item);
53            }
54        }
55
56        Ok(root)
57    }
58
59    fn build_tree<S: Source + ?Sized>(&self, tree: RedTree<TypstLanguage>, source: &S) -> Result<Option<TypstItem>, OakError> {
60        match tree {
61            RedTree::Node(node) => self.build_item(node, source),
62            RedTree::Leaf(leaf) => match leaf.kind {
63                TypstTokenType::Whitespace | TypstTokenType::Newline => Ok(Some(TypstItem::Space)),
64                _ => Ok(Some(TypstItem::Text(source.get_text_in(leaf.span).to_string()))),
65            },
66        }
67    }
68
69    fn build_item<S: Source + ?Sized>(&self, node: RedNode<TypstLanguage>, source: &S) -> Result<Option<TypstItem>, OakError> {
70        match node.kind::<TypstElementType>() {
71            TypstElementType::Heading => {
72                let text = source.get_text_in(node.span());
73                let mut level = 0;
74                for ch in text.chars() {
75                    if ch == '=' {
76                        level += 1;
77                    }
78                    else {
79                        break;
80                    }
81                }
82                let content_text = text.trim_start_matches('=').trim_start().to_string();
83                let mut content = TypstRoot::new(node.span());
84                content.items.push(TypstItem::Text(content_text));
85
86                Ok(Some(TypstItem::Heading(TypstHeading { level, content })))
87            }
88            TypstElementType::Math => {
89                let mut root = TypstRoot::new(node.span());
90                for child in node.children() {
91                    if let Some(item) = self.build_tree(child, source)? {
92                        root.items.push(item);
93                    }
94                }
95                Ok(Some(TypstItem::Math(root)))
96            }
97            TypstElementType::Strong => {
98                let mut root = TypstRoot::new(node.span());
99                for child in node.children() {
100                    if let Some(item) = self.build_tree(child, source)? {
101                        root.items.push(item);
102                    }
103                }
104                Ok(Some(TypstItem::Strong(root)))
105            }
106            TypstElementType::Emphasis => {
107                let mut root = TypstRoot::new(node.span().into());
108                for child in node.children() {
109                    if let Some(item) = self.build_tree(child, source)? {
110                        root.items.push(item);
111                    }
112                }
113                Ok(Some(TypstItem::Emphasis(root)))
114            }
115            TypstElementType::Quote => {
116                let mut root = TypstRoot::new(node.span().into());
117                for child in node.children() {
118                    if let Some(item) = self.build_tree(child, source)? {
119                        root.items.push(item);
120                    }
121                }
122                Ok(Some(TypstItem::Quote(root)))
123            }
124            TypstElementType::ListItem => {
125                let mut root = TypstRoot::new(node.span().into());
126                for child in node.children() {
127                    if let Some(item) = self.build_tree(child, source)? {
128                        root.items.push(item);
129                    }
130                }
131                Ok(Some(TypstItem::ListItem(root)))
132            }
133            TypstElementType::EnumItem => {
134                let mut root = TypstRoot::new(node.span().into());
135                for child in node.children() {
136                    if let Some(item) = self.build_tree(child, source)? {
137                        root.items.push(item);
138                    }
139                }
140                Ok(Some(TypstItem::EnumItem(root)))
141            }
142            TypstElementType::Raw => Ok(Some(TypstItem::Raw(source.get_text_in(node.span()).to_string()))),
143            _ => Ok(None),
144        }
145    }
146}