Skip to main content

oak_crystal/builder/
mod.rs

1#![doc = include_str!("readme.md")]
2use crate::{
3    ast::*,
4    language::CrystalLanguage,
5    parser::{CrystalParser, element_type::CrystalElementType},
6};
7use oak_core::{Builder, BuilderCache, GreenNode, OakDiagnostics, Parser, RedNode, RedTree, SourceText, TextEdit, source::Source};
8
9/// AST builder for the Crystal language
10#[derive(Clone)]
11pub struct CrystalBuilder<'config> {
12    /// Language configuration
13    config: &'config CrystalLanguage,
14}
15
16impl<'config> CrystalBuilder<'config> {
17    /// Creates a new Crystal builder
18    pub fn new(config: &'config CrystalLanguage) -> Self {
19        Self { config }
20    }
21
22    /// Builds the AST root node from the syntax tree
23    pub fn build_root(&self, green: &GreenNode<CrystalLanguage>, source: &SourceText) -> Result<CrystalRoot, oak_core::OakError> {
24        let red = RedNode::new(green, 0);
25        let mut items = Vec::new();
26
27        for child in red.children() {
28            if let RedTree::Node(node) = child {
29                if let Some(item) = self.build_item(&node, source) {
30                    items.push(item);
31                }
32            }
33        }
34
35        Ok(CrystalRoot { items })
36    }
37
38    fn build_item(&self, node: &RedNode<CrystalLanguage>, source: &SourceText) -> Option<Item> {
39        match node.green.kind {
40            CrystalElementType::ClassDef => self.build_class(node, source).map(Item::Class),
41            CrystalElementType::ModuleDef => self.build_module(node, source).map(Item::Module),
42            CrystalElementType::MethodDef => self.build_method(node, source).map(Item::Def),
43            _ if node.green.kind as u16 >= CrystalElementType::IfExpr as u16 && node.green.kind as u16 <= CrystalElementType::ParenExpr as u16 => self.build_expression(node, source).map(Item::Expression),
44            _ => None,
45        }
46    }
47
48    fn build_class(&self, node: &RedNode<CrystalLanguage>, source: &SourceText) -> Option<ClassDeclaration> {
49        let mut name = None;
50        let mut body = Vec::new();
51
52        for child in node.children() {
53            if let RedTree::Node(n) = child {
54                match n.green.kind {
55                    CrystalElementType::Identifier => {
56                        if name.is_none() {
57                            name = Some(Identifier { name: source.get_text_in(n.span()).to_string(), span: n.span() });
58                        }
59                    }
60                    CrystalElementType::Block => {
61                        for block_child in n.children() {
62                            if let RedTree::Node(bn) = block_child {
63                                if let Some(item) = self.build_item(&bn, source) {
64                                    body.push(item);
65                                }
66                            }
67                        }
68                    }
69                    _ => {
70                        if let Some(item) = self.build_item(&n, source) {
71                            body.push(item);
72                        }
73                    }
74                }
75            }
76        }
77
78        name.map(|name| ClassDeclaration { name, body, span: node.span() })
79    }
80
81    fn build_module(&self, node: &RedNode<CrystalLanguage>, source: &SourceText) -> Option<ModuleDeclaration> {
82        let mut name = None;
83        let mut body = Vec::new();
84
85        for child in node.children() {
86            if let RedTree::Node(n) = child {
87                match n.green.kind {
88                    CrystalElementType::Identifier => {
89                        if name.is_none() {
90                            name = Some(Identifier { name: source.get_text_in(n.span()).to_string(), span: n.span() });
91                        }
92                    }
93                    CrystalElementType::Block => {
94                        for block_child in n.children() {
95                            if let RedTree::Node(bn) = block_child {
96                                if let Some(item) = self.build_item(&bn, source) {
97                                    body.push(item);
98                                }
99                            }
100                        }
101                    }
102                    _ => {
103                        if let Some(item) = self.build_item(&n, source) {
104                            body.push(item);
105                        }
106                    }
107                }
108            }
109        }
110
111        name.map(|name| ModuleDeclaration { name, body, span: node.span() })
112    }
113
114    fn build_method(&self, node: &RedNode<CrystalLanguage>, source: &SourceText) -> Option<MethodDeclaration> {
115        let mut name = None;
116        let mut params = Vec::new();
117        let mut body = Vec::new();
118
119        for child in node.children() {
120            if let RedTree::Node(n) = child {
121                match n.green.kind {
122                    CrystalElementType::Identifier => {
123                        if name.is_none() {
124                            name = Some(Identifier { name: source.get_text_in(n.span()).to_string(), span: n.span() });
125                        }
126                    }
127                    CrystalElementType::ParamList => {
128                        params = self.build_params(&n, source);
129                    }
130                    CrystalElementType::Block => {
131                        for block_child in n.children() {
132                            if let RedTree::Node(bn) = block_child {
133                                if let Some(item) = self.build_item(&bn, source) {
134                                    body.push(item);
135                                }
136                            }
137                        }
138                    }
139                    _ => {
140                        if let Some(item) = self.build_item(&n, source) {
141                            body.push(item);
142                        }
143                    }
144                }
145            }
146        }
147
148        name.map(|name| MethodDeclaration { name, params, body, span: node.span() })
149    }
150
151    fn build_params(&self, node: &RedNode<CrystalLanguage>, source: &SourceText) -> Vec<Parameter> {
152        let mut params = Vec::new();
153        for child in node.children() {
154            if let RedTree::Node(n) = child {
155                if n.green.kind == CrystalElementType::Param {
156                    if let Some(param) = self.build_param(&n, source) {
157                        params.push(param);
158                    }
159                }
160            }
161        }
162        params
163    }
164
165    fn build_param(&self, node: &RedNode<CrystalLanguage>, source: &SourceText) -> Option<Parameter> {
166        let mut name = None;
167        let mut type_name = None;
168
169        for child in node.children() {
170            if let RedTree::Node(n) = child {
171                match n.green.kind {
172                    CrystalElementType::Identifier => {
173                        if name.is_none() {
174                            name = Some(Identifier { name: source.get_text_in(n.span()).to_string(), span: n.span() });
175                        }
176                        else if type_name.is_none() {
177                            type_name = Some(Identifier { name: source.get_text_in(n.span()).to_string(), span: n.span() });
178                        }
179                    }
180                    _ => {}
181                }
182            }
183        }
184
185        name.map(|name| Parameter { name, type_name })
186    }
187
188    fn build_expression(&self, node: &RedNode<CrystalLanguage>, source: &SourceText) -> Option<Expression> {
189        match node.green.kind {
190            CrystalElementType::LiteralExpr => {
191                for child in node.children() {
192                    if let RedTree::Node(n) = child {
193                        match n.green.kind {
194                            CrystalElementType::Number => return Some(Expression::Literal(Literal::Number(source.get_text_in(n.span()).to_string()))),
195                            CrystalElementType::String => return Some(Expression::Literal(Literal::String(source.get_text_in(n.span()).to_string()))),
196                            CrystalElementType::TrueKeyword => return Some(Expression::Literal(Literal::Boolean(true))),
197                            CrystalElementType::FalseKeyword => return Some(Expression::Literal(Literal::Boolean(false))),
198                            CrystalElementType::NilKeyword => return Some(Expression::Literal(Literal::Nil)),
199                            _ => {}
200                        }
201                    }
202                }
203                None
204            }
205            CrystalElementType::CallExpr => {
206                let mut name = None;
207                let mut receiver = None;
208                let mut args = Vec::new();
209
210                for child in node.children() {
211                    if let RedTree::Node(n) = child {
212                        match n.green.kind {
213                            CrystalElementType::Identifier => {
214                                if name.is_none() {
215                                    name = Some(Identifier { name: source.get_text_in(n.span()).to_string(), span: n.span() });
216                                }
217                            }
218                            _ if n.green.kind as u16 >= CrystalElementType::IfExpr as u16 && n.green.kind as u16 <= CrystalElementType::ParenExpr as u16 => {
219                                if let Some(expr) = self.build_expression(&n, source) {
220                                    if receiver.is_none() {
221                                        receiver = Some(Box::new(expr));
222                                    }
223                                    else {
224                                        args.push(expr);
225                                    }
226                                }
227                            }
228                            _ => {}
229                        }
230                    }
231                }
232
233                name.map(|name| Expression::Call(Call { receiver, name, args }))
234            }
235            _ => None,
236        }
237    }
238}
239
240impl<'config> Builder<CrystalLanguage> for CrystalBuilder<'config> {
241    fn build<'a, S: Source + ?Sized>(&self, source: &'a S, edits: &[TextEdit], _cache: &'a mut impl BuilderCache<CrystalLanguage>) -> oak_core::builder::BuildOutput<CrystalLanguage> {
242        let parser = CrystalParser::new(self.config);
243        let mut cache = oak_core::parser::ParseSession::<CrystalLanguage>::default();
244        let parse_result = parser.parse(source, edits, &mut cache);
245
246        match parse_result.result {
247            Ok(green_tree) => {
248                let source_text = SourceText::new(source.get_text_in((0..source.length()).into()).into_owned());
249                match self.build_root(green_tree, &source_text) {
250                    Ok(ast_root) => OakDiagnostics { result: Ok(ast_root), diagnostics: parse_result.diagnostics },
251                    Err(build_error) => {
252                        let mut diagnostics = parse_result.diagnostics;
253                        diagnostics.push(build_error.clone());
254                        OakDiagnostics { result: Err(build_error), diagnostics }
255                    }
256                }
257            }
258            Err(parse_error) => OakDiagnostics { result: Err(parse_error), diagnostics: parse_result.diagnostics },
259        }
260    }
261}