donet_core/parser/
semantics.rs

1/*
2    This file is part of Donet.
3
4    Copyright © 2024 Max Rodriguez
5
6    Donet is free software; you can redistribute it and/or modify
7    it under the terms of the GNU Affero General Public License,
8    as published by the Free Software Foundation, either version 3
9    of the License, or (at your option) any later version.
10
11    Donet is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14    GNU Affero General Public License for more details.
15
16    You should have received a copy of the GNU Affero General Public
17    License along with Donet. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20//! The DC parser outputs an [`Abstract Syntax Tree`], which is just a big
21//! nested structure that defines the declarations in the DC file. At runtime,
22//! the Donet daemon (and its services) need a class hierarchy structure in
23//! memory to access while processing network messages.
24//!
25//! This source file defines the process of taking in the DC file abstract
26//! syntax tree as input and generating an output of a class hierarchy structure,
27//! where each class has pointers to its children, and vice versa, with methods
28//! that make it easy for the Donet daemon to look up information on the DC contract
29//! at runtime in order to understand the network messages it receives.
30//!
31//! [`Abstract Syntax Tree`]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
32
33use super::ast;
34use super::error::DCReadError;
35use super::PipelineData;
36use crate::dcfile;
37use crate::dconfig::*;
38use anyhow::Result;
39
40/// Takes in the [`Abstract Syntax Trees`] from the last stage of the pipeline
41/// and outputs a [`crate::dcfile::DCFile`] immutable structure.
42///
43/// [`Abstract Syntax Tree`]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
44pub fn semantic_analyzer<'a>(pipeline: &mut PipelineData) -> Result<dcfile::DCFile<'a>, DCReadError> {
45    // tell the pipeline we are moving onto the next stage
46    pipeline.next_stage();
47
48    // create a new interim DC file struct from our pipeline's dc parser configuration
49    let mut dc_file = dcfile::interim::DCFile::from(pipeline.get_dc_config().clone());
50
51    // Iterate through all ASTs and add them to our DCFile intermediate object.
52    for ast in pipeline.syntax_trees.clone() {
53        for type_declaration in ast.type_declarations {
54            match type_declaration {
55                ast::TypeDeclaration::PythonImport(import) => {
56                    dc_file.add_python_import(pipeline, import.clone());
57                }
58                ast::TypeDeclaration::KeywordType(keyword) => {
59                    dc_file.add_keyword(pipeline, keyword);
60                }
61                ast::TypeDeclaration::StructType(_) => {}
62                ast::TypeDeclaration::DClassType(_) => {}
63                ast::TypeDeclaration::TypedefType(_) => {}
64                // Ignore is returned by productions that parsed certain
65                // grammar that may be deprecated but ignored for
66                // compatibility & should not be added to the DC file.
67                ast::TypeDeclaration::Ignore => {}
68            }
69        }
70        pipeline.next_file(); // tell the pipeline we are processing the next file
71    }
72
73    if pipeline.failing() {
74        Err(DCReadError::Semantic)
75    } else {
76        // Convert intermediate DC file structure to final immutable DC file structure.
77        Ok(dc_file.into())
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::read_dc;
85    use dcfile::DCPythonImport;
86
87    #[test]
88    fn python_imports() {
89        let dc_config = DCFileConfig::default();
90        let dc_string: &str = "
91            from views import *
92            from views import DistributedDonut
93            from views import Class/AI/OV
94        ";
95
96        let dcf: dcfile::DCFile = read_dc(dc_config, dc_string.into()).expect("Failed to parse syntax.");
97
98        let num_imports: usize = dcf.get_num_imports();
99        assert_eq!(num_imports, 3);
100
101        let symbols: Vec<Vec<String>> = vec![
102            vec!["*".into()],
103            vec!["DistributedDonut".into()],
104            vec!["Class".into(), "ClassAI".into(), "ClassOV".into()],
105        ];
106
107        for index in 0..num_imports - 1 {
108            let import: &DCPythonImport = dcf.get_python_import(index);
109
110            assert_eq!(import.module, "views");
111
112            let target_symbols: &Vec<String> = symbols.get(index).unwrap();
113
114            assert_eq!(*target_symbols, import.symbols);
115        }
116    }
117
118    #[test]
119    #[should_panic]
120    fn redundant_view_suffix() {
121        let dc_config = DCFileConfig::default();
122        let dc_string: &str = "
123            from views import Class/AI/OV/OV
124        ";
125
126        let _ = read_dc(dc_config, dc_string.into()).expect("Should fail.");
127    }
128
129    #[test]
130    #[should_panic]
131    fn keyword_already_defined() {
132        let dc_config = DCFileConfig::default();
133        let dc_string: &str = "
134            keyword abcdef;
135            keyword abcdef;
136        ";
137
138        let _ = read_dc(dc_config, dc_string.into()).expect("Should fail.");
139    }
140}