Skip to main content

fmi_schema/fmi3/
terminals_and_icons.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::{Error, fmi3::variable::AbstractVariableTrait};
4
5use super::{Annotations, Fmi3ModelDescription};
6
7#[derive(Default, Debug, PartialEq, hard_xml::XmlRead, hard_xml::XmlWrite)]
8#[xml(
9    tag = "fmiTerminalsAndIcons",
10    strict(unknown_attribute, unknown_element)
11)]
12pub struct Fmi3TerminalsAndIcons {
13    #[xml(attr = "fmiVersion")]
14    pub fmi_version: String,
15    #[xml(child = "Terminals")]
16    pub terminals: Option<Terminals>,
17    #[xml(child = "Annotations")]
18    pub annotations: Option<Annotations>,
19}
20
21#[derive(Default, Debug, PartialEq, hard_xml::XmlRead, hard_xml::XmlWrite)]
22#[xml(tag = "Terminals", strict(unknown_attribute, unknown_element))]
23pub struct Terminals {
24    #[xml(child = "Terminal")]
25    pub terminals: Vec<Terminal>,
26}
27
28#[derive(Default, Debug, PartialEq, hard_xml::XmlRead, hard_xml::XmlWrite)]
29#[xml(tag = "Terminal", strict(unknown_attribute, unknown_element))]
30pub struct Terminal {
31    #[xml(child = "TerminalMemberVariable")]
32    pub terminal_member_variables: Vec<TerminalMemberVariable>,
33    #[xml(child = "TerminalStreamMemberVariable")]
34    pub terminal_stream_member_variables: Vec<TerminalStreamMemberVariable>,
35    #[xml(child = "Terminal")]
36    pub terminals: Vec<Terminal>,
37    #[xml(child = "Annotations")]
38    pub annotations: Option<Annotations>,
39    #[xml(attr = "name")]
40    pub name: String,
41    #[xml(attr = "matchingRule")]
42    pub matching_rule: String,
43    #[xml(attr = "terminalKind")]
44    pub terminal_kind: Option<String>,
45    #[xml(attr = "description")]
46    pub description: Option<String>,
47}
48
49#[derive(Default, Debug, PartialEq, hard_xml::XmlRead, hard_xml::XmlWrite)]
50#[xml(
51    tag = "TerminalMemberVariable",
52    strict(unknown_attribute, unknown_element)
53)]
54pub struct TerminalMemberVariable {
55    #[xml(child = "Annotations")]
56    pub annotations: Option<Annotations>,
57    #[xml(attr = "variableName")]
58    pub variable_name: String,
59    #[xml(attr = "memberName")]
60    pub member_name: Option<String>,
61    #[xml(attr = "variableKind")]
62    pub variable_kind: String,
63}
64
65#[derive(Default, Debug, PartialEq, hard_xml::XmlRead, hard_xml::XmlWrite)]
66#[xml(
67    tag = "TerminalStreamMemberVariable",
68    strict(unknown_attribute, unknown_element)
69)]
70pub struct TerminalStreamMemberVariable {
71    #[xml(child = "Annotations")]
72    pub annotations: Option<Annotations>,
73    #[xml(attr = "inStreamMemberName")]
74    pub in_stream_member_name: String,
75    #[xml(attr = "outStreamMemberName")]
76    pub out_stream_member_name: String,
77    #[xml(attr = "inStreamVariableName")]
78    pub in_stream_variable_name: String,
79    #[xml(attr = "outStreamVariableName")]
80    pub out_stream_variable_name: String,
81}
82
83#[derive(Debug, PartialEq, Eq)]
84pub enum MatchingRule {
85    Plug,
86    Bus,
87    Sequence,
88    Other(String),
89}
90
91impl Terminal {
92    pub fn matching_rule_kind(&self) -> MatchingRule {
93        match self.matching_rule.as_str() {
94            "plug" => MatchingRule::Plug,
95            "bus" => MatchingRule::Bus,
96            "sequence" => MatchingRule::Sequence,
97            other => MatchingRule::Other(other.to_string()),
98        }
99    }
100
101    pub fn validate_matching_rule(&self) -> Result<(), Error> {
102        match self.matching_rule_kind() {
103            MatchingRule::Plug | MatchingRule::Bus => {
104                let mut seen = HashSet::new();
105                for member in &self.terminal_member_variables {
106                    let member_name = member.member_name.as_deref().ok_or_else(|| {
107                        Error::Model(format!(
108                            "Terminal '{}' requires memberName for matchingRule '{}'",
109                            self.name, self.matching_rule
110                        ))
111                    })?;
112                    if !seen.insert(member_name) {
113                        return Err(Error::Model(format!(
114                            "Terminal '{}' has duplicate memberName '{}' for matchingRule '{}'",
115                            self.name, member_name, self.matching_rule
116                        )));
117                    }
118                }
119            }
120            MatchingRule::Sequence | MatchingRule::Other(_) => {}
121        }
122        Ok(())
123    }
124}
125
126pub struct ResolvedTerminals<'a> {
127    pub terminals: Vec<ResolvedTerminal<'a>>,
128}
129
130pub struct ResolvedTerminal<'a> {
131    pub terminal: &'a Terminal,
132    pub members: Vec<ResolvedTerminalMemberVariable<'a>>,
133    pub stream_members: Vec<ResolvedTerminalStreamMemberVariable<'a>>,
134    pub terminals: Vec<ResolvedTerminal<'a>>,
135}
136
137pub struct ResolvedTerminalMemberVariable<'a> {
138    pub member: &'a TerminalMemberVariable,
139    pub variable: &'a dyn AbstractVariableTrait,
140}
141
142pub struct ResolvedTerminalStreamMemberVariable<'a> {
143    pub member: &'a TerminalStreamMemberVariable,
144    pub in_stream_variable: &'a dyn AbstractVariableTrait,
145    pub out_stream_variable: &'a dyn AbstractVariableTrait,
146}
147
148#[derive(Debug, PartialEq, Eq)]
149pub enum TerminalResolutionError {
150    MissingVariable {
151        terminal_path: String,
152        variable_name: String,
153    },
154}
155
156pub fn resolve_terminals<'a>(
157    terminals: &'a Fmi3TerminalsAndIcons,
158    model: &'a Fmi3ModelDescription,
159) -> Result<ResolvedTerminals<'a>, TerminalResolutionError> {
160    let Some(root) = terminals.terminals.as_ref() else {
161        return Ok(ResolvedTerminals {
162            terminals: Vec::new(),
163        });
164    };
165
166    let lookup = build_variable_lookup(&model.model_variables);
167    let mut resolved = Vec::with_capacity(root.terminals.len());
168    for terminal in &root.terminals {
169        resolved.push(resolve_terminal(terminal, &lookup, terminal.name.as_str())?);
170    }
171    Ok(ResolvedTerminals {
172        terminals: resolved,
173    })
174}
175
176fn build_variable_lookup<'a>(
177    model_variables: &'a super::variable::ModelVariables,
178) -> HashMap<&'a str, &'a dyn AbstractVariableTrait> {
179    let mut lookup = HashMap::new();
180    for var in model_variables.iter_abstract() {
181        lookup.insert(var.name(), var);
182    }
183    lookup
184}
185
186fn resolve_terminal<'a>(
187    terminal: &'a Terminal,
188    lookup: &HashMap<&'a str, &'a dyn AbstractVariableTrait>,
189    terminal_path: &str,
190) -> Result<ResolvedTerminal<'a>, TerminalResolutionError> {
191    let mut members = Vec::with_capacity(terminal.terminal_member_variables.len());
192    for member in &terminal.terminal_member_variables {
193        let variable = *lookup.get(member.variable_name.as_str()).ok_or_else(|| {
194            TerminalResolutionError::MissingVariable {
195                terminal_path: terminal_path.to_string(),
196                variable_name: member.variable_name.clone(),
197            }
198        })?;
199        members.push(ResolvedTerminalMemberVariable { member, variable });
200    }
201
202    let mut stream_members = Vec::with_capacity(terminal.terminal_stream_member_variables.len());
203    for member in &terminal.terminal_stream_member_variables {
204        let in_stream_variable = *lookup
205            .get(member.in_stream_variable_name.as_str())
206            .ok_or_else(|| TerminalResolutionError::MissingVariable {
207                terminal_path: terminal_path.to_string(),
208                variable_name: member.in_stream_variable_name.clone(),
209            })?;
210        let out_stream_variable = *lookup
211            .get(member.out_stream_variable_name.as_str())
212            .ok_or_else(|| TerminalResolutionError::MissingVariable {
213                terminal_path: terminal_path.to_string(),
214                variable_name: member.out_stream_variable_name.clone(),
215            })?;
216        stream_members.push(ResolvedTerminalStreamMemberVariable {
217            member,
218            in_stream_variable,
219            out_stream_variable,
220        });
221    }
222
223    let mut terminals = Vec::with_capacity(terminal.terminals.len());
224    for child in &terminal.terminals {
225        let child_path = format!("{}/{}", terminal_path, child.name);
226        terminals.push(resolve_terminal(child, lookup, &child_path)?);
227    }
228
229    Ok(ResolvedTerminal {
230        terminal,
231        members,
232        stream_members,
233        terminals,
234    })
235}