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