Skip to main content

perl_dap_value/
lib.rs

1//! Shared Perl value model for DAP parser and renderer crates.
2
3#![deny(unsafe_code)]
4
5use serde::{Deserialize, Serialize};
6
7/// Represents a Perl value in the debugger context.
8///
9/// This enum models the different types of values that can be inspected
10/// during a Perl debugging session.
11#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
12pub enum PerlValue {
13    /// Undefined value (Perl's `undef`)
14    #[default]
15    Undef,
16
17    /// Scalar value (string representation)
18    Scalar(String),
19
20    /// Numeric scalar value
21    Number(f64),
22
23    /// Integer scalar value
24    Integer(i64),
25
26    /// Array value with elements
27    Array(Vec<PerlValue>),
28
29    /// Hash value with key-value pairs
30    Hash(Vec<(String, PerlValue)>),
31
32    /// Reference to another value
33    Reference(Box<PerlValue>),
34
35    /// Blessed reference (object)
36    Object {
37        /// The package/class name
38        class: String,
39        /// The underlying value
40        value: Box<PerlValue>,
41    },
42
43    /// Code reference (subroutine)
44    Code {
45        /// Optional name if it's a named subroutine
46        name: Option<String>,
47    },
48
49    /// Glob (typeglob)
50    Glob(String),
51
52    /// Regular expression (compiled pattern)
53    Regex(String),
54
55    /// Tied variable (magic)
56    Tied {
57        /// The tie class
58        class: String,
59        /// The underlying value if available
60        value: Option<Box<PerlValue>>,
61    },
62
63    /// Truncated value (for large data structures)
64    Truncated {
65        /// Brief description of the truncated value
66        summary: String,
67        /// Total count of elements if applicable
68        total_count: Option<usize>,
69    },
70
71    /// Error during value inspection
72    Error(String),
73}
74
75impl PerlValue {
76    /// Returns true if this value can be expanded (has children).
77    #[must_use]
78    pub fn is_expandable(&self) -> bool {
79        matches!(
80            self,
81            Self::Array(_)
82                | Self::Hash(_)
83                | Self::Reference(_)
84                | Self::Object { .. }
85                | Self::Tied { .. }
86        )
87    }
88
89    /// Returns the type name for this value.
90    #[must_use]
91    pub fn type_name(&self) -> &'static str {
92        match self {
93            Self::Undef => "undef",
94            Self::Scalar(_) | Self::Number(_) | Self::Integer(_) => "SCALAR",
95            Self::Array(_) => "ARRAY",
96            Self::Hash(_) => "HASH",
97            Self::Reference(_) => "REF",
98            Self::Object { .. } => "OBJECT",
99            Self::Code { .. } => "CODE",
100            Self::Glob(_) => "GLOB",
101            Self::Regex(_) => "Regexp",
102            Self::Tied { .. } => "TIED",
103            Self::Truncated { .. } => "...",
104            Self::Error(_) => "ERROR",
105        }
106    }
107
108    /// Returns the number of child elements if applicable.
109    #[must_use]
110    pub fn child_count(&self) -> Option<usize> {
111        match self {
112            Self::Array(elements) => Some(elements.len()),
113            Self::Hash(pairs) => Some(pairs.len()),
114            Self::Truncated { total_count, .. } => *total_count,
115            _ => None,
116        }
117    }
118
119    /// Creates a scalar value from a string.
120    #[must_use]
121    pub fn scalar(s: impl Into<String>) -> Self {
122        Self::Scalar(s.into())
123    }
124
125    /// Creates an array value from elements.
126    #[must_use]
127    pub fn array(elements: Vec<PerlValue>) -> Self {
128        Self::Array(elements)
129    }
130
131    /// Creates a hash value from key-value pairs.
132    #[must_use]
133    pub fn hash(pairs: Vec<(String, PerlValue)>) -> Self {
134        Self::Hash(pairs)
135    }
136
137    /// Creates a reference to another value.
138    #[must_use]
139    pub fn reference(value: PerlValue) -> Self {
140        Self::Reference(Box::new(value))
141    }
142
143    /// Creates an object (blessed reference).
144    #[must_use]
145    pub fn object(class: impl Into<String>, value: PerlValue) -> Self {
146        Self::Object { class: class.into(), value: Box::new(value) }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::PerlValue;
153
154    #[test]
155    fn perl_value_is_expandable() {
156        assert!(!PerlValue::Undef.is_expandable());
157        assert!(!PerlValue::Scalar("test".to_string()).is_expandable());
158        assert!(PerlValue::Array(vec![]).is_expandable());
159        assert!(PerlValue::Hash(vec![]).is_expandable());
160        assert!(PerlValue::Reference(Box::new(PerlValue::Undef)).is_expandable());
161    }
162
163    #[test]
164    fn perl_value_type_name() {
165        assert_eq!(PerlValue::Undef.type_name(), "undef");
166        assert_eq!(PerlValue::Scalar("test".to_string()).type_name(), "SCALAR");
167        assert_eq!(PerlValue::Array(vec![]).type_name(), "ARRAY");
168        assert_eq!(PerlValue::Hash(vec![]).type_name(), "HASH");
169    }
170
171    #[test]
172    fn perl_value_child_count() {
173        assert_eq!(PerlValue::Undef.child_count(), None);
174        assert_eq!(
175            PerlValue::Array(vec![PerlValue::Undef, PerlValue::Undef]).child_count(),
176            Some(2)
177        );
178        assert_eq!(
179            PerlValue::Hash(vec![("key".to_string(), PerlValue::Undef)]).child_count(),
180            Some(1)
181        );
182    }
183
184    #[test]
185    fn perl_value_constructors() {
186        let scalar = PerlValue::scalar("hello");
187        assert!(matches!(scalar, PerlValue::Scalar(s) if s == "hello"));
188
189        let array = PerlValue::array(vec![PerlValue::Integer(1), PerlValue::Integer(2)]);
190        assert!(matches!(array, PerlValue::Array(a) if a.len() == 2));
191
192        let hash = PerlValue::hash(vec![("key".to_string(), PerlValue::scalar("value"))]);
193        assert!(matches!(hash, PerlValue::Hash(h) if h.len() == 1));
194
195        let reference = PerlValue::reference(PerlValue::Integer(42));
196        assert!(matches!(reference, PerlValue::Reference(_)));
197
198        let object = PerlValue::object("MyClass", PerlValue::Hash(vec![]));
199        assert!(matches!(object, PerlValue::Object { class, .. } if class == "MyClass"));
200    }
201
202    #[test]
203    fn is_expandable_all_variants() {
204        // Non-expandable
205        assert!(!PerlValue::Undef.is_expandable());
206        assert!(!PerlValue::Scalar("test".into()).is_expandable());
207        assert!(!PerlValue::Number(3.125).is_expandable());
208        assert!(!PerlValue::Integer(42).is_expandable());
209        assert!(!PerlValue::Code { name: None }.is_expandable());
210        assert!(!PerlValue::Code { name: Some("foo".into()) }.is_expandable());
211        assert!(!PerlValue::Glob("*main::STDOUT".into()).is_expandable());
212        assert!(!PerlValue::Regex("^foo$".into()).is_expandable());
213        assert!(
214            !PerlValue::Truncated { summary: "...".into(), total_count: Some(100) }.is_expandable()
215        );
216        assert!(!PerlValue::Error("oops".into()).is_expandable());
217
218        // Expandable
219        assert!(PerlValue::Array(vec![]).is_expandable());
220        assert!(PerlValue::Hash(vec![]).is_expandable());
221        assert!(PerlValue::Reference(Box::new(PerlValue::Undef)).is_expandable());
222        assert!(
223            PerlValue::Object { class: "Foo".into(), value: Box::new(PerlValue::Hash(vec![])) }
224                .is_expandable()
225        );
226        assert!(PerlValue::Tied { class: "Tie::Hash".into(), value: None }.is_expandable());
227    }
228
229    #[test]
230    fn type_name_all_variants() {
231        assert_eq!(PerlValue::Undef.type_name(), "undef");
232        assert_eq!(PerlValue::Scalar("s".into()).type_name(), "SCALAR");
233        assert_eq!(PerlValue::Number(1.0).type_name(), "SCALAR");
234        assert_eq!(PerlValue::Integer(1).type_name(), "SCALAR");
235        assert_eq!(PerlValue::Array(vec![]).type_name(), "ARRAY");
236        assert_eq!(PerlValue::Hash(vec![]).type_name(), "HASH");
237        assert_eq!(PerlValue::Reference(Box::new(PerlValue::Undef)).type_name(), "REF");
238        assert_eq!(
239            PerlValue::Object { class: "Foo".into(), value: Box::new(PerlValue::Undef) }
240                .type_name(),
241            "OBJECT"
242        );
243        assert_eq!(PerlValue::Code { name: None }.type_name(), "CODE");
244        assert_eq!(PerlValue::Glob("g".into()).type_name(), "GLOB");
245        assert_eq!(PerlValue::Regex("r".into()).type_name(), "Regexp");
246        assert_eq!(PerlValue::Tied { class: "T".into(), value: None }.type_name(), "TIED");
247        assert_eq!(
248            PerlValue::Truncated { summary: "s".into(), total_count: None }.type_name(),
249            "..."
250        );
251        assert_eq!(PerlValue::Error("e".into()).type_name(), "ERROR");
252    }
253
254    #[test]
255    fn child_count_all_variants() {
256        assert_eq!(PerlValue::Undef.child_count(), None);
257        assert_eq!(PerlValue::Scalar("s".into()).child_count(), None);
258        assert_eq!(PerlValue::Number(1.0).child_count(), None);
259        assert_eq!(PerlValue::Integer(1).child_count(), None);
260        assert_eq!(
261            PerlValue::Array(vec![
262                PerlValue::Integer(1),
263                PerlValue::Integer(2),
264                PerlValue::Integer(3)
265            ])
266            .child_count(),
267            Some(3)
268        );
269        assert_eq!(PerlValue::Array(vec![]).child_count(), Some(0));
270        assert_eq!(
271            PerlValue::Hash(vec![("a".into(), PerlValue::Undef), ("b".into(), PerlValue::Undef)])
272                .child_count(),
273            Some(2)
274        );
275        assert_eq!(PerlValue::Hash(vec![]).child_count(), Some(0));
276        assert_eq!(PerlValue::Reference(Box::new(PerlValue::Undef)).child_count(), None);
277        assert_eq!(PerlValue::Code { name: None }.child_count(), None);
278        assert_eq!(
279            PerlValue::Truncated { summary: "big".into(), total_count: Some(500) }.child_count(),
280            Some(500)
281        );
282        assert_eq!(
283            PerlValue::Truncated { summary: "big".into(), total_count: None }.child_count(),
284            None
285        );
286    }
287
288    #[test]
289    fn default_is_undef() {
290        assert_eq!(PerlValue::default(), PerlValue::Undef);
291    }
292
293    #[test]
294    fn nested_value_structure() {
295        let nested = PerlValue::object(
296            "My::Class",
297            PerlValue::hash(vec![
298                ("name".into(), PerlValue::scalar("Alice")),
299                (
300                    "scores".into(),
301                    PerlValue::array(vec![PerlValue::Integer(95), PerlValue::Integer(87)]),
302                ),
303            ]),
304        );
305        assert!(nested.is_expandable());
306        assert_eq!(nested.type_name(), "OBJECT");
307        assert_eq!(nested.child_count(), None); // Objects don't have direct child_count
308    }
309
310    #[test]
311    fn clone_and_equality() {
312        let original = PerlValue::array(vec![PerlValue::scalar("hello"), PerlValue::Integer(42)]);
313        let cloned = original.clone();
314        assert_eq!(original, cloned);
315    }
316}