donet_core/parser/
semantics.rs

1/*
2    This file is part of Donet.
3
4    Copyright © 2024-2025 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 methods that make it easy for the Donet daemon to look up
28//! information on the DC contract at runtime in order to understand the
29//! 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 anyhow::Result;
38
39/// Takes in the [`Abstract Syntax Trees`] from the last stage of the pipeline
40/// and outputs error diagnostics if any issues are found.
41///
42/// [`Abstract Syntax Trees`]: https://en.wikipedia.org/wiki/Abstract_syntax_tree
43///
44pub fn semantic_analyzer(pipeline: &mut PipelineData) -> Result<(), DCReadError> {
45    // tell the pipeline we are moving onto the next stage
46    pipeline.next_stage();
47
48    // Iterate through all ASTs and analyze it.
49    for ast in pipeline.syntax_trees.clone() {
50        for type_declaration in ast.type_declarations.iter() {
51            match type_declaration {
52                ast::TypeDeclaration::PythonImport(import) => {
53                    dcfile::semantics::analyze_python_import(pipeline, import);
54                }
55                ast::TypeDeclaration::KeywordType(keyword) => {
56                    dcfile::semantics::analyze_keyword(pipeline, keyword);
57                }
58                ast::TypeDeclaration::StructType(_strukt) => {
59                    // TODO
60                }
61                ast::TypeDeclaration::DClassType(_dclass) => {
62                    // TODO
63                }
64                ast::TypeDeclaration::TypedefType(_typedef) => {
65                    // TODO
66                }
67            }
68        }
69        pipeline.next_file(); // tell the pipeline we are processing the next file
70    }
71
72    if pipeline.failing() {
73        Err(DCReadError::Semantic)
74    } else {
75        Ok(())
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use crate::read_dc;
83    use dcfile::DCPythonImport;
84
85    #[test]
86    fn python_imports() {
87        let dc_string: &str = "
88            from views import *
89            from views import DistributedDonut
90            from views import Class/AI/OV
91        ";
92
93        let dcf: dcfile::DCFile = read_dc(dc_string.into()).expect("Failed to parse syntax.");
94
95        let num_imports: usize = dcf.get_num_imports();
96        assert_eq!(num_imports, 3);
97
98        let symbols: Vec<Vec<String>> = vec![
99            vec!["*".into()],
100            vec!["DistributedDonut".into()],
101            vec!["Class".into(), "ClassAI".into(), "ClassOV".into()],
102        ];
103
104        for index in 0..num_imports - 1 {
105            let import: &DCPythonImport = dcf.get_python_import(index);
106
107            assert_eq!(import.module, "views");
108
109            let target_symbols: &Vec<String> = symbols.get(index).unwrap();
110
111            assert_eq!(*target_symbols, import.symbols);
112        }
113    }
114
115    #[test]
116    #[should_panic]
117    fn redundant_view_suffix() {
118        let dc_string: &str = "
119            from views import Class/AI/OV/OV
120        ";
121
122        let _ = read_dc(dc_string.into()).expect("Should fail.");
123    }
124
125    #[test]
126    #[should_panic]
127    fn keyword_already_defined() {
128        let dc_string: &str = "
129            keyword abcdef;
130            keyword abcdef;
131        ";
132
133        let _ = read_dc(dc_string.into()).expect("Should fail.");
134    }
135}