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