1#![deny(unsafe_code)]
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
12pub enum PerlValue {
13 #[default]
15 Undef,
16
17 Scalar(String),
19
20 Number(f64),
22
23 Integer(i64),
25
26 Array(Vec<PerlValue>),
28
29 Hash(Vec<(String, PerlValue)>),
31
32 Reference(Box<PerlValue>),
34
35 Object {
37 class: String,
39 value: Box<PerlValue>,
41 },
42
43 Code {
45 name: Option<String>,
47 },
48
49 Glob(String),
51
52 Regex(String),
54
55 Tied {
57 class: String,
59 value: Option<Box<PerlValue>>,
61 },
62
63 Truncated {
65 summary: String,
67 total_count: Option<usize>,
69 },
70
71 Error(String),
73}
74
75impl PerlValue {
76 #[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 #[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 #[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 #[must_use]
121 pub fn scalar(s: impl Into<String>) -> Self {
122 Self::Scalar(s.into())
123 }
124
125 #[must_use]
127 pub fn array(elements: Vec<PerlValue>) -> Self {
128 Self::Array(elements)
129 }
130
131 #[must_use]
133 pub fn hash(pairs: Vec<(String, PerlValue)>) -> Self {
134 Self::Hash(pairs)
135 }
136
137 #[must_use]
139 pub fn reference(value: PerlValue) -> Self {
140 Self::Reference(Box::new(value))
141 }
142
143 #[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 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 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); }
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}