crisp 0.2.1

A small, expressive Lisp-inspired programming language.
Documentation
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::string::string_regex;

pub fn gen_bool() -> impl Strategy<Value = String> {
    prop_oneof![Just("true".to_string()), Just("false".to_string())]
}

fn list<S>(inner: S, range: std::ops::Range<usize>) -> impl Strategy<Value = String>
where
    S: Strategy<Value = String>,
{
    vec(inner, range).prop_map(|xs| format!("({})", xs.join(" ")))
}

pub fn gen_ident() -> impl Strategy<Value = String> {
    string_regex(r"[a-zA-Z_][a-zA-Z0-9_!$%&*+\-./<=>?@^~]{0,31}").unwrap()
}

pub fn gen_number() -> impl Strategy<Value = String> {
    any::<u64>().prop_map(|n| n.to_string())
}

pub fn gen_atom() -> impl Strategy<Value = String> {
    prop_oneof![
        gen_ident(),
        gen_bool(),
        gen_number(),
        string_regex(r#""[a-z]{1,10}""#).unwrap(),
    ]
}

pub fn gen_expr() -> impl Strategy<Value = String> {
    gen_atom().prop_recursive(8, 256, 10, |inner| list(inner, 1..6))
}

pub fn gen_body() -> impl Strategy<Value = String> {
    list(gen_expr(), 0..5)
}

pub fn gen_type() -> impl Strategy<Value = String> {
    prop_oneof![
        Just("i8".to_string()),
        Just("i16".to_string()),
        Just("i32".to_string()),
        Just("i64".to_string()),
        Just("u8".to_string()),
        Just("u16".to_string()),
        Just("u32".to_string()),
        Just("u64".to_string()),
        Just("bool".to_string()),
        Just("string".to_string()),
    ]
}

pub fn gen_return_type() -> impl Strategy<Value = String> {
    prop_oneof![gen_type(), Just("void".to_string())]
}

pub fn gen_param() -> impl Strategy<Value = String> {
    (gen_ident(), gen_type()).prop_map(|(name, ty)| format!("{name}:{ty}"))
}

pub fn gen_params() -> impl Strategy<Value = String> {
    list(gen_param(), 0..3)
}

pub fn gen_bad_params() -> impl Strategy<Value = String> {
    prop_oneof![
        gen_params().prop_map(|s| s.replace(['(', ')'], "X")),
        (gen_ident(), gen_ident()).prop_map(|(a, b)| format!("{a}__{b}")),
        gen_type().prop_map(|t| format!("{t}:::")),
        gen_params().prop_map(|s| format!("(((({s}))))")),
    ]
}

pub fn gen_bad_if() -> impl Strategy<Value = String> {
    prop_oneof![
        (gen_expr(), gen_expr(), gen_expr()).prop_map(|(a, b, c)| format!("(iff {a} {b} {c})")),
        (gen_expr(), gen_expr(), gen_expr()).prop_map(|(a, b, c)| format!("(i f {a} {b} {c})")),
        (gen_expr(), gen_expr()).prop_map(|(a, b)| format!("(if {a} {b})")),
        (gen_expr(), gen_expr(), gen_expr(), gen_expr(), gen_expr())
            .prop_map(|(a, b, c, d, e)| format!("(if {a} {b} {c} {d} {e})")),
        (gen_expr(), gen_expr(), gen_expr()).prop_map(|(a, b, c)| format!("if {a} {b} {c}")),
    ]
}

pub fn gen_bad_for() -> impl Strategy<Value = String> {
    prop_oneof![
        (
            prop_oneof![gen_body(), gen_number(), gen_bool()],
            gen_expr(),
            gen_body()
        )
            .prop_map(|(a, b, c)| format!("(for {a} {b} {c})")),
        gen_ident().prop_map(|a| format!("(for {a} \"invalid\" (a))")),
        (gen_ident(), any::<u64>()).prop_map(|(a, n)| format!("(for {a} {n} (a))")),
        (gen_ident(), gen_expr(), gen_atom()).prop_map(|(a, b, c)| format!("(for {a} {b} {c})")),
        (gen_ident(), gen_expr()).prop_map(|(a, b)| format!("(for {a} {b})")),
        (gen_ident(), gen_expr(), gen_body(), gen_expr())
            .prop_map(|(a, b, c, d)| format!("(for {a} {b} {c} {d})")),
    ]
}

pub fn gen_for() -> impl Strategy<Value = String> {
    (
        gen_ident(),
        prop_oneof![gen_ident(), gen_body()],
        gen_body(),
    )
        .prop_map(|(dummy, iter, body)| format!("(for {dummy} {iter} {body})"))
}

pub fn gen_let() -> impl Strategy<Value = String> {
    (
        gen_ident(),
        prop_oneof![gen_body(), gen_number(), gen_bool(), gen_ident()],
    )
        .prop_map(|(a, b)| format!("(let {a} {b})"))
}

pub fn gen_bad_let() -> impl Strategy<Value = String> {
    prop_oneof![
        gen_ident().prop_map(|a| format!("(let {a})")),
        (gen_ident(), gen_body(), gen_body()).prop_map(|(a, b, c)| format!("(let {a} {b} {c})")),
        (
            prop_oneof![gen_body(), gen_bool(), gen_number()],
            gen_body()
        )
            .prop_map(|(a, b)| format!("(let {a} {b})")),
    ]
}

pub fn gen_fn() -> impl Strategy<Value = String> {
    prop_oneof![
        (gen_return_type(), gen_ident(), gen_params(), gen_body())
            .prop_map(|(ret, name, params, body)| { format!("(fn:{ret} {name} {params} {body})") }),
        (gen_return_type(), gen_params(), gen_body())
            .prop_map(|(ret, params, body)| { format!("(fn:{ret} {params} {body})") }),
    ]
}

pub fn gen_bad_fn() -> impl Strategy<Value = String> {
    prop_oneof![
        (gen_return_type(), gen_ident(), gen_params())
            .prop_map(|(r, n, p)| format!("(fn:{r} {n} {p})")),
        (gen_return_type(), gen_ident(), gen_body())
            .prop_map(|(r, n, b)| format!("(fn:{r} {n} {b})")),
        (
            gen_return_type(),
            gen_ident(),
            gen_params(),
            gen_body(),
            gen_body()
        )
            .prop_map(|(r, n, p, b1, b2)| format!("(fn:{r} {n} {p} {b1} {b2})")),
        (gen_return_type(), gen_params()).prop_map(|(r, p)| format!("(fn:{r} {p})")),
        (gen_return_type(), gen_body()).prop_map(|(r, b)| format!("(fn:{r} {b})")),
        (gen_return_type(), gen_params(), gen_body(), gen_body())
            .prop_map(|(r, p, b1, b2)| format!("(fn:{r} {p} {b1} {b2})")),
        (gen_return_type(), gen_ident(), gen_bad_params(), gen_body())
            .prop_map(|(r, n, bp, b)| format!("(fn:{r} {n} {bp} {b})")),
        (gen_return_type(), gen_bad_params(), gen_body())
            .prop_map(|(r, bp, b)| format!("(fn:{r} {bp} {b})")),
    ]
}