Skip to main content

perl_test_generators/
variable.rs

1//! Generators for Perl variable names.
2//!
3//! Covers sigils (`$`, `@`, `%`), special variables (`$_`, `@_`, `$1`–`$9`),
4//! and package-qualified names (`$Foo::bar`).
5
6use proptest::prelude::*;
7
8/// Valid ASCII identifier characters for variable name body (first char).
9static IDENT_START: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
10
11/// Valid ASCII identifier characters for subsequent positions.
12static IDENT_CONT: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
13
14/// Generate a single Perl identifier (without sigil).
15fn identifier() -> impl Strategy<Value = String> {
16    prop_oneof![
17        // Common short names
18        Just("self".to_string()),
19        Just("class".to_string()),
20        // Random identifier 1–12 chars
21        (
22            prop::sample::select(IDENT_START),
23            prop::collection::vec(prop::sample::select(IDENT_CONT), 0..=11_usize),
24        )
25            .prop_map(|(first, rest)| {
26                let mut s = String::new();
27                s.push(first as char);
28                for b in rest {
29                    s.push(b as char);
30                }
31                s
32            }),
33    ]
34}
35
36/// Generate a full Perl variable name including sigil.
37///
38/// Covers scalar (`$x`), array (`@x`), hash (`%x`), special variables
39/// (`$_`, `@_`, `$1`–`$9`), and optionally package-qualified names
40/// (`$Foo::bar`).
41pub fn variable() -> impl Strategy<Value = String> {
42    prop_oneof![
43        // Special variables
44        Just("$_".to_string()),
45        Just("@_".to_string()),
46        Just("%ENV".to_string()),
47        Just("@ARGV".to_string()),
48        Just("$0".to_string()),
49        (1u32..=9).prop_map(|n| format!("${}", n)),
50        // Simple sigiled variable
51        (prop_oneof![Just('$'), Just('@'), Just('%')], identifier())
52            .prop_map(|(sigil, name)| format!("{}{}", sigil, name)),
53        // Package-qualified
54        (prop_oneof![Just('$'), Just('@'), Just('%')], identifier(), identifier())
55            .prop_map(|(sigil, pkg, name)| format!("{}{}::{}", sigil, pkg, name)),
56    ]
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    proptest! {
64        #[test]
65        fn variable_starts_with_sigil(v in variable()) {
66            assert!(v.starts_with('$') || v.starts_with('@') || v.starts_with('%'));
67        }
68
69        #[test]
70        fn variable_body_is_valid(v in variable()) {
71            let body = &v[1..];
72            assert!(!body.is_empty(), "variable body must not be empty: {}", v);
73        }
74
75        #[test]
76        fn identifier_is_ascii(id in identifier()) {
77            prop_assert!(id.is_ascii(), "identifier must be ASCII: {}", id);
78        }
79    }
80}