Skip to main content

oak_typst/builder/
mod.rs

1use crate::{ast::*, kind::TypstSyntaxKind, language::TypstLanguage, parser::TypstParser};
2use oak_core::{
3    Builder, BuilderCache, GreenNode, OakDiagnostics, OakError, Parser, RedNode, RedTree,
4    source::{Source, TextEdit},
5};
6
7/// Typst 语言的 AST 构建器
8#[derive(Clone)]
9pub struct TypstBuilder<'config> {
10    config: &'config TypstLanguage,
11}
12
13impl<'config> TypstBuilder<'config> {
14    pub fn new(config: &'config TypstLanguage) -> Self {
15        Self { config }
16    }
17}
18
19impl<'config> Builder<TypstLanguage> for TypstBuilder<'config> {
20    fn build<'a, S: Source + ?Sized>(&self, source: &'a S, edits: &[TextEdit], cache: &'a mut impl BuilderCache<TypstLanguage>) -> OakDiagnostics<TypstRoot> {
21        let parser = TypstParser::new(self.config);
22
23        let parse_result = parser.parse(source, edits, cache);
24
25        match parse_result.result {
26            Ok(green_tree) => match self.build_root(green_tree, source) {
27                Ok(ast_root) => OakDiagnostics { result: Ok(ast_root), diagnostics: parse_result.diagnostics },
28                Err(build_error) => {
29                    let mut diagnostics = parse_result.diagnostics;
30                    diagnostics.push(build_error.clone());
31                    OakDiagnostics { result: Err(build_error), diagnostics }
32                }
33            },
34            Err(parse_error) => OakDiagnostics { result: Err(parse_error), diagnostics: parse_result.diagnostics },
35        }
36    }
37}
38
39impl<'config> TypstBuilder<'config> {
40    pub(crate) fn build_root<S: Source + ?Sized>(&self, green_tree: &GreenNode<TypstLanguage>, source: &S) -> Result<TypstRoot, OakError> {
41        let red_root = RedNode::new(green_tree, 0);
42        let mut root = TypstRoot::new(red_root.span());
43
44        for child in red_root.children() {
45            if let Some(item) = self.build_tree(child, source)? {
46                root.items.push(item);
47            }
48        }
49
50        Ok(root)
51    }
52
53    fn build_tree<S: Source + ?Sized>(&self, tree: RedTree<TypstLanguage>, source: &S) -> Result<Option<TypstItem>, OakError> {
54        match tree {
55            RedTree::Node(node) => self.build_item(node, source),
56            RedTree::Leaf(leaf) => match leaf.kind {
57                TypstSyntaxKind::Whitespace | TypstSyntaxKind::Newline => Ok(Some(TypstItem::Space)),
58                _ => Ok(Some(TypstItem::Text(source.get_text_in(leaf.span).to_string()))),
59            },
60        }
61    }
62
63    fn build_item<S: Source + ?Sized>(&self, node: RedNode<TypstLanguage>, source: &S) -> Result<Option<TypstItem>, OakError> {
64        match node.kind::<TypstSyntaxKind>() {
65            TypstSyntaxKind::Text => Ok(Some(TypstItem::Text(source.get_text_in(node.span()).to_string()))),
66            TypstSyntaxKind::Paragraph => {
67                let mut root = TypstRoot::new(node.span());
68                for child in node.children() {
69                    if let Some(item) = self.build_tree(child, source)? {
70                        root.items.push(item);
71                    }
72                }
73                Ok(Some(TypstItem::Block(root)))
74            }
75            TypstSyntaxKind::Math | TypstSyntaxKind::InlineMath | TypstSyntaxKind::DisplayMath => {
76                let mut root = TypstRoot::new(node.span());
77                for child in node.children() {
78                    if let Some(item) = self.build_tree(child, source)? {
79                        root.items.push(item);
80                    }
81                }
82                Ok(Some(TypstItem::Math(root)))
83            }
84            TypstSyntaxKind::Heading => {
85                let text = source.get_text_in(node.span());
86                let mut level = 0;
87                for ch in text.chars() {
88                    if ch == '=' {
89                        level += 1;
90                    }
91                    else {
92                        break;
93                    }
94                }
95                let content_text = text.trim_start_matches('=').trim_start().to_string();
96                let mut content = TypstRoot::new(node.span());
97                content.items.push(TypstItem::Text(content_text));
98
99                Ok(Some(TypstItem::Heading(TypstHeading { level, content })))
100            }
101            TypstSyntaxKind::Strong => {
102                let mut root = TypstRoot::new(node.span());
103                for child in node.children() {
104                    if let Some(item) = self.build_tree(child, source)? {
105                        root.items.push(item);
106                    }
107                }
108                Ok(Some(TypstItem::Strong(root)))
109            }
110            TypstSyntaxKind::Emphasis => {
111                let mut root = TypstRoot::new(node.span());
112                for child in node.children() {
113                    if let Some(item) = self.build_tree(child, source)? {
114                        root.items.push(item);
115                    }
116                }
117                Ok(Some(TypstItem::Emphasis(root)))
118            }
119            TypstSyntaxKind::ListItem => {
120                let mut root = TypstRoot::new(node.span());
121                for child in node.children() {
122                    if let Some(item) = self.build_tree(child, source)? {
123                        root.items.push(item);
124                    }
125                }
126                Ok(Some(TypstItem::ListItem(root)))
127            }
128            TypstSyntaxKind::EnumItem => {
129                let mut root = TypstRoot::new(node.span());
130                for child in node.children() {
131                    if let Some(item) = self.build_tree(child, source)? {
132                        root.items.push(item);
133                    }
134                }
135                Ok(Some(TypstItem::EnumItem(root)))
136            }
137            TypstSyntaxKind::Link => Ok(Some(TypstItem::Link(TypstLink { url: source.get_text_in(node.span()).to_string(), content: None }))),
138            TypstSyntaxKind::Raw => Ok(Some(TypstItem::Raw(source.get_text_in(node.span()).to_string()))),
139            TypstSyntaxKind::Quote => {
140                let mut root = TypstRoot::new(node.span());
141                // In our simple parser, Quote just contains the whole line
142                // But for the converter, we want the content inside #quote[...]
143                let text = source.get_text_in(node.span()).to_string();
144                let content = if text.starts_with("#quote[") && text.ends_with("]") {
145                    &text[7..text.len() - 1]
146                }
147                else if text.starts_with("#quote") {
148                    &text[6..]
149                }
150                else {
151                    &text
152                };
153                root.items.push(TypstItem::Text(content.trim().to_string()));
154                Ok(Some(TypstItem::Quote(root)))
155            }
156            _ => {
157                let mut has_children = false;
158                for _ in node.children() {
159                    has_children = true;
160                    break;
161                }
162                if has_children {
163                    // Could recursively build, but for now just return None for unknown blocks
164                }
165                Ok(None)
166            }
167        }
168    }
169}