use proptest::prelude::*;
use super::qw::identifier;
pub fn package_name() -> impl Strategy<Value = String> {
prop::collection::vec(identifier(), 1..4).prop_map(|parts| parts.join("::"))
}
pub fn package_declaration() -> impl Strategy<Value = String> {
(
package_name(),
prop::sample::select(vec!["", " 1.23", " v5.36"]),
prop::sample::select(vec![true, false]),
)
.prop_map(|(name, version, block)| {
if block {
format!("package {}{} {{\n sub helper {{ return 1; }}\n}}\n", name, version)
} else {
format!("package {}{};\n", name, version)
}
})
}
pub fn class_declaration() -> impl Strategy<Value = String> {
(package_name(), identifier(), identifier()).prop_map(|(name, field, method)| {
format!(
"class {} {{\n field ${} :param = 0;\n method {} {{ return ${}; }}\n}}\n",
name, field, method, field
)
})
}
pub fn stateful_subroutine() -> impl Strategy<Value = String> {
(identifier(), prop::sample::select(vec!["0", "1", "10"])).prop_map(|(name, init)| {
format!("sub {} {{\n state $count = {};\n return $count++;\n}}\n", name, init)
})
}
fn decl_keyword() -> impl Strategy<Value = &'static str> {
prop_oneof![Just("my"), Just("our"), Just("state"), Just("local"),]
}
pub fn variable_declaration() -> impl Strategy<Value = String> {
prop_oneof![
(decl_keyword(), identifier(), prop::sample::select(vec!["1", "\"value\"", "undef"]),)
.prop_map(|(kw, name, value)| format!("{} ${} = {};\n", kw, name, value)),
(decl_keyword(), identifier())
.prop_map(|(kw, name)| { format!("{} @{} = (1, 2, 3);\n", kw, name) }),
(decl_keyword(), identifier())
.prop_map(|(kw, name)| { format!("{} %{} = (a => 1, b => 2);\n", kw, name) }),
]
}
fn param_token() -> impl Strategy<Value = String> {
prop_oneof![
Just("$x".to_string()),
Just("$y".to_string()),
Just("$self".to_string()),
Just("$arg".to_string()),
Just("$value".to_string()),
Just("$opt = 0".to_string()),
Just("$limit = 1".to_string()),
]
}
fn slurpy_token() -> impl Strategy<Value = Option<String>> {
prop_oneof![Just(None), Just(Some("@rest".to_string())), Just(Some("%opts".to_string())),]
}
fn subroutine_attribute() -> impl Strategy<Value = &'static str> {
prop_oneof![Just("method"), Just("lvalue"), Just("prototype($$)"),]
}
fn subroutine_attributes() -> impl Strategy<Value = Vec<&'static str>> {
prop::collection::vec(subroutine_attribute(), 0..3).prop_map(|mut attrs| {
attrs.sort_unstable();
attrs.dedup();
attrs
})
}
pub fn subroutine_declaration() -> impl Strategy<Value = String> {
(
identifier(),
prop::collection::vec(param_token(), 0..3),
slurpy_token(),
prop::sample::select(vec![true, false]),
subroutine_attributes(),
)
.prop_map(|(name, mut params, slurpy, use_signature, attributes)| {
if let Some(extra) = slurpy {
params.push(extra);
}
let signature =
if use_signature { format!(" ({})", params.join(", ")) } else { String::new() };
let attribute = if attributes.is_empty() {
String::new()
} else {
format!(" :{}", attributes.join(" :"))
};
format!("sub {}{}{} {{\n return 1;\n}}\n", name, signature, attribute)
})
}
pub fn anonymous_subroutine() -> impl Strategy<Value = String> {
(
prop::collection::vec(param_token(), 0..3),
slurpy_token(),
prop::sample::select(vec![true, false]),
)
.prop_map(|(mut params, slurpy, use_signature)| {
if let Some(extra) = slurpy {
params.push(extra);
}
let signature =
if use_signature { format!(" ({})", params.join(", ")) } else { String::new() };
format!("my $handler = sub{} {{ return 1; }};\n", signature)
})
}
pub fn subroutine_forward_declaration() -> impl Strategy<Value = String> {
(
identifier(),
prop::sample::select(vec!["", " ($$)", " (@)", " ($;$)"]),
subroutine_attributes(),
)
.prop_map(|(name, proto, attributes)| {
let attribute = if attributes.is_empty() {
String::new()
} else {
format!(" :{}", attributes.join(" :"))
};
format!("sub {}{}{};\n", name, proto, attribute)
})
}
fn call_arg() -> impl Strategy<Value = String> {
prop_oneof![
Just("$x".to_string()),
Just("$y".to_string()),
Just("1".to_string()),
Just("\"value\"".to_string()),
Just("@items".to_string()),
]
}
pub fn method_call_in_context() -> impl Strategy<Value = String> {
(
prop_oneof![Just("$obj".to_string()), Just("$self".to_string()), package_name(),],
identifier(),
prop::collection::vec(call_arg(), 0..3),
)
.prop_map(|(target, method, args)| {
let arg_list = args.join(", ");
format!("{}->{}({});\n", target, method, arg_list)
})
}
pub fn use_require_statement() -> impl Strategy<Value = String> {
prop_oneof![
Just("use strict;\n".to_string()),
Just("use warnings;\n".to_string()),
Just("use warnings FATAL => 'all';\n".to_string()),
Just("use v5.36;\n".to_string()),
Just("use v5.38;\n".to_string()),
Just("use feature ':5.36';\n".to_string()),
Just("use feature 'signatures';\n".to_string()),
Just("use feature qw(signatures say state);\n".to_string()),
Just("use experimental qw(try defer);\n".to_string()),
Just("use builtin qw(true false is_bool);\n".to_string()),
Just("use constant PI => 3.14;\n".to_string()),
Just("use constant FOO => 1, BAR => 2;\n".to_string()),
Just("use constant { FOO => 1, BAR => 2 };\n".to_string()),
Just("use bytes;\n".to_string()),
Just("use utf8;\n".to_string()),
Just("use open ':std', ':encoding(UTF-8)';\n".to_string()),
Just("use mro 'c3';\n".to_string()),
Just("use lib \"lib\";\n".to_string()),
Just("use base qw(Exporter);\n".to_string()),
Just("use parent qw(Exporter);\n".to_string()),
Just("use autodie;\n".to_string()),
Just("no warnings 'experimental::signatures';\n".to_string()),
Just("no warnings qw(experimental::signatures deprecated);\n".to_string()),
Just("no strict 'refs';\n".to_string()),
Just("use if $] >= 5.036, 'feature', 'say';\n".to_string()),
Just("use subs qw(helper util);\n".to_string()),
package_name().prop_map(|name| format!("use {};\n", name)),
package_name().prop_map(|name| format!("use {} 1.23;\n", name)),
package_name().prop_map(|name| format!("require {};\n", name)),
]
}
pub fn declaration_in_context() -> impl Strategy<Value = String> {
prop_oneof![
package_declaration(),
class_declaration(),
variable_declaration(),
subroutine_declaration(),
anonymous_subroutine(),
subroutine_forward_declaration(),
stateful_subroutine(),
method_call_in_context(),
use_require_statement(),
]
}
#[cfg(test)]
mod tests {
use super::*;
proptest! {
#[test]
fn package_declaration_includes_keyword(code in package_declaration()) {
assert!(code.contains("package"));
}
#[test]
fn subroutine_declaration_includes_keyword(code in subroutine_declaration()) {
assert!(code.contains("sub"));
}
#[test]
fn anonymous_subroutine_includes_sub(code in anonymous_subroutine()) {
assert!(code.contains("sub"));
}
#[test]
fn forward_declaration_includes_sub(code in subroutine_forward_declaration()) {
assert!(code.contains("sub"));
assert!(code.trim_end().ends_with(';'));
}
#[test]
fn variable_declaration_includes_keyword(code in variable_declaration()) {
assert!(
code.contains("my")
|| code.contains("our")
|| code.contains("state")
|| code.contains("local")
);
}
#[test]
fn method_call_includes_arrow(code in method_call_in_context()) {
assert!(code.contains("->"));
}
#[test]
fn use_require_includes_keyword(code in use_require_statement()) {
assert!(code.contains("use") || code.contains("require") || code.contains("no"));
}
}
}