use proptest::prelude::*;
use super::builtins::builtin_in_context;
use super::control_flow::loop_with_control;
use super::declarations::{
declaration_in_context, subroutine_declaration, use_require_statement, variable_declaration,
};
use super::expressions::expression_in_context;
use super::qw::qw_in_context;
use super::tie::tie_in_context;
fn program_header() -> impl Strategy<Value = String> {
prop_oneof![
Just("use strict;\nuse warnings;\n\n".to_string()),
Just("use v5.36;\n\n".to_string()),
Just("use v5.38;\nuse feature 'class';\n\n".to_string()),
Just("use strict;\nuse warnings;\nuse utf8;\n\n".to_string()),
Just("#!/usr/bin/perl\nuse strict;\nuse warnings;\n\n".to_string()),
Just("".to_string()), ]
}
fn standalone_statement() -> impl Strategy<Value = String> {
prop_oneof![
expression_in_context(),
builtin_in_context(),
qw_in_context(),
variable_declaration(),
]
}
pub fn simple_program() -> impl Strategy<Value = String> {
(program_header(), prop::collection::vec(standalone_statement(), 1..5)).prop_map(
|(header, statements)| {
let mut program = header;
for stmt in statements {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program
},
)
}
pub fn program_with_subs() -> impl Strategy<Value = String> {
(
program_header(),
prop::collection::vec(subroutine_declaration(), 1..3),
prop::collection::vec(standalone_statement(), 0..3),
)
.prop_map(|(header, subs, statements)| {
let mut program = header;
for sub in subs {
program.push_str(&sub);
if !sub.ends_with('\n') {
program.push('\n');
}
program.push('\n');
}
for stmt in statements {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program
})
}
pub fn program_with_control_flow() -> impl Strategy<Value = String> {
(
program_header(),
prop::collection::vec(standalone_statement(), 0..2),
loop_with_control(),
prop::collection::vec(standalone_statement(), 0..2),
)
.prop_map(|(header, pre, control, post)| {
let mut program = header;
for stmt in pre {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program.push_str(&control);
if !control.ends_with('\n') {
program.push('\n');
}
for stmt in post {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program
})
}
pub fn program_with_declarations() -> impl Strategy<Value = String> {
(program_header(), prop::collection::vec(declaration_in_context(), 2..5)).prop_map(
|(header, decls)| {
let mut program = header;
for decl in decls {
program.push_str(&decl);
if !decl.ends_with('\n') {
program.push('\n');
}
}
program
},
)
}
pub fn program_with_imports() -> impl Strategy<Value = String> {
(
prop::collection::vec(use_require_statement(), 1..4),
prop::collection::vec(standalone_statement(), 1..4),
)
.prop_map(|(imports, statements)| {
let mut program = String::new();
for imp in imports {
program.push_str(&imp);
}
program.push('\n');
for stmt in statements {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program
})
}
pub fn program_with_tie() -> impl Strategy<Value = String> {
(
program_header(),
prop::collection::vec(standalone_statement(), 0..2),
tie_in_context(),
prop::collection::vec(standalone_statement(), 0..2),
)
.prop_map(|(header, pre, tie_code, post)| {
let mut program = header;
for stmt in pre {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program.push_str(&tie_code);
if !tie_code.ends_with('\n') {
program.push('\n');
}
for stmt in post {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program
})
}
pub fn any_program() -> impl Strategy<Value = String> {
prop_oneof![
simple_program(),
program_with_subs(),
program_with_control_flow(),
program_with_declarations(),
program_with_imports(),
program_with_tie(),
]
}
pub fn complex_program() -> impl Strategy<Value = String> {
(
program_header(),
prop::collection::vec(use_require_statement(), 0..3),
prop::collection::vec(subroutine_declaration(), 0..2),
prop::collection::vec(
prop_oneof![standalone_statement(), loop_with_control(), declaration_in_context(),],
2..6,
),
)
.prop_map(|(header, imports, subs, body)| {
let mut program = header;
for imp in imports {
program.push_str(&imp);
}
if !program.is_empty() && !program.ends_with('\n') {
program.push('\n');
}
for sub in subs {
program.push_str(&sub);
if !sub.ends_with('\n') {
program.push('\n');
}
program.push('\n');
}
for stmt in body {
program.push_str(&stmt);
if !stmt.ends_with('\n') {
program.push('\n');
}
}
program
})
}
#[cfg(test)]
mod tests {
use super::*;
proptest! {
#[test]
fn simple_programs_are_non_empty(prog in simple_program()) {
assert!(!prog.trim().is_empty() || prog.is_empty());
}
#[test]
fn programs_with_subs_have_sub(prog in program_with_subs()) {
assert!(prog.contains("sub "));
}
#[test]
fn any_program_is_valid_utf8(prog in any_program()) {
let _ = prog.chars().count();
}
#[test]
fn complex_programs_generate(prog in complex_program()) {
let _ = prog.len();
}
}
}