mlang_rs/lang/
analyzer.rs

1//! semantic analyzer for `mlang`.
2
3use std::collections::HashMap;
4
5use parserc::Span;
6
7use super::ir::*;
8
9const ANALYZER_ERROR: &str = "MLANG_ANALYZER";
10
11/// Error report by semantic analyze step.
12#[derive(Debug, thiserror::Error)]
13pub enum AnalyzerError {
14    #[error("duplicate symbol `{0}`, previous declaration is here {1}")]
15    Duplicate(String, Span),
16
17    #[error("Unknown symbol `{0}`.")]
18    Unknown(String),
19
20    #[error("Use group `{0}` as field type declaration, group declaration is here {1}")]
21    Group(String, Span),
22
23    #[error("Unable merge mixin({0})'s fields into node, mixin declaration is here {1}.")]
24    Merge(String, Span),
25
26    #[error("Custom property `{0}`, expect empty call list.")]
27    VariableOption(String),
28
29    #[error("Custom property `rename`, expect one `literial str` as call list.")]
30    Rename,
31}
32
33#[derive(Default)]
34struct SymbolTable(HashMap<String, (Span, usize)>);
35
36impl SymbolTable {
37    /// Add a new symbol to the checker.
38    fn add(&mut self, index: usize, ident: &Ident) -> bool {
39        if let Some((span, _)) = self.0.insert(ident.1.clone(), (ident.0, index)) {
40            log::error!(target: ANALYZER_ERROR, span:serde; "{}", AnalyzerError::Duplicate(ident.1.clone(), span));
41            false
42        } else {
43            true
44        }
45    }
46
47    /// Search symbol.
48    fn lookup(&self, ident: &Ident) -> Option<usize> {
49        self.0.get(&ident.1).map(|(_, index)| *index)
50    }
51}
52
53#[derive(Default)]
54struct MixinTable {
55    mixin: HashMap<String, (Span, usize)>,
56}
57
58impl MixinTable {
59    /// add new mixin item.
60    ///
61    /// This function delegate symbol conflict check to `SymbolChecker`
62    fn add(&mut self, index: usize, ident: &Ident) {
63        self.mixin.insert(ident.1.clone(), (ident.0, index));
64    }
65
66    fn lookup(&self, ident: &Ident) -> Option<usize> {
67        self.mixin.get(ident.1.as_str()).map(|(_, index)| *index)
68    }
69}
70
71#[derive(Default)]
72struct GroupTable {
73    groups: HashMap<String, (Span, usize)>,
74}
75
76impl GroupTable {
77    /// See a group item.
78    fn add(&mut self, index: usize, ident: &Ident) {
79        self.groups.insert(ident.1.clone(), (ident.0, index));
80    }
81
82    fn lookup(&self, ident: &Ident) -> Option<usize> {
83        self.groups.get(ident.1.as_str()).map(|(_, index)| *index)
84    }
85}
86
87/// A semantic analyzer for `mlang`.
88#[derive(Default)]
89struct SemanticAnalyzer {
90    /// A symbol index database.
91    symbol_table: SymbolTable,
92    /// A mixin fields merger.
93    merger: MixinTable,
94    /// `apply..to..` `chidlren..of..` syntax checker.
95    digraph_analyzer: GroupTable,
96    /// report errors.
97    errors: usize,
98}
99
100impl SemanticAnalyzer {
101    fn analyze(mut self, opcodes: &mut [Stat]) -> bool {
102        self.build_index(opcodes);
103        self.check(opcodes);
104        self.errors == 0
105    }
106
107    fn build_index(&mut self, opcodes: &mut [Stat]) {
108        for (index, opcode) in opcodes.iter().enumerate() {
109            match opcode {
110                Stat::Element(node) | Stat::Leaf(node) | Stat::Attr(node) | Stat::Data(node) => {
111                    if !self.symbol_table.add(index, &node.ident) {
112                        self.errors += 1;
113                    }
114                }
115                Stat::Mixin(node) => {
116                    if !self.symbol_table.add(index, &node.ident) {
117                        self.errors += 1;
118                    }
119                    self.merger.add(index, &node.ident);
120                }
121                Stat::Enum(node) => {
122                    if !self.symbol_table.add(index, &node.ident) {
123                        self.errors += 1;
124                    }
125                }
126                Stat::Group(node) => {
127                    if !self.symbol_table.add(index, &node.ident) {
128                        self.errors += 1;
129                    }
130                    self.digraph_analyzer.add(index, &node.ident);
131                }
132                Stat::ApplyTo(_) => {}
133                Stat::ChildrenOf(_) => {}
134            }
135        }
136    }
137
138    fn check(&mut self, opcodes: &mut [Stat]) {
139        let mut updates = vec![];
140        for (index, opcode) in opcodes.iter().enumerate() {
141            match opcode {
142                Stat::Element(node) => {
143                    if let Some(node) = self.node_check(opcodes, node) {
144                        updates.push((index, Stat::Element(Box::new(node))));
145                    }
146                }
147                Stat::Leaf(node) => {
148                    if let Some(node) = self.node_check(opcodes, node) {
149                        updates.push((index, Stat::Leaf(Box::new(node))));
150                    }
151                }
152                Stat::Attr(node) => {
153                    if let Some(node) = self.node_check(opcodes, node) {
154                        updates.push((index, Stat::Attr(Box::new(node))));
155                    }
156                }
157                Stat::Mixin(node) => {
158                    assert_eq!(
159                        self.node_check(opcodes, node),
160                        None,
161                        "Mixin: inner error, mixin can't mixin other one."
162                    );
163                }
164                Stat::Data(node) => {
165                    if let Some(node) = self.node_check(opcodes, node) {
166                        updates.push((index, Stat::Data(Box::new(node))));
167                    }
168                }
169                Stat::Enum(node) => {
170                    self.enum_check(opcodes, node);
171                }
172                Stat::Group(group) => {
173                    self.group_check(opcodes, group);
174                }
175                Stat::ApplyTo(apply_to) => {
176                    if let Some(opcode) = self.apply_to_check(opcodes, apply_to) {
177                        updates.push((index, opcode));
178                    }
179                }
180                Stat::ChildrenOf(children_of) => {
181                    if let Some(opcode) = self.children_of_check(opcodes, children_of) {
182                        updates.push((index, opcode));
183                    }
184                }
185            }
186        }
187
188        for (index, update) in updates {
189            opcodes[index] = update;
190        }
191    }
192
193    fn symbol_check(&mut self, opcodes: &[Stat], ident: &Ident, expect_type: bool) -> bool {
194        if let Some(index) = self.symbol_table.lookup(ident) {
195            if let Stat::Group(group) = &opcodes[index] {
196                if expect_type {
197                    self.errors += 1;
198                    log::error!(
199                        target: ANALYZER_ERROR, span:serde = ident.0;
200                        "{}", AnalyzerError::Group(group.ident.1.clone(), group.ident.0)
201                    );
202                }
203
204                return false;
205            }
206
207            return true;
208        } else {
209            self.errors += 1;
210            log::error!(
211                target: ANALYZER_ERROR, span:serde = ident.0;
212                "{}", AnalyzerError::Unknown(ident.1.clone())
213            );
214            return false;
215        }
216    }
217
218    fn type_check(&mut self, opcodes: &[Stat], ty: &Type) {
219        match ty {
220            Type::Data(ident) => {
221                self.symbol_check(opcodes, ident, true);
222            }
223
224            Type::ListOf(component, _) => {
225                self.type_check(opcodes, component);
226            }
227            Type::ArrayOf(component, _, _) => {
228                self.type_check(opcodes, component);
229            }
230            _ => {}
231        }
232    }
233
234    fn node_check(&mut self, opcodes: &[Stat], node: &Node) -> Option<Node> {
235        for field in node.fields.iter() {
236            self.type_check(opcodes, &field.ty());
237        }
238
239        for property in &node.properties {
240            for call in &property.calls {
241                match call.target.1.as_str() {
242                    "option" | "variable" | "init" => {
243                        if call.params.len() != 0 {
244                            self.errors += 1;
245                            log::error!(
246                                target: ANALYZER_ERROR, span:serde = call.target.0;
247                                "{}", AnalyzerError::VariableOption(call.target.1.clone())
248                            );
249                        }
250                    }
251                    "rename" => {
252                        if call.params.len() != 1 {
253                            log::error!(
254                                target: ANALYZER_ERROR,
255                                span:serde = call.target.0; "{}", AnalyzerError::Rename
256                            );
257                        }
258                    }
259                    _ => {}
260                }
261            }
262        }
263
264        if let Some(mixin) = &node.mixin {
265            if let Some(index) = self.merger.lookup(mixin) {
266                if let Stat::Mixin(mixin) = &opcodes[index] {
267                    let expand = mixin.fields.clone();
268                    let fields = node.fields.clone();
269
270                    let fields = match fields.append(expand) {
271                        Ok(fields) => fields,
272                        Err(fields) => {
273                            log::error!(
274                                target: ANALYZER_ERROR,
275                                span:serde = node.ident.0;
276                                "{}",
277                                AnalyzerError::Merge(mixin.ident.1.clone(), mixin.ident.0)
278                            );
279                            fields
280                        }
281                    };
282
283                    return Some(Node {
284                        span: node.span,
285                        comments: node.comments.clone(),
286                        mixin: None,
287                        properties: node.properties.clone(),
288                        ident: node.ident.clone(),
289                        fields,
290                    });
291                } else {
292                    panic!("node_check(mxin): inner error.");
293                }
294            } else {
295                log::error!(
296                    target: ANALYZER_ERROR,
297                    span:serde = mixin.0; "{}", AnalyzerError::Unknown(mixin.1.clone())
298                );
299                return None;
300            }
301        }
302
303        return None;
304    }
305
306    fn enum_check(&mut self, opcodes: &[Stat], node: &Enum) {
307        for field_node in &node.fields {
308            for field in field_node.fields.iter() {
309                self.type_check(opcodes, field.ty());
310            }
311        }
312    }
313
314    fn group_check(&mut self, opcodes: &[Stat], node: &Group) {
315        for ident in &node.children {
316            self.symbol_check(opcodes, ident, true);
317        }
318    }
319
320    fn expand_with_group(&self, opcodes: &[Stat], ident: &Ident) -> Option<Vec<Ident>> {
321        if let Some(index) = self.digraph_analyzer.lookup(ident) {
322            if let Stat::Group(group) = &opcodes[index] {
323                return Some(group.children.clone());
324            } else {
325                panic!("expand_with_group: inner error.");
326            }
327        } else {
328            log::error!(
329                target: ANALYZER_ERROR,
330                span:serde = ident.0; "{}", AnalyzerError::Unknown(ident.1.clone())
331            );
332            None
333        }
334    }
335
336    fn apply_to_check(&mut self, opcodes: &[Stat], node: &ApplyTo) -> Option<Stat> {
337        let mut from_expand = vec![];
338
339        for ident in &node.from {
340            if !self.symbol_check(opcodes, ident, false) {
341                if let Some(mut expand) = self.expand_with_group(opcodes, ident) {
342                    from_expand.append(&mut expand);
343                }
344            } else {
345                from_expand.push(ident.clone());
346            }
347        }
348
349        let mut to_expand = vec![];
350
351        for ident in &node.to {
352            if !self.symbol_check(opcodes, ident, false) {
353                if let Some(mut expand) = self.expand_with_group(opcodes, ident) {
354                    to_expand.append(&mut expand);
355                }
356            } else {
357                to_expand.push(ident.clone());
358            }
359        }
360
361        Some(Stat::ApplyTo(Box::new(ApplyTo {
362            from: from_expand,
363            to: to_expand,
364            span: node.span,
365            comments: node.comments.clone(),
366            properties: node.properties.clone(),
367        })))
368    }
369
370    fn children_of_check(&mut self, opcodes: &[Stat], node: &ChildrenOf) -> Option<Stat> {
371        let mut from_expand = vec![];
372
373        for ident in &node.from {
374            if !self.symbol_check(opcodes, ident, false) {
375                if let Some(mut expand) = self.expand_with_group(opcodes, ident) {
376                    from_expand.append(&mut expand);
377                }
378            } else {
379                from_expand.push(ident.clone());
380            }
381        }
382
383        let mut to_expand = vec![];
384
385        for ident in &node.to {
386            if !self.symbol_check(opcodes, ident, false) {
387                if let Some(mut expand) = self.expand_with_group(opcodes, ident) {
388                    to_expand.append(&mut expand);
389                }
390            } else {
391                to_expand.push(ident.clone());
392            }
393        }
394
395        Some(Stat::ChildrenOf(Box::new(ChildrenOf {
396            from: from_expand,
397            to: to_expand,
398            span: node.span,
399            comments: node.comments.clone(),
400            properties: node.properties.clone(),
401        })))
402    }
403}
404
405/// Process semantic analyze on `opcodes` slice.
406pub fn semantic_analyze(opcodes: &mut [Stat]) -> bool {
407    SemanticAnalyzer::default().analyze(opcodes)
408}