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: String) -> (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, 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().clone();
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. Currently, the only practical use for this is
301    /// a placehodler when serializing and deserializing wave state.
302    Empty,
303    Cxxrtl(Box<Mutex<CxxrtlContainer>>),
304}
305
306impl WaveContainer {
307    #[must_use]
308    pub fn new_waveform(hierarchy: std::sync::Arc<wellen::Hierarchy>) -> Self {
309        WaveContainer::Wellen(Box::new(WellenContainer::new(hierarchy, None, None)))
310    }
311
312    #[must_use]
313    pub fn new_remote_waveform(
314        server_url: &str,
315        hierarchy: std::sync::Arc<wellen::Hierarchy>,
316        file_index: usize,
317    ) -> Self {
318        WaveContainer::Wellen(Box::new(WellenContainer::new(
319            hierarchy,
320            Some(server_url.to_string()),
321            Some(file_index),
322        )))
323    }
324
325    /// Creates a new empty wave container. Should only be used as a default for serde. If
326    /// no wave container is present, the `WaveData` should be None, rather than this being
327    /// Empty
328    #[must_use]
329    pub fn __new_empty() -> Self {
330        WaveContainer::Empty
331    }
332
333    // Perform tasks that are done on the main thread each frame
334    pub fn tick(&self) {
335        match self {
336            WaveContainer::Wellen(_) => {}
337            WaveContainer::Empty => {}
338            WaveContainer::Cxxrtl(c) => c.lock().unwrap().tick(),
339        }
340    }
341
342    #[must_use]
343    pub fn wants_anti_aliasing(&self) -> bool {
344        match self {
345            WaveContainer::Wellen(_) => true,
346            WaveContainer::Empty => true,
347            // FIXME: Once we do AA on the server side, we can set this to false
348            WaveContainer::Cxxrtl(_) => true,
349        }
350    }
351
352    /// Returns true if all requested signals have been loaded.
353    /// Used for testing to make sure the GUI is at its final state before taking a
354    /// snapshot.
355    #[must_use]
356    pub fn is_fully_loaded(&self) -> bool {
357        match self {
358            WaveContainer::Wellen(f) => f.is_fully_loaded(),
359            WaveContainer::Empty => true,
360            WaveContainer::Cxxrtl(_) => true,
361        }
362    }
363
364    /// Returns the full names of all variables in the design.
365    #[must_use]
366    pub fn variable_names(&self) -> Vec<String> {
367        match self {
368            WaveContainer::Wellen(f) => f.variable_names(),
369            WaveContainer::Empty => vec![],
370            // I don't know if we can do
371            WaveContainer::Cxxrtl(_) => vec![], // FIXME: List variable names
372        }
373    }
374
375    /// Return all variables (excluding parameters) in the whole design.
376    #[must_use]
377    pub fn variables(&self) -> Vec<VariableRef> {
378        match self {
379            WaveContainer::Wellen(f) => f.variables(),
380            WaveContainer::Empty => vec![],
381            WaveContainer::Cxxrtl(_) => vec![],
382        }
383    }
384
385    /// Return all variables (excluding parameters) in a scope.
386    #[must_use]
387    pub fn variables_in_scope(&self, scope: &ScopeRef) -> Vec<VariableRef> {
388        match self {
389            WaveContainer::Wellen(f) => f.variables_in_scope(scope),
390            WaveContainer::Empty => vec![],
391            WaveContainer::Cxxrtl(c) => c.lock().unwrap().variables_in_module(scope),
392        }
393    }
394
395    /// Return all parameters in a scope.
396    #[must_use]
397    pub fn parameters_in_scope(&self, scope: &ScopeRef) -> Vec<VariableRef> {
398        match self {
399            WaveContainer::Wellen(f) => f.parameters_in_scope(scope),
400            WaveContainer::Empty => vec![],
401            // No parameters in Cxxrtl
402            WaveContainer::Cxxrtl(_) => vec![],
403        }
404    }
405
406    /// Return true if there are no variables or parameters in the scope.
407    #[must_use]
408    pub fn no_variables_in_scope(&self, scope: &ScopeRef) -> bool {
409        match self {
410            WaveContainer::Wellen(f) => f.no_variables_in_scope(scope),
411            WaveContainer::Empty => true,
412            WaveContainer::Cxxrtl(c) => c.lock().unwrap().no_variables_in_module(scope),
413        }
414    }
415
416    /// Loads multiple variables at once. This is useful when we want to add multiple variables in one go.
417    pub fn load_variables<S: AsRef<VariableRef>, T: Iterator<Item = S>>(
418        &mut self,
419        variables: T,
420    ) -> Result<Option<LoadSignalsCmd>> {
421        match self {
422            WaveContainer::Wellen(f) => f.load_variables(variables),
423            WaveContainer::Empty => bail!("Cannot load variables from empty container."),
424            WaveContainer::Cxxrtl(c) => {
425                c.get_mut().unwrap().load_variables(variables);
426                Ok(None)
427            }
428        }
429    }
430    /// Load all the parameters in the design so that the value can be displayed.
431    pub fn load_parameters(&mut self) -> Result<Option<LoadSignalsCmd>> {
432        match self {
433            WaveContainer::Wellen(f) => f.load_all_params(),
434            WaveContainer::Empty => bail!("Cannot load parameters from empty container."),
435            WaveContainer::Cxxrtl(_) => {
436                // Cxxrtl does not deal with parameters
437                Ok(None)
438            }
439        }
440    }
441
442    /// Callback for when wellen signals have been loaded. Might lead to a new load variable
443    /// command since new variables might have been requested in the meantime.
444    pub fn on_signals_loaded(&mut self, res: LoadSignalsResult) -> Result<Option<LoadSignalsCmd>> {
445        match self {
446            WaveContainer::Wellen(f) => f.on_signals_loaded(res),
447            WaveContainer::Empty => {
448                bail!("on_load_signals should only be called with the wellen backend.")
449            }
450            WaveContainer::Cxxrtl(_) => {
451                bail!("on_load_signals should only be called with the wellen backend.")
452            }
453        }
454    }
455
456    pub fn variable_meta<'a>(&'a self, variable: &'a VariableRef) -> Result<VariableMeta> {
457        match self {
458            WaveContainer::Wellen(f) => f.variable_to_meta(variable),
459            WaveContainer::Empty => bail!("Getting meta from empty wave container"),
460            WaveContainer::Cxxrtl(c) => c.lock().unwrap().variable_meta(variable),
461        }
462    }
463
464    /// Query the value of the variable at a certain time step.
465    /// Returns `None` if we do not have any values for the variable.
466    /// That generally happens if the corresponding variable is still being loaded.
467    pub fn query_variable(
468        &self,
469        variable: &VariableRef,
470        time: &BigUint,
471    ) -> Result<Option<QueryResult>> {
472        match self {
473            WaveContainer::Wellen(f) => f.query_variable(variable, time),
474            WaveContainer::Empty => bail!("Querying variable from empty wave container"),
475            WaveContainer::Cxxrtl(c) => Ok(c.lock().unwrap().query_variable(variable, time)),
476        }
477    }
478
479    pub fn signal_accessor(&self, signal_id: SignalId) -> Result<SignalAccessor> {
480        match (self, signal_id) {
481            (WaveContainer::Wellen(f), SignalId::Wellen(signal_ref)) => {
482                Ok(SignalAccessor::Wellen(f.signal_accessor(signal_ref)?))
483            }
484            _ => bail!("Invalid signal accessor combination"),
485        }
486    }
487    /// Get the `SignalId` for a variable (canonical signal identity for cache keys)
488    pub fn signal_id(&self, variable: &VariableRef) -> Result<SignalId> {
489        match self {
490            WaveContainer::Wellen(f) => Ok(SignalId::Wellen(f.signal_ref(variable)?)),
491            WaveContainer::Empty => bail!("No signal data"),
492            WaveContainer::Cxxrtl(_) => bail!("Not supported for Cxxrtl yet"),
493        }
494    }
495
496    /// Check if a signal is already loaded (data available)
497    #[must_use]
498    pub fn is_signal_loaded(&self, signal_id: &SignalId) -> bool {
499        match (self, signal_id) {
500            (WaveContainer::Wellen(f), SignalId::Wellen(signal_ref)) => {
501                f.is_signal_loaded(*signal_ref)
502            }
503            _ => false,
504        }
505    }
506
507    /// Looks up the variable _by name_ and returns a new reference with an updated `id` if the variable is found.
508    #[must_use]
509    pub fn update_variable_ref(&self, variable: &VariableRef) -> Option<VariableRef> {
510        match self {
511            WaveContainer::Wellen(f) => f.update_variable_ref(variable),
512            WaveContainer::Empty => None,
513            WaveContainer::Cxxrtl(_) => None,
514        }
515    }
516
517    /// Returns the full names of all scopes in the design.
518    #[must_use]
519    pub fn scope_names(&self) -> Vec<String> {
520        match self {
521            WaveContainer::Wellen(f) => f.scope_names(),
522            WaveContainer::Empty => vec![],
523            WaveContainer::Cxxrtl(c) => c
524                .lock()
525                .unwrap()
526                .modules()
527                .iter()
528                .map(|m| m.strs().last().cloned().unwrap_or("root".to_string()))
529                .collect(),
530        }
531    }
532
533    /// Returns the full names of all array scopes in the design.
534    #[must_use]
535    pub fn array_names(&self) -> Vec<String> {
536        match self {
537            WaveContainer::Wellen(f) => f.array_scope_names(),
538            WaveContainer::Empty => vec![],
539            WaveContainer::Cxxrtl(_) => vec![],
540        }
541    }
542
543    #[must_use]
544    pub fn metadata(&self) -> MetaData {
545        match self {
546            WaveContainer::Wellen(f) => f.metadata(),
547            WaveContainer::Empty => MetaData {
548                date: None,
549                version: None,
550                timescale: TimeScale {
551                    unit: TimeUnit::None,
552                    multiplier: None,
553                },
554            },
555            WaveContainer::Cxxrtl(_) => {
556                MetaData {
557                    date: None,
558                    version: None,
559                    timescale: TimeScale {
560                        // Cxxrtl always uses FemtoSeconds
561                        unit: TimeUnit::FemtoSeconds,
562                        multiplier: None,
563                    },
564                }
565            }
566        }
567    }
568
569    #[must_use]
570    pub fn root_scopes(&self) -> Vec<ScopeRef> {
571        match self {
572            WaveContainer::Wellen(f) => f.root_scopes(),
573            WaveContainer::Empty => vec![],
574            WaveContainer::Cxxrtl(c) => c.lock().unwrap().root_modules(),
575        }
576    }
577
578    pub fn child_scopes(&self, scope: &ScopeRef) -> Result<Vec<ScopeRef>> {
579        match self {
580            WaveContainer::Wellen(f) => f.child_scopes(scope),
581            WaveContainer::Empty => bail!("Getting child modules from empty wave container"),
582            WaveContainer::Cxxrtl(c) => Ok(c.lock().unwrap().child_scopes(scope)),
583        }
584    }
585
586    #[must_use]
587    pub fn max_timestamp(&self) -> Option<BigUint> {
588        match self {
589            WaveContainer::Wellen(f) => f.max_timestamp(),
590            WaveContainer::Empty => None,
591            WaveContainer::Cxxrtl(c) => c
592                .lock()
593                .unwrap()
594                .max_displayed_timestamp()
595                .map(|t| t.as_femtoseconds()),
596        }
597    }
598
599    #[must_use]
600    pub fn scope_exists(&self, scope: &ScopeRef) -> bool {
601        match self {
602            WaveContainer::Wellen(f) => f.scope_exists(scope),
603            WaveContainer::Empty => false,
604            WaveContainer::Cxxrtl(c) => c.lock().unwrap().module_exists(scope),
605        }
606    }
607
608    #[must_use]
609    /// True if scope is a compound variable
610    pub fn scope_is_variable(&self, scope: &ScopeRef) -> bool {
611        match self {
612            WaveContainer::Wellen(f) => f.scope_is_variable(scope),
613            WaveContainer::Empty => false,
614            WaveContainer::Cxxrtl(_) => false, // TODO: Check if scope is variable
615        }
616    }
617
618    #[must_use]
619    /// True if scope is an array
620    pub fn scope_is_array(&self, scope: &ScopeRef) -> bool {
621        match self {
622            WaveContainer::Wellen(f) => f.scope_is_array(scope),
623            WaveContainer::Empty => false,
624            WaveContainer::Cxxrtl(_) => false, // TODO: Check if scope is array
625        }
626    }
627
628    /// Returns a human readable string with information about a scope.
629    /// The scope name itself should not be included, since it will be prepended automatically.
630    #[must_use]
631    pub fn get_scope_tooltip_data(&self, scope: &ScopeRef) -> String {
632        match self {
633            WaveContainer::Wellen(f) => f.get_scope_tooltip_data(scope),
634            WaveContainer::Empty => String::new(),
635            // FIXME: Tooltip
636            WaveContainer::Cxxrtl(_) => String::new(),
637        }
638    }
639
640    /// Returns the scope type for a given scope.
641    /// Returns `None` for backends that don't support scope types
642    #[must_use]
643    pub fn get_scope_type(&self, scope: &ScopeRef) -> Option<wellen::ScopeType> {
644        match self {
645            WaveContainer::Wellen(f) => f.get_scope_type(scope),
646            WaveContainer::Empty | WaveContainer::Cxxrtl(_) => None,
647        }
648    }
649
650    /// Returns the simulation status for this wave source if it exists. Wave sources which have no
651    /// simulation status should return None here, otherwise buttons for controlling simulation
652    /// will be shown
653    #[must_use]
654    pub fn simulation_status(&self) -> Option<SimulationStatus> {
655        match self {
656            WaveContainer::Wellen(_) => None,
657            WaveContainer::Empty => None,
658            WaveContainer::Cxxrtl(c) => c.lock().unwrap().simulation_status(),
659        }
660    }
661
662    /// If [`WaveContainer::simulation_status`] is `Some(SimulationStatus::Paused)`, attempt to unpause the
663    /// simulation otherwise does nothing
664    pub fn unpause_simulation(&self) {
665        match self {
666            WaveContainer::Wellen(_) => {}
667            WaveContainer::Empty => {}
668            WaveContainer::Cxxrtl(c) => c.lock().unwrap().unpause(),
669        }
670    }
671
672    /// See [`WaveContainer::unpause_simulation`]
673    pub fn pause_simulation(&self) {
674        match self {
675            WaveContainer::Wellen(_) => {}
676            WaveContainer::Empty => {}
677            WaveContainer::Cxxrtl(c) => c.lock().unwrap().pause(),
678        }
679    }
680
681    /// Called for `wellen` container, when the body of the waveform file has been parsed.
682    pub fn wellen_add_body(&mut self, body: BodyResult) -> Result<Option<LoadSignalsCmd>> {
683        match self {
684            WaveContainer::Wellen(inner) => inner.add_body(body),
685            _ => {
686                bail!("Should never call this function on a non wellen container!")
687            }
688        }
689    }
690
691    #[must_use]
692    pub fn body_loaded(&self) -> bool {
693        match self {
694            WaveContainer::Wellen(inner) => inner.body_loaded(),
695            WaveContainer::Empty => true,
696            WaveContainer::Cxxrtl(_) => true,
697        }
698    }
699
700    /// Returns true if this wave container supports analog rendering options in the GUI.
701    /// Currently only the wellen backend (VCD/FST/GHW) supports analog rendering.
702    #[must_use]
703    pub fn supports_analog(&self) -> bool {
704        matches!(self, WaveContainer::Wellen(_))
705    }
706}
707
708#[cfg(test)]
709mod tests {
710    use super::*;
711
712    #[test]
713    fn extract_index_with_valid_index() {
714        let (name, index) = extract_index("signal[5]".to_string());
715        assert_eq!(name, "signal");
716        assert_eq!(index, Some(5));
717    }
718
719    #[test]
720    fn extract_index_with_zero_index() {
721        let (name, index) = extract_index("data[0]".to_string());
722        assert_eq!(name, "data");
723        assert_eq!(index, Some(0));
724    }
725
726    #[test]
727    fn extract_index_with_negative_index() {
728        let (name, index) = extract_index("array[-1]".to_string());
729        assert_eq!(name, "array");
730        assert_eq!(index, Some(-1));
731    }
732
733    #[test]
734    fn extract_index_with_large_number() {
735        let (name, index) = extract_index("mem[999999]".to_string());
736        assert_eq!(name, "mem");
737        assert_eq!(index, Some(999999));
738    }
739
740    #[test]
741    fn extract_index_no_brackets() {
742        let (name, index) = extract_index("simple_signal".to_string());
743        assert_eq!(name, "simple_signal");
744        assert_eq!(index, None);
745    }
746
747    #[test]
748    fn extract_index_empty_brackets() {
749        let (name, index) = extract_index("signal[]".to_string());
750        assert_eq!(name, "signal[]");
751        assert_eq!(index, None);
752    }
753
754    #[test]
755    fn extract_index_non_numeric_index() {
756        let (name, index) = extract_index("signal[abc]".to_string());
757        assert_eq!(name, "signal[abc]");
758        assert_eq!(index, None);
759    }
760
761    #[test]
762    fn extract_index_only_opening_bracket() {
763        let (name, index) = extract_index("signal[5".to_string());
764        assert_eq!(name, "signal[5");
765        assert_eq!(index, None);
766    }
767
768    #[test]
769    fn extract_index_only_closing_bracket() {
770        let (name, index) = extract_index("signal5]".to_string());
771        assert_eq!(name, "signal5]");
772        assert_eq!(index, None);
773    }
774
775    #[test]
776    fn extract_index_multiple_brackets() {
777        let (name, index) = extract_index("array[3][5]".to_string());
778        assert_eq!(name, "array[3]");
779        assert_eq!(index, Some(5));
780    }
781
782    #[test]
783    fn extract_index_with_dot_notation() {
784        let (name, index) = extract_index("struct.field[10]".to_string());
785        assert_eq!(name, "struct.field");
786        assert_eq!(index, Some(10));
787    }
788
789    #[test]
790    fn extract_index_bracket_at_start() {
791        let (name, index) = extract_index("[5]signal".to_string());
792        assert_eq!(name, "[5]signal");
793        assert_eq!(index, None);
794    }
795
796    #[test]
797    fn extract_index_no_text() {
798        let (name, index) = extract_index("[5]".to_string());
799        assert_eq!(name, "[5]");
800        assert_eq!(index, None);
801    }
802}