Skip to main content

libsurfer/
wave_container.rs

1use std::sync::Mutex;
2
3use chrono::prelude::{DateTime, Utc};
4use eyre::{Result, bail};
5use num::BigUint;
6use serde::{Deserialize, Serialize};
7use surfer_translation_types::VariableValue;
8
9use crate::cxxrtl_container::CxxrtlContainer;
10use crate::time::{TimeScale, TimeUnit};
11use crate::wellen::{BodyResult, LoadSignalsCmd, LoadSignalsResult, WellenContainer};
12
13pub type FieldRef = surfer_translation_types::FieldRef<VarId, ScopeId>;
14pub type ScopeRef = surfer_translation_types::ScopeRef<ScopeId>;
15pub type VariableRef = surfer_translation_types::VariableRef<VarId, ScopeId>;
16pub type VariableMeta = surfer_translation_types::VariableMeta<VarId, ScopeId>;
17
18/// Cache key for analog signal data: (`signal_id`, `translator_name`)
19pub type AnalogCacheKey = (SignalId, String);
20
21#[derive(Debug, Clone)]
22pub enum SimulationStatus {
23    Paused,
24    Running,
25    Finished,
26}
27
28pub struct MetaData {
29    pub date: Option<DateTime<Utc>>,
30    pub version: Option<String>,
31    pub timescale: TimeScale,
32}
33
34/// A backend-specific, numeric reference for fast access to the associated scope.
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
36pub enum ScopeId {
37    #[default]
38    None,
39    Wellen(wellen::ScopeRef),
40}
41
42/// A backend-specific, numeric reference for fast access to the associated variable.
43#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
44pub enum VarId {
45    #[default]
46    None,
47    Wellen(wellen::VarRef),
48}
49
50/// A backend-specific, numeric reference for fast access to the associated signal data.
51/// Used as cache key for signal data lookups.
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub enum SignalId {
54    #[default]
55    None,
56    Wellen(wellen::SignalRef),
57}
58
59/// Backend-agnostic enum for accessing signal data.
60/// Variants provide iteration over signal changes.
61pub enum SignalAccessor {
62    Wellen(crate::wellen::WellenSignalAccessor),
63    // Future: Cxxrtl(CxxrtlSignalAccessor),
64}
65
66impl SignalAccessor {
67    /// Iterator over signal changes as (`time_u64`, value) pairs
68    #[must_use]
69    pub fn iter_changes(&self) -> Box<dyn Iterator<Item = (u64, VariableValue)> + '_> {
70        match self {
71            SignalAccessor::Wellen(accessor) => accessor.iter_changes(),
72        }
73    }
74}
75
76#[derive(Debug, Default)]
77pub struct QueryResult {
78    pub current: Option<(BigUint, VariableValue)>,
79    pub next: Option<BigUint>,
80}
81
82#[local_impl::local_impl]
83impl ScopeRefExt for ScopeRef {
84    fn empty() -> Self {
85        Self {
86            strs: vec![],
87            id: ScopeId::default(),
88        }
89    }
90
91    fn from_strs<S: ToString>(s: &[S]) -> Self {
92        Self::from_strs_with_id(s, ScopeId::default())
93    }
94
95    fn from_strs_with_id(s: &[impl ToString], id: ScopeId) -> Self {
96        let strs = s.iter().map(ToString::to_string).collect();
97        Self { strs, id }
98    }
99
100    /// Creates a `ScopeRef` from a string with each scope separated by `.`
101    fn from_hierarchy_string(s: &str) -> Self {
102        let strs = s.split('.').map(ToString::to_string).collect();
103        let id = ScopeId::default();
104        Self { strs, id }
105    }
106
107    fn with_subscope(&self, subscope: String, id: ScopeId) -> Self {
108        let mut result = self.clone();
109        result.strs.push(subscope);
110        // the result refers to a different scope, which we do not know the ID of
111        result.id = id;
112        result
113    }
114
115    fn name(&self) -> String {
116        self.strs.last().cloned().unwrap_or_default()
117    }
118
119    fn full_name(&self) -> String {
120        self.strs.join(".")
121    }
122
123    fn strs(&self) -> &[String] {
124        &self.strs
125    }
126
127    fn with_id(&self, id: ScopeId) -> Self {
128        let mut out = self.clone();
129        out.id = id;
130        out
131    }
132
133    fn cxxrtl_repr(&self) -> String {
134        self.strs.join(" ")
135    }
136
137    fn has_empty_strs(&self) -> bool {
138        self.strs.is_empty()
139    }
140}
141
142fn extract_index(s: &str) -> (String, Option<i64>) {
143    if let Some(start_idx) = s.rfind('[')
144        && start_idx > 0
145        && s.ends_with(']')
146    {
147        let index_str = &s[start_idx + 1..s.len() - 1];
148        if let Ok(index) = index_str.parse::<i64>() {
149            let name = s[..start_idx].to_string();
150            return (name, Some(index));
151        }
152    }
153    (s.to_string(), None)
154}
155
156#[local_impl::local_impl]
157impl VariableRefExt for VariableRef {
158    fn new(path: ScopeRef, name: String) -> Self {
159        Self::new_with_id_and_index(path, name, VarId::default(), None)
160    }
161
162    fn new_with_id_and_index(path: ScopeRef, name: String, id: VarId, index: Option<i64>) -> Self {
163        let (name, index) = if index.is_none() {
164            extract_index(&name)
165        } else {
166            (name, index)
167        };
168        Self {
169            path,
170            name,
171            id,
172            index,
173        }
174    }
175
176    fn from_hierarchy_string(s: &str) -> Self {
177        let components = s.split('.').map(ToString::to_string).collect::<Vec<_>>();
178
179        if components.is_empty() {
180            Self {
181                path: ScopeRef::empty(),
182                name: String::new(),
183                id: VarId::default(),
184                index: None,
185            }
186        } else {
187            let name = components.last().unwrap();
188            let (name, index) = extract_index(name);
189            Self {
190                path: ScopeRef::from_strs(&components[..(components.len()) - 1]),
191                name,
192                id: VarId::default(),
193                index,
194            }
195        }
196    }
197
198    fn from_hierarchy_string_with_id(s: &str, id: VarId) -> Self {
199        let components = s
200            .split('.')
201            .map(std::string::ToString::to_string)
202            .collect::<Vec<_>>();
203
204        if components.is_empty() {
205            Self {
206                path: ScopeRef::empty(),
207                name: String::new(),
208                id,
209                index: None,
210            }
211        } else {
212            Self {
213                path: ScopeRef::from_strs(&components[..(components.len()) - 1]),
214                name: components.last().unwrap().clone(),
215                id,
216                index: None,
217            }
218        }
219    }
220
221    /// A human readable full path to the variable, including scope, but no index
222    fn full_path_string_no_index(&self) -> String {
223        if self.path.has_empty_strs() {
224            self.name.clone()
225        } else {
226            format!("{}.{}", self.path, self.name)
227        }
228    }
229
230    /// A human readable full path to the variable, including scope and index if present
231    fn full_path_string(&self) -> String {
232        if let Some(index) = self.index {
233            format!("{}.{}[{}]", self.path, self.name, index)
234        } else {
235            self.full_path_string_no_index()
236        }
237    }
238
239    /// The full path as a vector of strings, including scope and variable name
240    fn full_path(&self) -> Vec<String> {
241        self.path
242            .strs()
243            .iter()
244            .cloned()
245            .chain([self.name.clone()])
246            .collect()
247    }
248
249    /// The full path as a vector of strings, including scope, variable name and index if present
250    fn full_path_with_index(&self) -> Vec<String> {
251        if let Some(index) = self.index {
252            self.path
253                .strs()
254                .iter()
255                .cloned()
256                .chain([self.name.clone(), format!("[{index}]")])
257                .collect()
258        } else {
259            self.full_path()
260        }
261    }
262
263    fn from_strs(s: &[&str]) -> Self {
264        Self {
265            path: ScopeRef::from_strs(&s[..(s.len() - 1)]),
266            name: (*s.last().expect("from_strs called with an empty string")).to_string(),
267            id: VarId::default(),
268            index: None,
269        }
270    }
271
272    fn clear_id(&mut self) {
273        self.id = VarId::default();
274    }
275
276    fn cxxrtl_repr(&self) -> String {
277        self.full_path().join(" ")
278    }
279}
280
281#[local_impl::local_impl]
282impl FieldRefExt for FieldRef {
283    fn without_fields(root: VariableRef) -> Self {
284        Self {
285            root,
286            field: vec![],
287        }
288    }
289
290    fn from_strs(root: &[&str], field: &[&str]) -> Self {
291        Self {
292            root: VariableRef::from_strs(root),
293            field: field.iter().map(ToString::to_string).collect(),
294        }
295    }
296}
297
298pub enum WaveContainer {
299    Wellen(Box<WellenContainer>),
300    /// A wave container that contains nothing.
301    ///
302    /// Currently, the only practical use for this is a placeholder when
303    /// serializing and deserializing wave state.
304    Empty,
305    Cxxrtl(Box<Mutex<CxxrtlContainer>>),
306}
307
308impl WaveContainer {
309    #[must_use]
310    pub fn new_waveform(hierarchy: std::sync::Arc<wellen::Hierarchy>) -> Self {
311        WaveContainer::Wellen(Box::new(WellenContainer::new(hierarchy, None, None)))
312    }
313
314    #[must_use]
315    pub fn new_remote_waveform(
316        server_url: &str,
317        hierarchy: std::sync::Arc<wellen::Hierarchy>,
318        file_index: usize,
319    ) -> Self {
320        WaveContainer::Wellen(Box::new(WellenContainer::new(
321            hierarchy,
322            Some(server_url.to_string()),
323            Some(file_index),
324        )))
325    }
326
327    /// Creates a new empty wave container.
328    ///
329    /// Should only be used as a default for serde. If no wave container is present,
330    /// the `WaveData` should be None, rather than this being Empty
331    #[must_use]
332    pub fn __new_empty() -> Self {
333        WaveContainer::Empty
334    }
335
336    // Perform tasks that are done on the main thread each frame
337    pub fn tick(&self) {
338        match self {
339            WaveContainer::Wellen(_) => {}
340            WaveContainer::Empty => {}
341            WaveContainer::Cxxrtl(c) => c.lock().unwrap().tick(),
342        }
343    }
344
345    #[must_use]
346    pub fn wants_anti_aliasing(&self) -> bool {
347        match self {
348            WaveContainer::Wellen(_) => true,
349            WaveContainer::Empty => true,
350            // FIXME: Once we do AA on the server side, we can set this to false
351            WaveContainer::Cxxrtl(_) => true,
352        }
353    }
354
355    /// Returns true if all requested signals have been loaded.
356    /// Used for testing to make sure the GUI is at its final state before taking a
357    /// snapshot.
358    #[must_use]
359    pub fn is_fully_loaded(&self) -> bool {
360        match self {
361            WaveContainer::Wellen(f) => f.is_fully_loaded(),
362            WaveContainer::Empty => true,
363            WaveContainer::Cxxrtl(_) => true,
364        }
365    }
366
367    /// Returns the full names of all variables in the design.
368    #[must_use]
369    pub fn variable_names(&self) -> Vec<String> {
370        match self {
371            WaveContainer::Wellen(f) => f.variable_names(),
372            WaveContainer::Empty => vec![],
373            // I don't know if we can do
374            WaveContainer::Cxxrtl(_) => vec![], // FIXME: List variable names
375        }
376    }
377
378    /// Return all variables (excluding parameters) in the whole design.
379    #[must_use]
380    pub fn variables(&self) -> Vec<VariableRef> {
381        match self {
382            WaveContainer::Wellen(f) => f.variables(),
383            WaveContainer::Empty => vec![],
384            WaveContainer::Cxxrtl(_) => vec![],
385        }
386    }
387
388    /// Return all variables (excluding parameters) in a scope.
389    #[must_use]
390    pub fn variables_in_scope(&self, scope: &ScopeRef) -> Vec<VariableRef> {
391        match self {
392            WaveContainer::Wellen(f) => f.variables_in_scope(scope),
393            WaveContainer::Empty => vec![],
394            WaveContainer::Cxxrtl(c) => c.lock().unwrap().variables_in_module(scope),
395        }
396    }
397
398    /// Return all parameters in a scope.
399    #[must_use]
400    pub fn parameters_in_scope(&self, scope: &ScopeRef) -> Vec<VariableRef> {
401        match self {
402            WaveContainer::Wellen(f) => f.parameters_in_scope(scope),
403            WaveContainer::Empty => vec![],
404            // No parameters in Cxxrtl
405            WaveContainer::Cxxrtl(_) => vec![],
406        }
407    }
408
409    /// Return true if there are no variables or parameters in the scope.
410    #[must_use]
411    pub fn no_variables_in_scope(&self, scope: &ScopeRef) -> bool {
412        match self {
413            WaveContainer::Wellen(f) => f.no_variables_in_scope(scope),
414            WaveContainer::Empty => true,
415            WaveContainer::Cxxrtl(c) => c.lock().unwrap().no_variables_in_module(scope),
416        }
417    }
418
419    /// Loads multiple variables at once.
420    ///
421    /// This is useful when we want to add multiple variables in one go.
422    pub fn load_variables<S: AsRef<VariableRef>, T: Iterator<Item = S>>(
423        &mut self,
424        variables: T,
425    ) -> Result<Option<LoadSignalsCmd>> {
426        match self {
427            WaveContainer::Wellen(f) => f.load_variables(variables),
428            WaveContainer::Empty => {
429                bail!("Cannot load variables from empty container.");
430            }
431            WaveContainer::Cxxrtl(c) => {
432                c.get_mut().unwrap().load_variables(variables);
433                Ok(None)
434            }
435        }
436    }
437    /// Load all the parameters in the design so that the value can be displayed.
438    pub fn load_parameters(&mut self) -> Result<Option<LoadSignalsCmd>> {
439        match self {
440            WaveContainer::Wellen(f) => f.load_all_params(),
441            WaveContainer::Empty => {
442                bail!("Cannot load parameters from empty container.");
443            }
444            WaveContainer::Cxxrtl(_) => {
445                // Cxxrtl does not deal with parameters
446                Ok(None)
447            }
448        }
449    }
450
451    /// Callback for when wellen signals have been loaded.
452    ///
453    /// Might lead to a new load variable command since new variables might
454    /// have been requested in the meantime.
455    pub fn on_signals_loaded(&mut self, res: LoadSignalsResult) -> Result<Option<LoadSignalsCmd>> {
456        match self {
457            WaveContainer::Wellen(f) => f.on_signals_loaded(res),
458            WaveContainer::Empty => {
459                bail!("on_load_signals should only be called with the wellen backend.");
460            }
461            WaveContainer::Cxxrtl(_) => {
462                bail!("on_load_signals should only be called with the wellen backend.");
463            }
464        }
465    }
466
467    pub fn variable_meta<'a>(&'a self, variable: &'a VariableRef) -> Result<VariableMeta> {
468        match self {
469            WaveContainer::Wellen(f) => f.variable_to_meta(variable),
470            WaveContainer::Empty => {
471                bail!("Getting meta from empty wave container");
472            }
473            WaveContainer::Cxxrtl(c) => c.lock().unwrap().variable_meta(variable),
474        }
475    }
476
477    /// Query the value of the variable at a certain time step.
478    /// Returns `None` if we do not have any values for the variable.
479    /// That generally happens if the corresponding variable is still being loaded.
480    pub fn query_variable(
481        &self,
482        variable: &VariableRef,
483        time: &BigUint,
484    ) -> Result<Option<QueryResult>> {
485        match self {
486            WaveContainer::Wellen(f) => f.query_variable(variable, time),
487            WaveContainer::Empty => {
488                bail!("Querying variable from empty wave container");
489            }
490            WaveContainer::Cxxrtl(c) => Ok(c.lock().unwrap().query_variable(variable, time)),
491        }
492    }
493
494    pub fn signal_accessor(&self, signal_id: SignalId) -> Result<SignalAccessor> {
495        match (self, signal_id) {
496            (WaveContainer::Wellen(f), SignalId::Wellen(signal_ref)) => {
497                Ok(SignalAccessor::Wellen(f.signal_accessor(signal_ref)?))
498            }
499            _ => {
500                bail!("Invalid signal accessor combination");
501            }
502        }
503    }
504    /// Get the `SignalId` for a variable (canonical signal identity for cache keys)
505    pub fn signal_id(&self, variable: &VariableRef) -> Result<SignalId> {
506        match self {
507            WaveContainer::Wellen(f) => Ok(SignalId::Wellen(f.signal_ref(variable)?)),
508            WaveContainer::Empty => {
509                bail!("No signal data");
510            }
511            WaveContainer::Cxxrtl(_) => {
512                bail!("Not supported for Cxxrtl yet");
513            }
514        }
515    }
516
517    /// Check if a signal is already loaded (data available)
518    #[must_use]
519    pub fn is_signal_loaded(&self, signal_id: &SignalId) -> bool {
520        match (self, signal_id) {
521            (WaveContainer::Wellen(f), SignalId::Wellen(signal_ref)) => {
522                f.is_signal_loaded(*signal_ref)
523            }
524            _ => false,
525        }
526    }
527
528    /// Looks up the variable _by name_ and returns a new reference with an updated `id` if the variable is found.
529    #[must_use]
530    pub fn update_variable_ref(&self, variable: &VariableRef) -> Option<VariableRef> {
531        match self {
532            WaveContainer::Wellen(f) => f.update_variable_ref(variable),
533            WaveContainer::Empty => None,
534            WaveContainer::Cxxrtl(_) => None,
535        }
536    }
537
538    /// Returns the full names of all scopes in the design.
539    #[must_use]
540    pub fn scope_names(&self) -> Vec<String> {
541        match self {
542            WaveContainer::Wellen(f) => f.scope_names(),
543            WaveContainer::Empty => vec![],
544            WaveContainer::Cxxrtl(c) => c
545                .lock()
546                .unwrap()
547                .modules()
548                .iter()
549                .map(|m| m.strs().last().cloned().unwrap_or("root".to_string()))
550                .collect(),
551        }
552    }
553
554    /// Returns the full names of all array scopes in the design.
555    #[must_use]
556    pub fn array_names(&self) -> Vec<String> {
557        match self {
558            WaveContainer::Wellen(f) => f.array_scope_names(),
559            WaveContainer::Empty => vec![],
560            WaveContainer::Cxxrtl(_) => vec![],
561        }
562    }
563
564    #[must_use]
565    pub fn metadata(&self) -> MetaData {
566        match self {
567            WaveContainer::Wellen(f) => f.metadata(),
568            WaveContainer::Empty => MetaData {
569                date: None,
570                version: None,
571                timescale: TimeScale {
572                    unit: TimeUnit::None,
573                    multiplier: None,
574                },
575            },
576            WaveContainer::Cxxrtl(_) => {
577                MetaData {
578                    date: None,
579                    version: None,
580                    timescale: TimeScale {
581                        // Cxxrtl always uses FemtoSeconds
582                        unit: TimeUnit::FemtoSeconds,
583                        multiplier: None,
584                    },
585                }
586            }
587        }
588    }
589
590    #[must_use]
591    pub fn root_scopes(&self) -> Vec<ScopeRef> {
592        match self {
593            WaveContainer::Wellen(f) => f.root_scopes(),
594            WaveContainer::Empty => vec![],
595            WaveContainer::Cxxrtl(c) => c.lock().unwrap().root_modules(),
596        }
597    }
598
599    pub fn child_scopes(&self, scope: &ScopeRef) -> Result<Vec<ScopeRef>> {
600        match self {
601            WaveContainer::Wellen(f) => f.child_scopes(scope),
602            WaveContainer::Empty => {
603                bail!("Getting child modules from empty wave container");
604            }
605            WaveContainer::Cxxrtl(c) => Ok(c.lock().unwrap().child_scopes(scope)),
606        }
607    }
608
609    #[must_use]
610    pub fn max_timestamp(&self) -> Option<BigUint> {
611        match self {
612            WaveContainer::Wellen(f) => f.max_timestamp(),
613            WaveContainer::Empty => None,
614            WaveContainer::Cxxrtl(c) => c
615                .lock()
616                .unwrap()
617                .max_displayed_timestamp()
618                .map(|t| t.as_femtoseconds()),
619        }
620    }
621    #[must_use]
622    pub fn min_timestamp(&self) -> Option<BigUint> {
623        match self {
624            WaveContainer::Wellen(f) => f.min_timestamp(),
625            WaveContainer::Empty => None,
626            WaveContainer::Cxxrtl(_) => None,
627        }
628    }
629
630    #[must_use]
631    pub fn scope_exists(&self, scope: &ScopeRef) -> bool {
632        match self {
633            WaveContainer::Wellen(f) => f.scope_exists(scope),
634            WaveContainer::Empty => false,
635            WaveContainer::Cxxrtl(c) => c.lock().unwrap().module_exists(scope),
636        }
637    }
638
639    #[must_use]
640    /// True if scope is a compound variable
641    pub fn scope_is_variable(&self, scope: &ScopeRef) -> bool {
642        match self {
643            WaveContainer::Wellen(f) => f.scope_is_variable(scope),
644            WaveContainer::Empty => false,
645            WaveContainer::Cxxrtl(_) => false, // TODO: Check if scope is variable
646        }
647    }
648
649    #[must_use]
650    /// True if scope is an array
651    pub fn scope_is_array(&self, scope: &ScopeRef) -> bool {
652        match self {
653            WaveContainer::Wellen(f) => f.scope_is_array(scope),
654            WaveContainer::Empty => false,
655            WaveContainer::Cxxrtl(_) => false, // TODO: Check if scope is array
656        }
657    }
658
659    /// Returns a human readable string with information about a scope.
660    /// The scope name itself should not be included, since it will be prepended automatically.
661    #[must_use]
662    pub fn get_scope_tooltip_data(&self, scope: &ScopeRef) -> String {
663        match self {
664            WaveContainer::Wellen(f) => f.get_scope_tooltip_data(scope),
665            WaveContainer::Empty => String::new(),
666            // FIXME: Tooltip
667            WaveContainer::Cxxrtl(_) => String::new(),
668        }
669    }
670
671    /// Returns the scope type for a given scope.
672    /// Returns `None` for backends that don't support scope types
673    #[must_use]
674    pub fn get_scope_type(&self, scope: &ScopeRef) -> Option<wellen::ScopeType> {
675        match self {
676            WaveContainer::Wellen(f) => f.get_scope_type(scope),
677            WaveContainer::Empty | WaveContainer::Cxxrtl(_) => None,
678        }
679    }
680
681    /// Returns the simulation status for this wave source if it exists.
682    ///
683    /// Wave sources which have no simulation status should return None here,
684    /// otherwise buttons for controlling simulation will be shown
685    #[must_use]
686    pub fn simulation_status(&self) -> Option<SimulationStatus> {
687        match self {
688            WaveContainer::Wellen(_) => None,
689            WaveContainer::Empty => None,
690            WaveContainer::Cxxrtl(c) => c.lock().unwrap().simulation_status(),
691        }
692    }
693
694    /// If [`WaveContainer::simulation_status`] is `Some(SimulationStatus::Paused)`, attempt to unpause the
695    /// simulation otherwise does nothing
696    pub fn unpause_simulation(&self) {
697        match self {
698            WaveContainer::Wellen(_) => {}
699            WaveContainer::Empty => {}
700            WaveContainer::Cxxrtl(c) => c.lock().unwrap().unpause(),
701        }
702    }
703
704    /// See [`WaveContainer::unpause_simulation`]
705    pub fn pause_simulation(&self) {
706        match self {
707            WaveContainer::Wellen(_) => {}
708            WaveContainer::Empty => {}
709            WaveContainer::Cxxrtl(c) => c.lock().unwrap().pause(),
710        }
711    }
712
713    /// Called for `wellen` container, when the body of the waveform file has been parsed.
714    pub fn wellen_add_body(&mut self, body: BodyResult) -> Result<Option<LoadSignalsCmd>> {
715        match self {
716            WaveContainer::Wellen(inner) => inner.add_body(body),
717            _ => {
718                bail!("Should never call this function on a non wellen container!");
719            }
720        }
721    }
722
723    #[must_use]
724    pub fn body_loaded(&self) -> bool {
725        match self {
726            WaveContainer::Wellen(inner) => inner.body_loaded(),
727            WaveContainer::Empty => true,
728            WaveContainer::Cxxrtl(_) => true,
729        }
730    }
731
732    /// Returns true if this wave container supports analog rendering options in the GUI.
733    /// Currently only the wellen backend (VCD/FST/GHW) supports analog rendering.
734    #[must_use]
735    pub fn supports_analog(&self) -> bool {
736        matches!(self, WaveContainer::Wellen(_))
737    }
738}
739
740#[cfg(test)]
741mod tests {
742    use super::*;
743
744    #[test]
745    fn extract_index_with_valid_index() {
746        let (name, index) = extract_index("signal[5]");
747        assert_eq!(name, "signal");
748        assert_eq!(index, Some(5));
749    }
750
751    #[test]
752    fn extract_index_with_zero_index() {
753        let (name, index) = extract_index("data[0]");
754        assert_eq!(name, "data");
755        assert_eq!(index, Some(0));
756    }
757
758    #[test]
759    fn extract_index_with_negative_index() {
760        let (name, index) = extract_index("array[-1]");
761        assert_eq!(name, "array");
762        assert_eq!(index, Some(-1));
763    }
764
765    #[test]
766    fn extract_index_with_large_number() {
767        let (name, index) = extract_index("mem[999999]");
768        assert_eq!(name, "mem");
769        assert_eq!(index, Some(999999));
770    }
771
772    #[test]
773    fn extract_index_no_brackets() {
774        let (name, index) = extract_index("simple_signal");
775        assert_eq!(name, "simple_signal");
776        assert_eq!(index, None);
777    }
778
779    #[test]
780    fn extract_index_empty_brackets() {
781        let (name, index) = extract_index("signal[]");
782        assert_eq!(name, "signal[]");
783        assert_eq!(index, None);
784    }
785
786    #[test]
787    fn extract_index_non_numeric_index() {
788        let (name, index) = extract_index("signal[abc]");
789        assert_eq!(name, "signal[abc]");
790        assert_eq!(index, None);
791    }
792
793    #[test]
794    fn extract_index_only_opening_bracket() {
795        let (name, index) = extract_index("signal[5");
796        assert_eq!(name, "signal[5");
797        assert_eq!(index, None);
798    }
799
800    #[test]
801    fn extract_index_only_closing_bracket() {
802        let (name, index) = extract_index("signal5]");
803        assert_eq!(name, "signal5]");
804        assert_eq!(index, None);
805    }
806
807    #[test]
808    fn extract_index_multiple_brackets() {
809        let (name, index) = extract_index("array[3][5]");
810        assert_eq!(name, "array[3]");
811        assert_eq!(index, Some(5));
812    }
813
814    #[test]
815    fn extract_index_with_dot_notation() {
816        let (name, index) = extract_index("struct.field[10]");
817        assert_eq!(name, "struct.field");
818        assert_eq!(index, Some(10));
819    }
820
821    #[test]
822    fn extract_index_bracket_at_start() {
823        let (name, index) = extract_index("[5]signal");
824        assert_eq!(name, "[5]signal");
825        assert_eq!(index, None);
826    }
827
828    #[test]
829    fn extract_index_no_text() {
830        let (name, index) = extract_index("[5]");
831        assert_eq!(name, "[5]");
832        assert_eq!(index, None);
833    }
834}