protoflow_syntax/
system_parser.rs

1// This is free and unencumbered software released into the public domain.
2
3#[cfg(feature = "std")]
4extern crate std;
5
6use crate::{
7    prelude::{vec, BTreeSet, ToString, Vec},
8    AnalysisError, AnalysisResult,
9};
10use error_stack::ResultExt;
11use protoflow_blocks::BlockTag;
12use sysml_model::QualifiedName;
13
14pub use sysml_parser::{ParseError, ParsedBlock, ParsedMember, ParsedModel};
15
16#[derive(Debug, Default)]
17pub struct SystemParser {
18    pub(crate) model: ParsedModel,
19    pub(crate) imported_names: BTreeSet<QualifiedName>,
20}
21
22impl SystemParser {
23    fn new(model: ParsedModel) -> Self {
24        Self {
25            model,
26            ..Default::default()
27        }
28    }
29
30    #[cfg(feature = "std")]
31    pub fn from_file(pathname: impl AsRef<std::path::Path>) -> AnalysisResult<Self> {
32        Ok(Self::new(
33            sysml_parser::parse_from_file(pathname).change_context(AnalysisError::ParseFailure)?,
34        ))
35    }
36
37    #[cfg(feature = "std")]
38    pub fn from_reader(reader: impl std::io::Read) -> AnalysisResult<Self> {
39        Ok(Self::new(
40            sysml_parser::parse_from_reader(reader).change_context(AnalysisError::ParseFailure)?,
41        ))
42    }
43
44    pub fn from_string(&mut self, input: &str) -> AnalysisResult<Self> {
45        Ok(Self::new(
46            sysml_parser::parse_from_string(input).change_context(AnalysisError::ParseFailure)?,
47        ))
48    }
49
50    pub fn check(&mut self) -> AnalysisResult<&ParsedModel> {
51        let members: Vec<ParsedMember> = self.model.members().iter().cloned().collect();
52        for member in members {
53            self.check_usage(&member)?;
54        }
55        Ok(&self.model)
56    }
57
58    pub fn check_usage(
59        &mut self,
60        member: &ParsedMember,
61    ) -> core::result::Result<(), AnalysisError> {
62        match member {
63            ParsedMember::Import(import) => match import.imported_name.to_tuple3() {
64                (Some("Protoflow"), Some("*") | Some("**"), None) => {
65                    for block_tag in BlockTag::all() {
66                        let block_name = block_tag.to_string();
67                        self.imported_names.insert(QualifiedName::new(vec![
68                            "Protoflow".into(),
69                            block_name.into(),
70                        ]));
71                    }
72                }
73                (Some("Protoflow"), Some(unqualified_name), None) => {
74                    if !BlockTag::all()
75                        .iter()
76                        .any(|block_tag| block_tag.as_str() == unqualified_name)
77                    {
78                        return Err(AnalysisError::InvalidImport(import.imported_name.clone()));
79                    }
80                    self.imported_names.insert(import.imported_name.clone());
81                }
82                _ => {
83                    return Err(AnalysisError::InvalidImport(import.imported_name.clone()));
84                }
85            },
86            ParsedMember::Package(package) => {
87                for member in package.members() {
88                    self.check_usage(&member)?;
89                }
90            }
91            ParsedMember::BlockUsage(block) => {
92                if let Some(definition_name) = &block.definition {
93                    if !self.imported_names.contains(&definition_name) {
94                        return Err(AnalysisError::UnknownName(definition_name.clone()));
95                    }
96                }
97                let _ = self.check_block_usage(block)?;
98            }
99            ParsedMember::AttributeUsage(attribute) => {
100                if let Some(definition_name) = &attribute.definition {
101                    if !self.imported_names.contains(&definition_name) {
102                        return Err(AnalysisError::UnknownName(definition_name.clone()));
103                    }
104                }
105            }
106            ParsedMember::PortUsage(_port) => {
107                unreachable!("PortUsage")
108            }
109        };
110        Ok(())
111    }
112
113    pub fn check_block_usage(
114        &mut self,
115        _member: &ParsedBlock,
116    ) -> core::result::Result<(), AnalysisError> {
117        Ok(()) // TODO
118    }
119}