use proptest::prelude::*;
use super::qw::identifier;
fn small_int() -> impl Strategy<Value = i32> {
1i32..10
}
fn label() -> impl Strategy<Value = String> {
prop_oneof![
Just("OUTER".to_string()),
Just("INNER".to_string()),
Just("LOOP".to_string()),
identifier().prop_map(|s| s.to_uppercase()),
]
}
fn loop_control() -> impl Strategy<Value = &'static str> {
prop_oneof![Just("next"), Just("last"), Just("redo"),]
}
fn condition() -> impl Strategy<Value = String> {
prop_oneof![
(identifier(), small_int()).prop_map(|(var, n)| format!("${} < {}", var, n)),
(identifier(), small_int()).prop_map(|(var, n)| format!("${} > {}", var, n)),
(identifier(), small_int()).prop_map(|(var, n)| format!("${} == {}", var, n)),
(identifier(), small_int()).prop_map(|(var, n)| format!("${} != {}", var, n)),
identifier().prop_map(|var| format!("defined ${}", var)),
identifier().prop_map(|var| format!("${}", var)),
]
}
fn body_statement() -> impl Strategy<Value = String> {
prop_oneof![
identifier().prop_map(|var| format!("${}++;", var)),
identifier().prop_map(|var| format!("${}--;", var)),
(identifier(), small_int()).prop_map(|(var, n)| format!("${} += {};", var, n)),
identifier().prop_map(|var| format!("print ${};", var)),
Just("print $_;".to_string()),
]
}
fn while_loop() -> impl Strategy<Value = String> {
(
identifier(),
small_int(),
condition(),
body_statement(),
prop::option::of((loop_control(), condition())),
)
.prop_map(|(var, init, cond, body, ctrl)| {
let control = ctrl.map(|(kw, c)| format!("\n {} if {};", kw, c)).unwrap_or_default();
format!("my ${} = {};\nwhile ({}) {{\n {}{}\n}}\n", var, init, cond, body, control)
})
}
fn until_loop() -> impl Strategy<Value = String> {
(identifier(), small_int(), condition(), body_statement()).prop_map(
|(var, init, cond, body)| {
format!("my ${} = {};\nuntil ({}) {{\n {}\n}}\n", var, init, cond, body)
},
)
}
fn for_c_style() -> impl Strategy<Value = String> {
(identifier(), small_int(), small_int(), body_statement()).prop_map(
|(var, start, end, body)| {
format!(
"for (my ${} = {}; ${} < {}; ${}++) {{\n {}\n}}\n",
var, start, var, end, var, body
)
},
)
}
fn foreach_loop() -> impl Strategy<Value = String> {
(
identifier(),
identifier(),
small_int(),
small_int(),
body_statement(),
prop::option::of((loop_control(), condition())),
)
.prop_map(|(var, _arr, start, end, body, ctrl)| {
let control = ctrl.map(|(kw, c)| format!("\n {} if {};", kw, c)).unwrap_or_default();
format!("for my ${} ({}..{}) {{\n {}{}\n}}\n", var, start, end, body, control)
})
}
fn foreach_with_continue() -> impl Strategy<Value = String> {
(identifier(), small_int(), small_int(), body_statement(), body_statement()).prop_map(
|(var, start, end, body, cont_body)| {
format!(
"for my ${} ({}..{}) {{\n {}\n}} continue {{\n {}\n}}\n",
var, start, end, body, cont_body
)
},
)
}
fn labeled_loop() -> impl Strategy<Value = String> {
(label(), identifier(), small_int(), small_int(), body_statement(), loop_control()).prop_map(
|(lbl, var, start, end, body, ctrl)| {
format!(
"{}: for my ${} ({}..{}) {{\n {}\n {} {} if ${} == {};\n}}\n",
lbl,
var,
start,
end,
body,
ctrl,
lbl,
var,
(start + end) / 2
)
},
)
}
fn nested_labeled_loops() -> impl Strategy<Value = String> {
(
label(),
label(),
identifier(),
identifier(),
small_int(),
loop_control(),
)
.prop_map(|(outer, inner, var1, var2, n, ctrl)| {
format!(
"{}: for my ${} (1..{}) {{\n {}: for my ${} (1..{}) {{\n {} {} if ${} == ${};\n }}\n}}\n",
outer, var1, n, inner, var2, n, ctrl, outer, var1, var2
)
})
}
fn postfix_loop() -> impl Strategy<Value = String> {
(identifier(), small_int(), body_statement())
.prop_map(|(var, limit, body)| {
prop_oneof![
Just(format!("my ${} = 0;\n{} while ${} < {};\n", var, body, var, limit)),
Just(format!("my ${} = {};\n{} until ${} < 1;\n", var, limit, body, var)),
]
})
.prop_flat_map(|s| s)
}
fn given_when() -> impl Strategy<Value = String> {
(identifier(), small_int(), small_int(), small_int()).prop_map(|(var, val, case1, case2)| {
format!(
"use v5.10;\nmy ${} = {};\ngiven (${}) {{\n when ({}) {{ print \"case1\"; }}\n when ({}) {{ print \"case2\"; }}\n default {{ print \"other\"; }}\n}}\n",
var, val, var, case1, case2
)
})
}
fn try_catch() -> impl Strategy<Value = String> {
(identifier(), identifier()).prop_map(|(msg, err_var)| {
format!(
"try {{\n die \"{}\";\n}} catch (${}) {{\n warn ${};\n}} finally {{\n print \"done\";\n}}\n",
msg, err_var, err_var
)
})
}
fn eval_block() -> impl Strategy<Value = String> {
(identifier(), identifier()).prop_map(|(result, msg)| {
format!(
"my ${} = eval {{\n die \"{}\";\n 1;\n}};\nwarn $@ if !${};\n",
result, msg, result
)
})
}
fn do_block() -> impl Strategy<Value = String> {
(identifier(), small_int(), small_int()).prop_map(|(var, a, b)| {
format!("my ${} = do {{\n my $x = {};\n $x + {};\n}};\n", var, a, b)
})
}
fn defer_block() -> impl Strategy<Value = String> {
(identifier(), identifier()).prop_map(|(func, msg)| {
format!(
"use v5.36;\nuse feature 'defer';\nno warnings 'experimental::defer';\nsub {} {{\n defer {{ print \"{}\\n\"; }}\n return 1;\n}}\n",
func, msg
)
})
}
pub fn loop_with_control() -> impl Strategy<Value = String> {
prop_oneof![
while_loop(),
until_loop(),
for_c_style(),
foreach_loop(),
foreach_with_continue(),
labeled_loop(),
nested_labeled_loops(),
postfix_loop(),
given_when(),
try_catch(),
eval_block(),
do_block(),
defer_block(),
]
}
#[cfg(test)]
mod tests {
use super::*;
proptest! {
#[test]
fn control_flow_contains_keywords(code in loop_with_control()) {
assert!(
code.contains("next")
|| code.contains("last")
|| code.contains("redo")
|| code.contains("continue")
|| code.contains("for")
|| code.contains("while")
|| code.contains("until")
|| code.contains("given")
|| code.contains("when")
|| code.contains("try")
|| code.contains("catch")
|| code.contains("finally")
|| code.contains("defer")
|| code.contains("eval")
|| code.contains("do"),
"Expected loop control keyword in: {}",
code
);
}
#[test]
fn while_loops_are_valid(code in while_loop()) {
assert!(code.contains("while"));
assert!(code.contains("my $"));
}
#[test]
fn foreach_loops_have_iterator(code in foreach_loop()) {
assert!(code.contains("for my $"));
}
#[test]
fn labeled_loops_have_label(code in labeled_loop()) {
assert!(code.contains(":"));
assert!(code.contains("for my $"));
}
}
}