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}