use crate::edit::{ExprSpec, FunctionSpec, StepSpec};
use crate::node::{BinOp, Param, Produces};
use crate::ty::{Confidence, Type};
use std::collections::BTreeSet;
fn r(n: &str) -> ExprSpec {
ExprSpec::Ref(n.into())
}
fn num(n: i64) -> ExprSpec {
ExprSpec::Lit(n)
}
fn add1(e: ExprSpec) -> ExprSpec {
ExprSpec::BinOp {
op: BinOp::Add,
lhs: Box::new(e),
rhs: Box::new(num(1)),
}
}
fn eq(a: ExprSpec, b: ExprSpec) -> ExprSpec {
ExprSpec::BinOp {
op: BinOp::Eq,
lhs: Box::new(a),
rhs: Box::new(b),
}
}
fn call(f: &str, args: Vec<ExprSpec>) -> ExprSpec {
ExprSpec::Call {
func: f.into(),
args,
}
}
fn callv(callee: ExprSpec, args: Vec<ExprSpec>) -> ExprSpec {
ExprSpec::CallValue {
callee: Box::new(callee),
args,
}
}
fn iff(c: ExprSpec, t: ExprSpec, e: ExprSpec) -> ExprSpec {
ExprSpec::If {
cond: Box::new(c),
then_branch: Box::new(t),
else_branch: Box::new(e),
}
}
fn lget(list: ExprSpec, index: ExprSpec) -> ExprSpec {
ExprSpec::ListGet {
list: Box::new(list),
index: Box::new(index),
}
}
fn llen(e: ExprSpec) -> ExprSpec {
ExprSpec::ListLen(Box::new(e))
}
fn cons(head: ExprSpec, tail: ExprSpec) -> ExprSpec {
ExprSpec::ListCons {
head: Box::new(head),
tail: Box::new(tail),
}
}
fn empty(elem: Type) -> ExprSpec {
ExprSpec::ListEmpty { elem }
}
fn boolean(b: bool) -> ExprSpec {
ExprSpec::Bool(b)
}
fn some(v: ExprSpec) -> ExprSpec {
ExprSpec::OptionSome(Box::new(v))
}
fn none(elem: Type) -> ExprSpec {
ExprSpec::OptionNone { elem }
}
fn p(name: &str, ty: Type) -> Param {
Param {
name: name.into(),
ty,
min_confidence: Confidence::External,
}
}
fn ext(ty: Type) -> Produces {
Produces {
ty,
confidence: Confidence::External,
}
}
fn step(b: &str, v: ExprSpec) -> StepSpec {
StepSpec {
binding: b.into(),
value: v,
}
}
fn var(n: &str) -> Type {
Type::Var(n.into())
}
fn list(t: Type) -> Type {
Type::List(Box::new(t))
}
fn fnt(params: Vec<Type>, ret: Type) -> Type {
Type::Fn {
params,
ret: Box::new(ret),
effects: BTreeSet::new(),
}
}
fn tp(names: &[&str]) -> Vec<String> {
names.iter().map(|s| (*s).to_string()).collect()
}
pub fn list_functions() -> Vec<FunctionSpec> {
let fold_at = FunctionSpec {
name: "fold_at".into(),
type_params: tp(&["T", "A"]),
params: vec![
p("f", fnt(vec![var("A"), var("T")], var("A"))),
p("acc", var("A")),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(var("A")),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
r("acc"),
call(
"fold_at",
vec![
r("f"),
callv(r("f"), vec![r("acc"), lget(r("xs"), r("i"))]),
r("xs"),
add1(r("i")),
],
),
),
};
let fold = FunctionSpec {
name: "fold".into(),
type_params: tp(&["T", "A"]),
params: vec![
p("f", fnt(vec![var("A"), var("T")], var("A"))),
p("acc", var("A")),
p("xs", list(var("T"))),
],
produces: ext(var("A")),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("fold_at", vec![r("f"), r("acc"), r("xs"), num(0)]),
};
let map_at = FunctionSpec {
name: "map_at".into(),
type_params: tp(&["T", "U"]),
params: vec![
p("f", fnt(vec![var("T")], var("U"))),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(list(var("U"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
empty(var("U")),
cons(
callv(r("f"), vec![lget(r("xs"), r("i"))]),
call("map_at", vec![r("f"), r("xs"), add1(r("i"))]),
),
),
};
let map = FunctionSpec {
name: "map".into(),
type_params: tp(&["T", "U"]),
params: vec![
p("f", fnt(vec![var("T")], var("U"))),
p("xs", list(var("T"))),
],
produces: ext(list(var("U"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("map_at", vec![r("f"), r("xs"), num(0)]),
};
let filter_at = FunctionSpec {
name: "filter_at".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
empty(var("T")),
iff(
callv(r("f"), vec![lget(r("xs"), r("i"))]),
cons(
lget(r("xs"), r("i")),
call("filter_at", vec![r("f"), r("xs"), add1(r("i"))]),
),
call("filter_at", vec![r("f"), r("xs"), add1(r("i"))]),
),
),
};
let filter = FunctionSpec {
name: "filter".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("filter_at", vec![r("f"), r("xs"), num(0)]),
};
let range_at = FunctionSpec {
name: "range_at".into(),
type_params: vec![],
params: vec![p("lo", Type::Number), p("hi", Type::Number)],
produces: ext(list(Type::Number)),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: iff(
eq(r("lo"), r("hi")),
empty(Type::Number),
cons(
r("lo"),
call("range_at", vec![add1(r("lo")), r("hi")]),
),
),
};
let range = FunctionSpec {
name: "range".into(),
type_params: vec![],
params: vec![p("lo", Type::Number), p("hi", Type::Number)],
produces: ext(list(Type::Number)),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("range_at", vec![r("lo"), r("hi")]),
};
let any_at = FunctionSpec {
name: "any_at".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
boolean(false),
iff(
callv(r("f"), vec![lget(r("xs"), r("i"))]),
boolean(true),
call("any_at", vec![r("f"), r("xs"), add1(r("i"))]),
),
),
};
let any = FunctionSpec {
name: "any".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("any_at", vec![r("f"), r("xs"), num(0)]),
};
let all_at = FunctionSpec {
name: "all_at".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
boolean(true),
iff(
callv(r("f"), vec![lget(r("xs"), r("i"))]),
call("all_at", vec![r("f"), r("xs"), add1(r("i"))]),
boolean(false),
),
),
};
let all = FunctionSpec {
name: "all".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("all_at", vec![r("f"), r("xs"), num(0)]),
};
let find_at = FunctionSpec {
name: "find_at".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(Type::Option(Box::new(var("T")))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
none(var("T")),
iff(
callv(r("f"), vec![lget(r("xs"), r("i"))]),
some(lget(r("xs"), r("i"))),
call("find_at", vec![r("f"), r("xs"), add1(r("i"))]),
),
),
};
let find = FunctionSpec {
name: "find".into(),
type_params: tp(&["T"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Bool)),
p("xs", list(var("T"))),
],
produces: ext(Type::Option(Box::new(var("T")))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("find_at", vec![r("f"), r("xs"), num(0)]),
};
let concat_at = FunctionSpec {
name: "concat_at".into(),
type_params: tp(&["T"]),
params: vec![
p("a", list(var("T"))),
p("b", list(var("T"))),
p("i", Type::Number),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("a")))],
result: iff(
eq(r("i"), r("n")),
r("b"),
cons(
lget(r("a"), r("i")),
call("concat_at", vec![r("a"), r("b"), add1(r("i"))]),
),
),
};
let concat = FunctionSpec {
name: "concat".into(),
type_params: tp(&["T"]),
params: vec![p("a", list(var("T"))), p("b", list(var("T")))],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("concat_at", vec![r("a"), r("b"), num(0)]),
};
let s_str = || ExprSpec::Str("0".into());
let strlen = |e: ExprSpec| ExprSpec::StrLen(Box::new(e));
let scat = |a: ExprSpec, b: ExprSpec| {
ExprSpec::StrConcat(Box::new(a), Box::new(b))
};
let n2s = |e: ExprSpec| ExprSpec::NumberToStr(Box::new(e));
let bop = |op: BinOp, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let lpad0 = FunctionSpec {
name: "lpad0".into(),
type_params: vec![],
params: vec![p("s", Type::String), p("w", Type::Number)],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("ls", strlen(r("s")))],
result: iff(
bop(BinOp::Ge, r("ls"), r("w")),
r("s"),
call("lpad0", vec![scat(s_str(), r("s")), r("w")]),
),
};
let decimal_to_str = FunctionSpec {
name: "decimal_to_str".into(),
type_params: vec![],
params: vec![p("d", Type::Decimal)],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![
step("raw", ExprSpec::DecimalRaw(Box::new(r("d")))),
step(
"m",
iff(
bop(BinOp::Lt, r("raw"), num(0)),
bop(BinOp::Sub, num(0), r("raw")),
r("raw"),
),
),
step("whole", bop(BinOp::Div, r("m"), num(10_000))),
step("frac", bop(BinOp::Mod, r("m"), num(10_000))),
],
result: scat(
scat(
scat(
iff(
bop(BinOp::Lt, r("raw"), num(0)),
ExprSpec::Str("-".into()),
ExprSpec::Str("".into()),
),
n2s(r("whole")),
),
ExprSpec::Str(".".into()),
),
call("lpad0", vec![n2s(r("frac")), num(4)]),
),
};
let take_at = FunctionSpec {
name: "take_at".into(),
type_params: tp(&["T"]),
params: vec![
p("xs", list(var("T"))),
p("k", Type::Number),
p("i", Type::Number),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
bop(BinOp::Or, bop(BinOp::Ge, r("i"), r("k")), eq(r("i"), r("n"))),
empty(var("T")),
cons(
lget(r("xs"), r("i")),
call("take_at", vec![r("xs"), r("k"), add1(r("i"))]),
),
),
};
let take = FunctionSpec {
name: "take".into(),
type_params: tp(&["T"]),
params: vec![p("xs", list(var("T"))), p("k", Type::Number)],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("take_at", vec![r("xs"), r("k"), num(0)]),
};
let drop_at = FunctionSpec {
name: "drop_at".into(),
type_params: tp(&["T"]),
params: vec![
p("xs", list(var("T"))),
p("k", Type::Number),
p("i", Type::Number),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
empty(var("T")),
iff(
bop(BinOp::Lt, r("i"), r("k")),
call("drop_at", vec![r("xs"), r("k"), add1(r("i"))]),
cons(
lget(r("xs"), r("i")),
call("drop_at", vec![r("xs"), r("k"), add1(r("i"))]),
),
),
),
};
let drop = FunctionSpec {
name: "drop".into(),
type_params: tp(&["T"]),
params: vec![p("xs", list(var("T"))), p("k", Type::Number)],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("drop_at", vec![r("xs"), r("k"), num(0)]),
};
let rev_at = FunctionSpec {
name: "rev_at".into(),
type_params: tp(&["T"]),
params: vec![p("xs", list(var("T"))), p("i", Type::Number)],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: iff(
bop(BinOp::Lt, r("i"), num(0)),
empty(var("T")),
cons(
lget(r("xs"), r("i")),
call(
"rev_at",
vec![r("xs"), bop(BinOp::Sub, r("i"), num(1))],
),
),
),
};
let reverse = FunctionSpec {
name: "reverse".into(),
type_params: tp(&["T"]),
params: vec![p("xs", list(var("T")))],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call(
"rev_at",
vec![r("xs"), bop(BinOp::Sub, llen(r("xs")), num(1))],
),
};
let flat_map_at = FunctionSpec {
name: "flat_map_at".into(),
type_params: tp(&["T", "U"]),
params: vec![
p("f", fnt(vec![var("T")], list(var("U")))),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(list(var("U"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
empty(var("U")),
call(
"concat",
vec![
callv(r("f"), vec![lget(r("xs"), r("i"))]),
call("flat_map_at", vec![r("f"), r("xs"), add1(r("i"))]),
],
),
),
};
let flat_map = FunctionSpec {
name: "flat_map".into(),
type_params: tp(&["T", "U"]),
params: vec![
p("f", fnt(vec![var("T")], list(var("U")))),
p("xs", list(var("T"))),
],
produces: ext(list(var("U"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("flat_map_at", vec![r("f"), r("xs"), num(0)]),
};
let ins = FunctionSpec {
name: "ins".into(),
type_params: tp(&["T"]),
params: vec![
p("le", fnt(vec![var("T"), var("T")], Type::Bool)),
p("x", var("T")),
p("ys", list(var("T"))),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("ys")))],
result: iff(
eq(r("n"), num(0)),
cons(r("x"), empty(var("T"))),
iff(
callv(r("le"), vec![r("x"), lget(r("ys"), num(0))]),
cons(r("x"), r("ys")),
cons(
lget(r("ys"), num(0)),
call(
"ins",
vec![
r("le"),
r("x"),
call("drop", vec![r("ys"), num(1)]),
],
),
),
),
),
};
let sort_at = FunctionSpec {
name: "sort_at".into(),
type_params: tp(&["T"]),
params: vec![
p("le", fnt(vec![var("T"), var("T")], Type::Bool)),
p("xs", list(var("T"))),
p("i", Type::Number),
p("acc", list(var("T"))),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
r("acc"),
call(
"sort_at",
vec![
r("le"),
r("xs"),
add1(r("i")),
call("ins", vec![r("le"), lget(r("xs"), r("i")), r("acc")]),
],
),
),
};
let sort_by = FunctionSpec {
name: "sort_by".into(),
type_params: tp(&["T"]),
params: vec![
p("le", fnt(vec![var("T"), var("T")], Type::Bool)),
p("xs", list(var("T"))),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call(
"sort_at",
vec![r("le"), r("xs"), num(0), empty(var("T"))],
),
};
let zip_at = FunctionSpec {
name: "zip_at".into(),
type_params: tp(&["A", "B", "C"]),
params: vec![
p("f", fnt(vec![var("A"), var("B")], var("C"))),
p("as", list(var("A"))),
p("bs", list(var("B"))),
p("i", Type::Number),
],
produces: ext(list(var("C"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![
step("na", llen(r("as"))),
step("nb", llen(r("bs"))),
],
result: iff(
bop(
BinOp::Or,
bop(BinOp::Ge, r("i"), r("na")),
bop(BinOp::Ge, r("i"), r("nb")),
),
empty(var("C")),
cons(
callv(
r("f"),
vec![lget(r("as"), r("i")), lget(r("bs"), r("i"))],
),
call("zip_at", vec![r("f"), r("as"), r("bs"), add1(r("i"))]),
),
),
};
let zip_with = FunctionSpec {
name: "zip_with".into(),
type_params: tp(&["A", "B", "C"]),
params: vec![
p("f", fnt(vec![var("A"), var("B")], var("C"))),
p("as", list(var("A"))),
p("bs", list(var("B"))),
],
produces: ext(list(var("C"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("zip_at", vec![r("f"), r("as"), r("bs"), num(0)]),
};
let dd_at = FunctionSpec {
name: "dd_at".into(),
type_params: tp(&["T"]),
params: vec![
p("eq", fnt(vec![var("T"), var("T")], Type::Bool)),
p("xs", list(var("T"))),
p("i", Type::Number),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
empty(var("T")),
iff(
bop(BinOp::Eq, r("i"), num(0)),
cons(
lget(r("xs"), num(0)),
call("dd_at", vec![r("eq"), r("xs"), num(1)]),
),
iff(
callv(
r("eq"),
vec![
lget(r("xs"), r("i")),
lget(r("xs"), bop(BinOp::Sub, r("i"), num(1))),
],
),
call("dd_at", vec![r("eq"), r("xs"), add1(r("i"))]),
cons(
lget(r("xs"), r("i")),
call("dd_at", vec![r("eq"), r("xs"), add1(r("i"))]),
),
),
),
),
};
let dedup_by = FunctionSpec {
name: "dedup_by".into(),
type_params: tp(&["T"]),
params: vec![
p("eq", fnt(vec![var("T"), var("T")], Type::Bool)),
p("xs", list(var("T"))),
],
produces: ext(list(var("T"))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("dd_at", vec![r("eq"), r("xs"), num(0)]),
};
let join_at = FunctionSpec {
name: "join_at".into(),
type_params: vec![],
params: vec![
p("sep", Type::String),
p("xs", list(Type::String)),
p("i", Type::Number),
],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("i"), r("n")),
ExprSpec::Str(String::new()),
iff(
eq(r("i"), num(0)),
scat(
lget(r("xs"), num(0)),
call("join_at", vec![r("sep"), r("xs"), num(1)]),
),
scat(
scat(r("sep"), lget(r("xs"), r("i"))),
call("join_at", vec![r("sep"), r("xs"), add1(r("i"))]),
),
),
),
};
let str_join = FunctionSpec {
name: "str_join".into(),
type_params: vec![],
params: vec![p("sep", Type::String), p("xs", list(Type::String))],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("join_at", vec![r("sep"), r("xs"), num(0)]),
};
let rep_at = FunctionSpec {
name: "rep_at".into(),
type_params: vec![],
params: vec![
p("s", Type::String),
p("n", Type::Number),
p("i", Type::Number),
],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: iff(
bop(BinOp::Ge, r("i"), r("n")),
ExprSpec::Str(String::new()),
scat(
r("s"),
call("rep_at", vec![r("s"), r("n"), add1(r("i"))]),
),
),
};
let str_repeat = FunctionSpec {
name: "str_repeat".into(),
type_params: vec![],
params: vec![p("s", Type::String), p("n", Type::Number)],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("rep_at", vec![r("s"), r("n"), num(0)]),
};
let sl = |s: ExprSpec, a: ExprSpec, b: ExprSpec| ExprSpec::StrSlice {
s: Box::new(s),
start: Box::new(a),
len: Box::new(b),
};
let slen_ = |x: ExprSpec| ExprSpec::StrLen(Box::new(x));
let sidx = |h: ExprSpec, n: ExprSpec| ExprSpec::StrIndexOf {
haystack: Box::new(h),
needle: Box::new(n),
};
let seq = |a: ExprSpec, b: ExprSpec| ExprSpec::StrEq(Box::new(a), Box::new(b));
let sub1 = |x: ExprSpec| bop(BinOp::Sub, x, num(1));
let is_ws = FunctionSpec {
name: "is_ws".into(),
type_params: vec![],
params: vec![p("c", Type::String)],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: bop(
BinOp::Or,
bop(
BinOp::Or,
seq(r("c"), ExprSpec::Str(" ".into())),
seq(r("c"), ExprSpec::Str("\t".into())),
),
bop(
BinOp::Or,
seq(r("c"), ExprSpec::Str("\n".into())),
seq(r("c"), ExprSpec::Str("\r".into())),
),
),
};
let lead = FunctionSpec {
name: "lead".into(),
type_params: vec![],
params: vec![p("s", Type::String), p("i", Type::Number)],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", slen_(r("s")))],
result: iff(
bop(BinOp::Ge, r("i"), r("n")),
r("n"),
iff(
call("is_ws", vec![sl(r("s"), r("i"), num(1))]),
call("lead", vec![r("s"), add1(r("i"))]),
r("i"),
),
),
};
let tend = FunctionSpec {
name: "tend".into(),
type_params: vec![],
params: vec![p("s", Type::String), p("i", Type::Number)],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: iff(
bop(BinOp::Lt, r("i"), num(0)),
num(0),
iff(
call("is_ws", vec![sl(r("s"), r("i"), num(1))]),
call("tend", vec![r("s"), sub1(r("i"))]),
add1(r("i")),
),
),
};
let str_trim = FunctionSpec {
name: "str_trim".into(),
type_params: vec![],
params: vec![p("s", Type::String)],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![
step("st", call("lead", vec![r("s"), num(0)])),
step("en", call("tend", vec![r("s"), sub1(slen_(r("s")))])),
],
result: iff(
bop(BinOp::Ge, r("st"), r("en")),
ExprSpec::Str(String::new()),
sl(r("s"), r("st"), bop(BinOp::Sub, r("en"), r("st"))),
),
};
let rep = FunctionSpec {
name: "rep".into(),
type_params: vec![],
params: vec![
p("s", Type::String),
p("from", Type::String),
p("to", Type::String),
],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![
step("fl", slen_(r("from"))),
step("ix", sidx(r("s"), r("from"))),
],
result: iff(
bop(BinOp::Eq, r("fl"), num(0)),
r("s"),
iff(
bop(BinOp::Lt, r("ix"), num(0)),
r("s"),
scat(
scat(sl(r("s"), num(0), r("ix")), r("to")),
call(
"rep",
vec![
sl(
r("s"),
bop(BinOp::Add, r("ix"), r("fl")),
bop(
BinOp::Sub,
slen_(r("s")),
bop(BinOp::Add, r("ix"), r("fl")),
),
),
r("from"),
r("to"),
],
),
),
),
),
};
let str_replace = FunctionSpec {
name: "str_replace".into(),
type_params: vec![],
params: vec![
p("s", Type::String),
p("from", Type::String),
p("to", Type::String),
],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call("rep", vec![r("s"), r("from"), r("to")]),
};
let first = FunctionSpec {
name: "first".into(),
type_params: tp(&["T"]),
params: vec![p("xs", list(var("T")))],
produces: ext(Type::Option(Box::new(var("T")))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("n"), num(0)),
none(var("T")),
some(lget(r("xs"), num(0))),
),
};
let last = FunctionSpec {
name: "last".into(),
type_params: tp(&["T"]),
params: vec![p("xs", list(var("T")))],
produces: ext(Type::Option(Box::new(var("T")))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![step("n", llen(r("xs")))],
result: iff(
eq(r("n"), num(0)),
none(var("T")),
some(lget(r("xs"), bop(BinOp::Sub, r("n"), num(1)))),
),
};
let omatch = |o: ExprSpec, sb: ExprSpec, nb: ExprSpec| {
ExprSpec::OptionMatch {
opt: Box::new(o),
some_bind: "v".into(),
some_body: Box::new(sb),
none_body: Box::new(nb),
}
};
let opt_map = FunctionSpec {
name: "opt_map".into(),
type_params: tp(&["T", "U"]),
params: vec![
p("f", fnt(vec![var("T")], var("U"))),
p("o", Type::Option(Box::new(var("T")))),
],
produces: ext(Type::Option(Box::new(var("U")))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: omatch(
r("o"),
some(callv(r("f"), vec![r("v")])),
none(var("U")),
),
};
let opt_then = FunctionSpec {
name: "opt_then".into(),
type_params: tp(&["T", "U"]),
params: vec![
p("f", fnt(vec![var("T")], Type::Option(Box::new(var("U"))))),
p("o", Type::Option(Box::new(var("T")))),
],
produces: ext(Type::Option(Box::new(var("U")))),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: omatch(
r("o"),
callv(r("f"), vec![r("v")]),
none(var("U")),
),
};
let nf0 = |name: &str, params: Vec<Param>, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params,
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let iabs = nf0(
"iabs",
vec![p("x", Type::Number)],
iff(
bop(BinOp::Lt, r("x"), num(0)),
bop(BinOp::Sub, num(0), r("x")),
r("x"),
),
);
let imin = nf0(
"imin",
vec![p("a", Type::Number), p("b", Type::Number)],
iff(bop(BinOp::Lt, r("a"), r("b")), r("a"), r("b")),
);
let imax = nf0(
"imax",
vec![p("a", Type::Number), p("b", Type::Number)],
iff(bop(BinOp::Gt, r("a"), r("b")), r("a"), r("b")),
);
let clamp = nf0(
"clamp",
vec![
p("x", Type::Number),
p("lo", Type::Number),
p("hi", Type::Number),
],
call(
"imax",
vec![r("lo"), call("imin", vec![r("x"), r("hi")])],
),
);
let ipow = nf0(
"ipow",
vec![p("base", Type::Number), p("exp", Type::Number)],
iff(
bop(BinOp::Le, r("exp"), num(0)),
num(1),
bop(
BinOp::Mul,
r("base"),
call(
"ipow",
vec![r("base"), bop(BinOp::Sub, r("exp"), num(1))],
),
),
),
);
let gcd = nf0(
"gcd",
vec![p("a", Type::Number), p("b", Type::Number)],
iff(
bop(BinOp::Eq, r("b"), num(0)),
call("iabs", vec![r("a")]),
call(
"gcd",
vec![r("b"), bop(BinOp::Mod, r("a"), r("b"))],
),
),
);
let dr = FunctionSpec {
name: "decimal_round_str".into(),
type_params: vec![],
params: vec![p("d", Type::Decimal), p("places", Type::Number)],
produces: ext(Type::String),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![
step("raw", ExprSpec::DecimalRaw(Box::new(r("d")))),
step("m", call("iabs", vec![r("raw")])),
step("pl", call("clamp", vec![r("places"), num(0), num(4)])),
step(
"f",
call(
"ipow",
vec![num(10), bop(BinOp::Sub, num(4), r("pl"))],
),
),
step(
"rnd",
bop(
BinOp::Mul,
bop(
BinOp::Div,
bop(
BinOp::Add,
r("m"),
bop(BinOp::Div, r("f"), num(2)),
),
r("f"),
),
r("f"),
),
),
step("whole", bop(BinOp::Div, r("rnd"), num(10_000))),
step(
"frac",
bop(
BinOp::Div,
bop(BinOp::Mod, r("rnd"), num(10_000)),
r("f"),
),
),
step(
"sign",
iff(
bop(BinOp::Lt, r("raw"), num(0)),
ExprSpec::Str("-".into()),
ExprSpec::Str(String::new()),
),
),
],
result: scat(
scat(r("sign"), n2s(r("whole"))),
iff(
bop(BinOp::Eq, r("pl"), num(0)),
ExprSpec::Str(String::new()),
scat(
ExprSpec::Str(".".into()),
call("lpad0", vec![n2s(r("frac")), r("pl")]),
),
),
),
};
vec![
fold_at, fold, map_at, map, filter_at, filter, range_at, range,
any_at, any, all_at, all, find_at, find, concat_at, concat,
lpad0, decimal_to_str, take_at, take, drop_at, drop, rev_at,
reverse, flat_map_at, flat_map, ins, sort_at, sort_by, zip_at,
zip_with, dd_at, dedup_by, join_at, str_join, rep_at, str_repeat,
is_ws, lead, tend, str_trim, rep, str_replace, first, last,
opt_map, opt_then, iabs, imin, imax, clamp, ipow, gcd, dr,
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::edit::{Editor, ModuleSpec};
use crate::store::Store;
use crate::wasm::{lower, run_i64};
#[test]
fn decimal_to_str_formats() {
let probe = |name: &str, v: f64, want: &str| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: ExprSpec::StrEq(
Box::new(call(
"decimal_to_str",
vec![ExprSpec::Decimal(v)],
)),
Box::new(ExprSpec::Str(want.into())),
),
};
let cases = [
("a", 1.25, "1.2500"),
("b", 19.99, "19.9900"),
("c", -0.5, "-0.5000"),
("d", 0.0, "0.0000"),
("e", 1000.0, "1000.0000"),
("f", -12.3456, "-12.3456"),
];
let mut fns = list_functions();
fns.extend(cases.iter().map(|(n, v, w)| probe(n, *v, w)));
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "dec".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for (n, _, w) in cases {
assert_eq!(
run_i64(&wasm, n, &[]).unwrap(),
1,
"decimal_to_str should produce {w}"
);
}
}
#[test]
fn the_stdlib_maps_filters_and_folds() {
let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
params: params
.into_iter()
.map(|(n, t)| (n.to_string(), t))
.collect(),
body: Box::new(body),
};
let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let is_even = lam(
vec![("x", Type::Number)],
bin(BinOp::Eq, bin(BinOp::Mod, r("x"), num(2)), num(0)),
);
let double = lam(vec![("x", Type::Number)], bin(BinOp::Mul, r("x"), num(2)));
let plus = lam(
vec![("a", Type::Number), ("x", Type::Number)],
bin(BinOp::Add, r("a"), r("x")),
);
let xs = ExprSpec::List((1..=6).map(num).collect());
let caller = FunctionSpec {
name: "run".into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: call(
"fold",
vec![
plus,
num(0),
call("map", vec![double, call("filter", vec![is_even, xs])]),
],
),
};
let e = Editor::new(Store::open_in_memory().unwrap());
let mut fns = list_functions();
fns.push(caller);
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdtest".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "run", &[]).unwrap(), 24);
}
#[test]
fn the_stdlib_ranges_searches_and_quantifies() {
let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
params: params
.into_iter()
.map(|(n, t)| (n.to_string(), t))
.collect(),
body: Box::new(body),
};
let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
let nfn = |name: &str, ret: Type, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(ret),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let nullary = |name: &str, result: ExprSpec| nfn(name, Type::Number, result);
let plus = || {
lam(
vec![("a", Type::Number), ("x", Type::Number)],
bin(BinOp::Add, r("a"), r("x")),
)
};
let find_or = |pred: ExprSpec, xs: ExprSpec| ExprSpec::OptionMatch {
opt: Box::new(call("find", vec![pred, xs])),
some_bind: "v".into(),
some_body: Box::new(r("v")),
none_body: Box::new(num(-1)),
};
let mut fns = list_functions();
fns.extend([
nullary(
"r_sum",
call("fold", vec![plus(), num(0), call("range", vec![num(1), num(5)])]),
),
nfn(
"anyt",
Type::Bool,
call(
"any",
vec![
lam(vec![("x", Type::Number)], bin(BinOp::Eq, r("x"), num(3))),
nums(&[1, 2, 3]),
],
),
),
nfn(
"allt",
Type::Bool,
call(
"all",
vec![
lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(0))),
nums(&[1, 2, 3]),
],
),
),
nfn(
"allf",
Type::Bool,
call(
"all",
vec![
lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(1))),
nums(&[1, 2, 3]),
],
),
),
nullary(
"find_hit",
find_or(
lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(2))),
nums(&[1, 2, 3, 4]),
),
),
nullary(
"find_miss",
find_or(
lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(9))),
nums(&[1, 2, 3, 4]),
),
),
]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdtest2".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "r_sum", &[]).unwrap(), 10);
assert_eq!(run_i64(&wasm, "anyt", &[]).unwrap(), 1);
assert_eq!(run_i64(&wasm, "allt", &[]).unwrap(), 1);
assert_eq!(run_i64(&wasm, "allf", &[]).unwrap(), 0);
assert_eq!(run_i64(&wasm, "find_hit", &[]).unwrap(), 3);
assert_eq!(run_i64(&wasm, "find_miss", &[]).unwrap(), -1);
}
#[test]
fn the_stdlib_takes_and_drops() {
let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
params: params
.into_iter()
.map(|(n, t)| (n.to_string(), t))
.collect(),
body: Box::new(body),
};
let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let xs = || ExprSpec::List([10, 20, 30, 40, 50].iter().copied().map(num).collect());
let plus = || {
lam(
vec![("a", Type::Number), ("x", Type::Number)],
bin(BinOp::Add, r("a"), r("x")),
)
};
let sum = |l: ExprSpec| call("fold", vec![plus(), num(0), l]);
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let mut fns = list_functions();
fns.extend([
nf("t_take2", sum(call("take", vec![xs(), num(2)]))), nf("t_drop2", sum(call("drop", vec![xs(), num(2)]))), nf("t_take_over", llen(call("take", vec![xs(), num(99)]))), nf("t_take_zero", llen(call("take", vec![xs(), num(0)]))), nf("t_drop_zero", llen(call("drop", vec![xs(), num(0)]))), nf("t_drop_over", llen(call("drop", vec![xs(), num(99)]))), ]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdtake".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "t_take2", &[]).unwrap(), 30);
assert_eq!(run_i64(&wasm, "t_drop2", &[]).unwrap(), 120);
assert_eq!(run_i64(&wasm, "t_take_over", &[]).unwrap(), 5);
assert_eq!(run_i64(&wasm, "t_take_zero", &[]).unwrap(), 0);
assert_eq!(run_i64(&wasm, "t_drop_zero", &[]).unwrap(), 5);
assert_eq!(run_i64(&wasm, "t_drop_over", &[]).unwrap(), 0);
}
#[test]
fn the_stdlib_reverses_and_flat_maps() {
let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
params: params
.into_iter()
.map(|(n, t)| (n.to_string(), t))
.collect(),
body: Box::new(body),
};
let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let horner = || {
lam(
vec![("a", Type::Number), ("x", Type::Number)],
bin(BinOp::Add, bin(BinOp::Mul, r("a"), num(10)), r("x")),
)
};
let dig = |l: ExprSpec| call("fold", vec![horner(), num(0), l]);
let dup = || {
lam(
vec![("x", Type::Number)],
ExprSpec::List(vec![r("x"), r("x")]),
)
};
let mut fns = list_functions();
fns.extend([
nf("rev3", dig(call("reverse", vec![nums(&[1, 2, 3])]))), nf(
"rev_empty",
llen(call("reverse", vec![empty(Type::Number)])),
), nf(
"fm_dup",
dig(call("flat_map", vec![dup(), nums(&[1, 2, 3])])),
), nf(
"fm_len",
llen(call("flat_map", vec![dup(), nums(&[4, 5])])),
), ]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdrev".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "rev3", &[]).unwrap(), 321);
assert_eq!(run_i64(&wasm, "rev_empty", &[]).unwrap(), 0);
assert_eq!(run_i64(&wasm, "fm_dup", &[]).unwrap(), 112233);
assert_eq!(run_i64(&wasm, "fm_len", &[]).unwrap(), 4);
}
#[test]
fn the_stdlib_sorts_by_a_comparator() {
let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
params: params
.into_iter()
.map(|(n, t)| (n.to_string(), t))
.collect(),
body: Box::new(body),
};
let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let le = || {
lam(
vec![("a", Type::Number), ("b", Type::Number)],
bin(BinOp::Le, r("a"), r("b")),
)
};
let ge = || {
lam(
vec![("a", Type::Number), ("b", Type::Number)],
bin(BinOp::Ge, r("a"), r("b")),
)
};
let horner = || {
lam(
vec![("a", Type::Number), ("x", Type::Number)],
bin(BinOp::Add, bin(BinOp::Mul, r("a"), num(10)), r("x")),
)
};
let dig = |l: ExprSpec| call("fold", vec![horner(), num(0), l]);
let mut fns = list_functions();
fns.extend([
nf("asc", dig(call("sort_by", vec![le(), nums(&[3, 1, 2])]))), nf("desc", dig(call("sort_by", vec![ge(), nums(&[3, 1, 2])]))), nf(
"already",
dig(call("sort_by", vec![le(), nums(&[1, 2, 3])])),
), nf(
"sort_empty",
llen(call("sort_by", vec![le(), empty(Type::Number)])),
), nf(
"asc5",
dig(call("sort_by", vec![le(), nums(&[5, 4, 3, 2, 1])])),
), ]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdsort".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "asc", &[]).unwrap(), 123);
assert_eq!(run_i64(&wasm, "desc", &[]).unwrap(), 321);
assert_eq!(run_i64(&wasm, "already", &[]).unwrap(), 123);
assert_eq!(run_i64(&wasm, "sort_empty", &[]).unwrap(), 0);
assert_eq!(run_i64(&wasm, "asc5", &[]).unwrap(), 12345);
}
#[test]
fn the_stdlib_zips_and_dedups() {
let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
params: params
.into_iter()
.map(|(n, t)| (n.to_string(), t))
.collect(),
body: Box::new(body),
};
let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let plus = || {
lam(
vec![("a", Type::Number), ("x", Type::Number)],
bin(BinOp::Add, r("a"), r("x")),
)
};
let sum = |l: ExprSpec| call("fold", vec![plus(), num(0), l]);
let addf = || {
lam(
vec![("a", Type::Number), ("b", Type::Number)],
bin(BinOp::Add, r("a"), r("b")),
)
};
let eqf = || {
lam(
vec![("a", Type::Number), ("b", Type::Number)],
bin(BinOp::Eq, r("a"), r("b")),
)
};
let horner = || {
lam(
vec![("a", Type::Number), ("x", Type::Number)],
bin(BinOp::Add, bin(BinOp::Mul, r("a"), num(10)), r("x")),
)
};
let dig = |l: ExprSpec| call("fold", vec![horner(), num(0), l]);
let mut fns = list_functions();
fns.extend([
nf(
"zip_sum",
sum(call(
"zip_with",
vec![addf(), nums(&[1, 2, 3]), nums(&[10, 20, 30])],
)),
),
nf(
"zip_uneven_len",
llen(call(
"zip_with",
vec![addf(), nums(&[1, 2]), nums(&[10, 20, 30])],
)),
),
nf(
"dd",
dig(call(
"dedup_by",
vec![eqf(), nums(&[1, 1, 2, 2, 2, 3, 1])],
)),
),
nf(
"dd_empty",
llen(call("dedup_by", vec![eqf(), empty(Type::Number)])),
),
]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdzip".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "zip_sum", &[]).unwrap(), 66);
assert_eq!(run_i64(&wasm, "zip_uneven_len", &[]).unwrap(), 2);
assert_eq!(run_i64(&wasm, "dd", &[]).unwrap(), 1231);
assert_eq!(run_i64(&wasm, "dd_empty", &[]).unwrap(), 0);
}
#[test]
fn the_stdlib_joins_and_repeats() {
let strs = |vs: &[&str]| {
ExprSpec::List(vs.iter().map(|s| ExprSpec::Str((*s).into())).collect())
};
let probe = |name: &str, got: ExprSpec, want: &str| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: ExprSpec::StrEq(
Box::new(got),
Box::new(ExprSpec::Str(want.into())),
),
};
let mut fns = list_functions();
fns.extend([
probe(
"j_abc",
call(
"str_join",
vec![ExprSpec::Str(", ".into()), strs(&["a", "b", "c"])],
),
"a, b, c",
),
probe(
"j_empty",
call(
"str_join",
vec![
ExprSpec::Str("-".into()),
ExprSpec::ListEmpty { elem: Type::String },
],
),
"",
),
probe(
"j_one",
call(
"str_join",
vec![ExprSpec::Str("-".into()), strs(&["x"])],
),
"x",
),
probe(
"r_ab3",
call(
"str_repeat",
vec![ExprSpec::Str("ab".into()), num(3)],
),
"ababab",
),
probe(
"r_zero",
call(
"str_repeat",
vec![ExprSpec::Str("z".into()), num(0)],
),
"",
),
]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdjoin".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for n in ["j_abc", "j_empty", "j_one", "r_ab3", "r_zero"] {
assert_eq!(run_i64(&wasm, n, &[]).unwrap(), 1, "{n}");
}
}
#[test]
fn the_stdlib_trims_and_replaces() {
let probe = |name: &str, got: ExprSpec, want: &str| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: ExprSpec::StrEq(
Box::new(got),
Box::new(ExprSpec::Str(want.into())),
),
};
let s = |x: &str| ExprSpec::Str(x.into());
let tr = |x: &str| call("str_trim", vec![s(x)]);
let rp = |a: &str, b: &str, c: &str| {
call("str_replace", vec![s(a), s(b), s(c)])
};
let mut fns = list_functions();
fns.extend([
probe("t_pad", tr(" hi "), "hi"),
probe("t_id", tr("x"), "x"),
probe("t_allws", tr(" "), ""),
probe("t_mixed", tr("\t a \n"), "a"),
probe("t_empty", tr(""), ""),
probe("rp_dots", rp("a.b.c", ".", "-"), "a-b-c"),
probe("rp_grow", rp("aaa", "a", "bb"), "bbbbbb"),
probe("rp_none", rp("none", "x", "y"), "none"),
probe("rp_empty_from", rp("abc", "", "-"), "abc"),
]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdtrim".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for n in [
"t_pad",
"t_id",
"t_allws",
"t_mixed",
"t_empty",
"rp_dots",
"rp_grow",
"rp_none",
"rp_empty_from",
] {
assert_eq!(run_i64(&wasm, n, &[]).unwrap(), 1, "{n}");
}
}
#[test]
fn the_stdlib_first_and_last() {
let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
let opt_or = |call_e: ExprSpec| ExprSpec::OptionMatch {
opt: Box::new(call_e),
some_bind: "v".into(),
some_body: Box::new(r("v")),
none_body: Box::new(num(-1)),
};
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let mut fns = list_functions();
fns.extend([
nf("f_hit", opt_or(call("first", vec![nums(&[7, 8, 9])]))), nf("f_miss", opt_or(call("first", vec![empty(Type::Number)]))), nf("l_hit", opt_or(call("last", vec![nums(&[7, 8, 9])]))), nf("l_one", opt_or(call("last", vec![nums(&[5])]))), nf("l_miss", opt_or(call("last", vec![empty(Type::Number)]))), ]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdopt".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "f_hit", &[]).unwrap(), 7);
assert_eq!(run_i64(&wasm, "f_miss", &[]).unwrap(), -1);
assert_eq!(run_i64(&wasm, "l_hit", &[]).unwrap(), 9);
assert_eq!(run_i64(&wasm, "l_one", &[]).unwrap(), 5);
assert_eq!(run_i64(&wasm, "l_miss", &[]).unwrap(), -1);
}
#[test]
fn the_stdlib_opt_map_and_then() {
let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
params: params
.into_iter()
.map(|(n, t)| (n.to_string(), t))
.collect(),
body: Box::new(body),
};
let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
};
let som = |v: ExprSpec| ExprSpec::OptionSome(Box::new(v));
let non = || ExprSpec::OptionNone { elem: Type::Number };
let unwrap = |o: ExprSpec| ExprSpec::OptionMatch {
opt: Box::new(o),
some_bind: "v".into(),
some_body: Box::new(r("v")),
none_body: Box::new(num(-1)),
};
let dbl = || lam(vec![("x", Type::Number)], bin(BinOp::Mul, r("x"), num(2)));
let pos10 = || {
lam(
vec![("x", Type::Number)],
ExprSpec::If {
cond: Box::new(bin(BinOp::Gt, r("x"), num(0))),
then_branch: Box::new(som(bin(BinOp::Mul, r("x"), num(10)))),
else_branch: Box::new(non()),
},
)
};
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let mut fns = list_functions();
fns.extend([
nf("m_some", unwrap(call("opt_map", vec![dbl(), som(num(5))]))), nf("m_none", unwrap(call("opt_map", vec![dbl(), non()]))), nf("t_some", unwrap(call("opt_then", vec![pos10(), som(num(3))]))), nf("t_zero", unwrap(call("opt_then", vec![pos10(), som(num(-1))]))), nf("t_none", unwrap(call("opt_then", vec![pos10(), non()]))), ]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdoptm".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
assert_eq!(run_i64(&wasm, "m_some", &[]).unwrap(), 10);
assert_eq!(run_i64(&wasm, "m_none", &[]).unwrap(), -1);
assert_eq!(run_i64(&wasm, "t_some", &[]).unwrap(), 30);
assert_eq!(run_i64(&wasm, "t_zero", &[]).unwrap(), -1);
assert_eq!(run_i64(&wasm, "t_none", &[]).unwrap(), -1);
}
#[test]
fn the_stdlib_integer_helpers() {
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let mut fns = list_functions();
fns.extend([
nf("ab_neg", call("iabs", vec![num(-7)])), nf("ab_pos", call("iabs", vec![num(3)])), nf("mn", call("imin", vec![num(2), num(5)])), nf("mx", call("imax", vec![num(2), num(5)])), nf("c_hi", call("clamp", vec![num(10), num(0), num(5)])), nf("c_lo", call("clamp", vec![num(-3), num(0), num(5)])), nf("c_in", call("clamp", vec![num(3), num(0), num(5)])), ]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdnum".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for (n, want) in [
("ab_neg", 7),
("ab_pos", 3),
("mn", 2),
("mx", 5),
("c_hi", 5),
("c_lo", 0),
("c_in", 3),
] {
assert_eq!(run_i64(&wasm, n, &[]).unwrap(), want, "{n}");
}
}
#[test]
fn the_stdlib_ipow_and_gcd() {
let nf = |name: &str, result: ExprSpec| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Number),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result,
};
let mut fns = list_functions();
fns.extend([
nf("p_2_10", call("ipow", vec![num(2), num(10)])), nf("p_5_0", call("ipow", vec![num(5), num(0)])), nf("p_3_3", call("ipow", vec![num(3), num(3)])), nf("p_neg", call("ipow", vec![num(7), num(-1)])), nf("g_12_8", call("gcd", vec![num(12), num(8)])), nf("g_54_24", call("gcd", vec![num(54), num(24)])), nf("g_7_0", call("gcd", vec![num(7), num(0)])), nf("g_0_0", call("gcd", vec![num(0), num(0)])), nf("g_neg", call("gcd", vec![num(-12), num(8)])), ]);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stdpow".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for (n, want) in [
("p_2_10", 1024),
("p_5_0", 1),
("p_3_3", 27),
("p_neg", 1),
("g_12_8", 4),
("g_54_24", 6),
("g_7_0", 7),
("g_0_0", 0),
("g_neg", 4),
] {
assert_eq!(run_i64(&wasm, n, &[]).unwrap(), want, "{n}");
}
}
#[test]
fn decimal_round_str_formats() {
let probe = |name: &str, v: f64, places: i64, want: &str| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: ExprSpec::StrEq(
Box::new(call(
"decimal_round_str",
vec![ExprSpec::Decimal(v), num(places)],
)),
Box::new(ExprSpec::Str(want.into())),
),
};
let cases = [
("a", 1.25, 1, "1.3"),
("b", 1.24, 1, "1.2"),
("c", 19.99, 1, "20.0"),
("d", 1.255, 2, "1.26"),
("e", -1.255, 2, "-1.26"),
("f", 2.5, 0, "3"),
("g", 1.4, 0, "1"),
("h", 1.2345, 4, "1.2345"),
("i", 0.0, 2, "0.00"),
];
let mut fns = list_functions();
fns.extend(cases.iter().map(|(n, v, pl, w)| probe(n, *v, *pl, w)));
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "stddr".into(),
types: vec![],
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for (n, _, _, w) in cases {
assert_eq!(
run_i64(&wasm, n, &[]).unwrap(),
1,
"decimal_round_str should produce {w}"
);
}
}
}