use crate::edit::{ExprSpec, FunctionSpec, StepSpec, TypeDefSpec};
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 lit(s: &str) -> ExprSpec {
ExprSpec::Str(s.into())
}
fn num(n: i64) -> ExprSpec {
ExprSpec::Lit(n)
}
fn cat(a: ExprSpec, b: ExprSpec) -> ExprSpec {
ExprSpec::StrConcat(Box::new(a), Box::new(b))
}
fn cats(parts: Vec<ExprSpec>) -> ExprSpec {
let mut it = parts.into_iter();
let first = it.next().unwrap();
it.fold(first, cat)
}
fn n2s(e: ExprSpec) -> ExprSpec {
ExprSpec::NumberToStr(Box::new(e))
}
fn slen(e: ExprSpec) -> ExprSpec {
ExprSpec::StrLen(Box::new(e))
}
fn slice(s: ExprSpec, a: ExprSpec, b: ExprSpec) -> ExprSpec {
ExprSpec::StrSlice {
s: Box::new(s),
start: Box::new(a),
len: Box::new(b),
}
}
fn seq(a: ExprSpec, b: ExprSpec) -> ExprSpec {
ExprSpec::StrEq(Box::new(a), Box::new(b))
}
fn bop(op: BinOp, a: ExprSpec, b: ExprSpec) -> ExprSpec {
ExprSpec::BinOp {
op,
lhs: Box::new(a),
rhs: Box::new(b),
}
}
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 add1(e: ExprSpec) -> ExprSpec {
bop(BinOp::Add, e, num(1))
}
fn call(f: &str, args: Vec<ExprSpec>) -> ExprSpec {
ExprSpec::Call {
func: f.into(),
args,
}
}
fn lget(l: ExprSpec, i: ExprSpec) -> ExprSpec {
ExprSpec::ListGet {
list: Box::new(l),
index: Box::new(i),
}
}
fn llen(e: ExprSpec) -> ExprSpec {
ExprSpec::ListLen(Box::new(e))
}
fn fieldof(base: ExprSpec, ty: &str, f: &str) -> ExprSpec {
ExprSpec::Field {
base: Box::new(base),
type_name: ty.into(),
field: f.into(),
}
}
fn mtch(s: ExprSpec, ty: &str, arms: Vec<(&str, Vec<&str>, ExprSpec)>) -> ExprSpec {
ExprSpec::Match {
scrutinee: Box::new(s),
type_name: ty.into(),
arms: arms
.into_iter()
.map(|(c, b, e)| {
(c.to_string(), b.into_iter().map(String::from).collect(), e)
})
.collect(),
}
}
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 json_ty() -> Type {
Type::Named("Json".into())
}
fn json_list() -> Type {
Type::List(Box::new(json_ty()))
}
fn entry_list() -> Type {
Type::List(Box::new(Type::Named("JEntry".into())))
}
fn pure(name: &str, params: Vec<Param>, prod: Type, steps: Vec<StepSpec>, result: ExprSpec) -> FunctionSpec {
FunctionSpec {
name: name.into(),
type_params: vec![],
params,
produces: ext(prod),
requires: BTreeSet::new(),
on_failure: vec![],
steps,
result,
}
}
pub fn types() -> Vec<TypeDefSpec> {
vec![
TypeDefSpec::Variant {
name: "Json".into(),
cases: vec![
("JNull".into(), vec![]),
("JBool".into(), vec![("b".into(), Type::Bool)]),
("JNum".into(), vec![("n".into(), Type::Number)]),
("JStr".into(), vec![("s".into(), Type::String)]),
("JArr".into(), vec![("items".into(), json_list())]),
("JObj".into(), vec![("entries".into(), entry_list())]),
],
},
TypeDefSpec::Record {
name: "JEntry".into(),
fields: vec![
("key".into(), Type::String),
("val".into(), json_ty()),
],
},
TypeDefSpec::Record {
name: "PJson".into(),
fields: vec![
("val".into(), json_ty()),
("rest".into(), Type::String),
],
},
TypeDefSpec::Record {
name: "PArr".into(),
fields: vec![
("items".into(), json_list()),
("rest".into(), Type::String),
],
},
TypeDefSpec::Record {
name: "PObj".into(),
fields: vec![
("entries".into(), entry_list()),
("rest".into(), Type::String),
],
},
TypeDefSpec::Record {
name: "PKey".into(),
fields: vec![
("k".into(), Type::String),
("rest".into(), Type::String),
],
},
]
}
pub fn functions() -> Vec<FunctionSpec> {
let esc_one = pure(
"esc_one",
vec![p("c", Type::String)],
Type::String,
vec![],
iff(
seq(r("c"), lit("\"")),
lit("\\\""),
iff(
seq(r("c"), lit("\\")),
lit("\\\\"),
iff(
seq(r("c"), lit("\n")),
lit("\\n"),
iff(
seq(r("c"), lit("\t")),
lit("\\t"),
iff(seq(r("c"), lit("\r")), lit("\\r"), r("c")),
),
),
),
),
);
let esc_at = pure(
"esc_at",
vec![p("s", Type::String), p("i", Type::Number)],
Type::String,
vec![step("n", slen(r("s")))],
iff(
bop(BinOp::Ge, r("i"), r("n")),
lit(""),
cat(
call("esc_one", vec![slice(r("s"), r("i"), num(1))]),
call("esc_at", vec![r("s"), add1(r("i"))]),
),
),
);
let json_escape = pure(
"json_escape",
vec![p("s", Type::String)],
Type::String,
vec![],
call("esc_at", vec![r("s"), num(0)]),
);
let sj_arr = pure(
"sj_arr",
vec![p("items", json_list()), p("i", Type::Number)],
Type::String,
vec![step("n", llen(r("items")))],
iff(
bop(BinOp::Eq, r("i"), r("n")),
lit(""),
cats(vec![
iff(bop(BinOp::Eq, r("i"), num(0)), lit(""), lit(",")),
call("json_stringify", vec![lget(r("items"), r("i"))]),
call("sj_arr", vec![r("items"), add1(r("i"))]),
]),
),
);
let sj_entry = pure(
"sj_entry",
vec![p("e", Type::Named("JEntry".into()))],
Type::String,
vec![],
cats(vec![
lit("\""),
call("json_escape", vec![fieldof(r("e"), "JEntry", "key")]),
lit("\":"),
call("json_stringify", vec![fieldof(r("e"), "JEntry", "val")]),
]),
);
let sj_obj = pure(
"sj_obj",
vec![p("entries", entry_list()), p("i", Type::Number)],
Type::String,
vec![step("n", llen(r("entries")))],
iff(
bop(BinOp::Eq, r("i"), r("n")),
lit(""),
cats(vec![
iff(bop(BinOp::Eq, r("i"), num(0)), lit(""), lit(",")),
call("sj_entry", vec![lget(r("entries"), r("i"))]),
call("sj_obj", vec![r("entries"), add1(r("i"))]),
]),
),
);
let json_stringify = pure(
"json_stringify",
vec![p("j", json_ty())],
Type::String,
vec![],
mtch(
r("j"),
"Json",
vec![
("JNull", vec![], lit("null")),
(
"JBool",
vec!["b"],
iff(r("b"), lit("true"), lit("false")),
),
("JNum", vec!["n"], n2s(r("n"))),
(
"JStr",
vec!["s"],
cats(vec![
lit("\""),
call("json_escape", vec![r("s")]),
lit("\""),
]),
),
(
"JArr",
vec!["items"],
cats(vec![
lit("["),
call("sj_arr", vec![r("items"), num(0)]),
lit("]"),
]),
),
(
"JObj",
vec!["entries"],
cats(vec![
lit("{"),
call("sj_obj", vec![r("entries"), num(0)]),
lit("}"),
]),
),
],
),
);
let jrec = |val: ExprSpec, rest: ExprSpec| ExprSpec::Record {
type_name: "PJson".into(),
fields: vec![("val".into(), val), ("rest".into(), rest)],
};
let jv = |case: &str, fields: Vec<(&str, ExprSpec)>| ExprSpec::Variant {
type_name: "Json".into(),
case: case.into(),
fields: fields.into_iter().map(|(n, e)| (n.into(), e)).collect(),
};
let isdig = |c: ExprSpec| {
bop(
BinOp::Ge,
ExprSpec::StrIndexOf {
haystack: Box::new(lit("0123456789")),
needle: Box::new(c),
},
num(0),
)
};
let sub = |a: ExprSpec, b: ExprSpec| bop(BinOp::Sub, a, b);
let restfrom = |s: ExprSpec, i: ExprSpec| slice(s.clone(), i.clone(), sub(slen(s), i));
let j_is_ws = pure(
"j_is_ws",
vec![p("c", Type::String)],
Type::Bool,
vec![],
bop(
BinOp::Or,
bop(
BinOp::Or,
seq(r("c"), lit(" ")),
seq(r("c"), lit("\t")),
),
bop(
BinOp::Or,
seq(r("c"), lit("\n")),
seq(r("c"), lit("\r")),
),
),
);
let jws_at = pure(
"jws_at",
vec![p("s", Type::String), p("i", Type::Number)],
Type::Number,
vec![step("n", slen(r("s")))],
iff(
bop(BinOp::Ge, r("i"), r("n")),
r("n"),
iff(
call("j_is_ws", vec![slice(r("s"), r("i"), num(1))]),
call("jws_at", vec![r("s"), add1(r("i"))]),
r("i"),
),
),
);
let jdrop_ws = pure(
"jdrop_ws",
vec![p("s", Type::String)],
Type::String,
vec![step("k", call("jws_at", vec![r("s"), num(0)]))],
restfrom(r("s"), r("k")),
);
let jclose = pure(
"jclose",
vec![p("s", Type::String), p("i", Type::Number)],
Type::Number,
vec![step("n", slen(r("s")))],
iff(
bop(BinOp::Ge, r("i"), r("n")),
r("n"),
iff(
seq(slice(r("s"), r("i"), num(1)), lit("\"")),
r("i"),
iff(
seq(slice(r("s"), r("i"), num(1)), lit("\\")),
call("jclose", vec![r("s"), bop(BinOp::Add, r("i"), num(2))]),
call("jclose", vec![r("s"), add1(r("i"))]),
),
),
),
);
let un_one = pure(
"un_one",
vec![p("d", Type::String)],
Type::String,
vec![],
iff(
seq(r("d"), lit("\"")),
lit("\""),
iff(
seq(r("d"), lit("\\")),
lit("\\"),
iff(
seq(r("d"), lit("n")),
lit("\n"),
iff(
seq(r("d"), lit("t")),
lit("\t"),
iff(
seq(r("d"), lit("r")),
lit("\r"),
iff(seq(r("d"), lit("/")), lit("/"), r("d")),
),
),
),
),
),
);
let un_at = pure(
"un_at",
vec![p("s", Type::String), p("i", Type::Number)],
Type::String,
vec![step("n", slen(r("s")))],
iff(
bop(BinOp::Ge, r("i"), r("n")),
lit(""),
iff(
seq(slice(r("s"), r("i"), num(1)), lit("\\")),
iff(
bop(BinOp::Ge, add1(r("i")), r("n")),
lit("\\"),
cat(
call(
"un_one",
vec![slice(r("s"), add1(r("i")), num(1))],
),
call(
"un_at",
vec![r("s"), bop(BinOp::Add, r("i"), num(2))],
),
),
),
cat(
slice(r("s"), r("i"), num(1)),
call("un_at", vec![r("s"), add1(r("i"))]),
),
),
),
);
let junescape = pure(
"junescape",
vec![p("s", Type::String)],
Type::String,
vec![],
call("un_at", vec![r("s"), num(0)]),
);
let jnum_end = pure(
"jnum_end",
vec![p("s", Type::String), p("i", Type::Number)],
Type::Number,
vec![step("n", slen(r("s")))],
iff(
bop(BinOp::Ge, r("i"), r("n")),
r("i"),
iff(
isdig(slice(r("s"), r("i"), num(1))),
call("jnum_end", vec![r("s"), add1(r("i"))]),
r("i"),
),
),
);
let json_str = pure(
"json_str",
vec![p("t", Type::String)],
Type::Named("PJson".into()),
vec![
step("close", call("jclose", vec![r("t"), num(1)])),
step("raw", slice(r("t"), num(1), sub(r("close"), num(1)))),
step(
"resti",
iff(
bop(BinOp::Gt, add1(r("close")), slen(r("t"))),
slen(r("t")),
add1(r("close")),
),
),
],
jrec(
jv("JStr", vec![("s", call("junescape", vec![r("raw")]))]),
restfrom(r("t"), r("resti")),
),
);
let json_num = pure(
"json_num",
vec![p("t", Type::String)],
Type::Named("PJson".into()),
vec![
step("neg", seq(slice(r("t"), num(0), num(1)), lit("-"))),
step("st", iff(r("neg"), num(1), num(0))),
step("en", call("jnum_end", vec![r("t"), r("st")])),
],
jrec(
jv(
"JNum",
vec![(
"n",
ExprSpec::StrToNumber(Box::new(slice(
r("t"),
num(0),
r("en"),
))),
)],
),
restfrom(r("t"), r("en")),
),
);
let json_scalar = pure(
"json_scalar",
vec![p("s", Type::String)],
Type::Named("PJson".into()),
vec![
step("t", call("jdrop_ws", vec![r("s")])),
step("lt", slen(r("t"))),
step(
"c0",
iff(
bop(BinOp::Eq, r("lt"), num(0)),
lit(""),
slice(r("t"), num(0), num(1)),
),
),
],
iff(
bop(BinOp::Eq, r("lt"), num(0)),
jrec(jv("JNull", vec![]), lit("")),
iff(
seq(r("c0"), lit("n")),
jrec(jv("JNull", vec![]), restfrom(r("t"), num(4))),
iff(
seq(r("c0"), lit("t")),
jrec(
jv("JBool", vec![("b", ExprSpec::Bool(true))]),
restfrom(r("t"), num(4)),
),
iff(
seq(r("c0"), lit("f")),
jrec(
jv("JBool", vec![("b", ExprSpec::Bool(false))]),
restfrom(r("t"), num(5)),
),
iff(
seq(r("c0"), lit("\"")),
call("json_str", vec![r("t")]),
call("json_num", vec![r("t")]),
),
),
),
),
),
);
let jnil = || ExprSpec::ListEmpty { elem: json_ty() };
let enil = || ExprSpec::ListEmpty {
elem: Type::Named("JEntry".into()),
};
let jcons = |h: ExprSpec, t: ExprSpec| ExprSpec::ListCons {
head: Box::new(h),
tail: Box::new(t),
};
let parr = |items: ExprSpec, rest: ExprSpec| ExprSpec::Record {
type_name: "PArr".into(),
fields: vec![("items".into(), items), ("rest".into(), rest)],
};
let pobj = |entries: ExprSpec, rest: ExprSpec| ExprSpec::Record {
type_name: "PObj".into(),
fields: vec![("entries".into(), entries), ("rest".into(), rest)],
};
let pkey = |k: ExprSpec, rest: ExprSpec| ExprSpec::Record {
type_name: "PKey".into(),
fields: vec![("k".into(), k), ("rest".into(), rest)],
};
let mk_entry = |k: ExprSpec, v: ExprSpec| ExprSpec::Record {
type_name: "JEntry".into(),
fields: vec![("key".into(), k), ("val".into(), v)],
};
let head1 = |s: ExprSpec| {
iff(
bop(BinOp::Eq, slen(s.clone()), num(0)),
lit(""),
slice(s, num(0), num(1)),
)
};
let pj = |x: ExprSpec, f: &str| fieldof(x, "PJson", f);
let jrev_at = pure(
"jrev_at",
vec![p("xs", json_list()), p("i", Type::Number)],
json_list(),
vec![],
iff(
bop(BinOp::Lt, r("i"), num(0)),
jnil(),
jcons(
lget(r("xs"), r("i")),
call("jrev_at", vec![r("xs"), sub(r("i"), num(1))]),
),
),
);
let jrev = pure(
"jrev",
vec![p("xs", json_list())],
json_list(),
vec![],
call("jrev_at", vec![r("xs"), sub(llen(r("xs")), num(1))]),
);
let jrev_eat = pure(
"jrev_eat",
vec![p("xs", entry_list()), p("i", Type::Number)],
entry_list(),
vec![],
iff(
bop(BinOp::Lt, r("i"), num(0)),
enil(),
ExprSpec::ListCons {
head: Box::new(lget(r("xs"), r("i"))),
tail: Box::new(call(
"jrev_eat",
vec![r("xs"), sub(r("i"), num(1))],
)),
},
),
);
let jrev_e = pure(
"jrev_e",
vec![p("xs", entry_list())],
entry_list(),
vec![],
call("jrev_eat", vec![r("xs"), sub(llen(r("xs")), num(1))]),
);
let jkey = pure(
"jkey",
vec![p("s", Type::String)],
Type::Named("PKey".into()),
vec![
step("close", call("jclose", vec![r("s"), num(1)])),
step("raw", slice(r("s"), num(1), sub(r("close"), num(1)))),
step(
"resti",
iff(
bop(BinOp::Gt, add1(r("close")), slen(r("s"))),
slen(r("s")),
add1(r("close")),
),
),
],
pkey(
call("junescape", vec![r("raw")]),
restfrom(r("s"), r("resti")),
),
);
let arr_loop = pure(
"arr_loop",
vec![p("s", Type::String), p("acc", json_list())],
Type::Named("PArr".into()),
vec![
step("s2", call("jdrop_ws", vec![r("s")])),
step("l", slen(r("s2"))),
step("c", head1(r("s2"))),
],
iff(
bop(BinOp::Eq, r("l"), num(0)),
parr(call("jrev", vec![r("acc")]), lit("")),
iff(
seq(r("c"), lit("]")),
parr(
call("jrev", vec![r("acc")]),
restfrom(r("s2"), num(1)),
),
call("arr_elem", vec![r("s2"), r("acc")]),
),
),
);
let arr_elem = pure(
"arr_elem",
vec![p("s2", Type::String), p("acc", json_list())],
Type::Named("PArr".into()),
vec![
step("pv", call("json_parse", vec![r("s2")])),
step("af", call("jdrop_ws", vec![pj(r("pv"), "rest")])),
step("ac", head1(r("af"))),
step("acc2", jcons(pj(r("pv"), "val"), r("acc"))),
],
iff(
seq(r("ac"), lit(",")),
call(
"arr_loop",
vec![restfrom(r("af"), num(1)), r("acc2")],
),
iff(
seq(r("ac"), lit("]")),
parr(
call("jrev", vec![r("acc2")]),
restfrom(r("af"), num(1)),
),
parr(call("jrev", vec![r("acc2")]), r("af")),
),
),
);
let json_arr = pure(
"json_arr",
vec![p("t", Type::String)],
Type::Named("PJson".into()),
vec![step(
"pa",
call("arr_loop", vec![restfrom(r("t"), num(1)), jnil()]),
)],
jrec(
jv(
"JArr",
vec![("items", fieldof(r("pa"), "PArr", "items"))],
),
fieldof(r("pa"), "PArr", "rest"),
),
);
let obj_loop = pure(
"obj_loop",
vec![p("s", Type::String), p("acc", entry_list())],
Type::Named("PObj".into()),
vec![
step("s2", call("jdrop_ws", vec![r("s")])),
step("l", slen(r("s2"))),
step("c", head1(r("s2"))),
],
iff(
bop(BinOp::Eq, r("l"), num(0)),
pobj(call("jrev_e", vec![r("acc")]), lit("")),
iff(
seq(r("c"), lit("}")),
pobj(
call("jrev_e", vec![r("acc")]),
restfrom(r("s2"), num(1)),
),
call("obj_elem", vec![r("s2"), r("acc")]),
),
),
);
let obj_elem = pure(
"obj_elem",
vec![p("s2", Type::String), p("acc", entry_list())],
Type::Named("PObj".into()),
vec![
step("ke", call("jkey", vec![r("s2")])),
step(
"s3",
call("jdrop_ws", vec![fieldof(r("ke"), "PKey", "rest")]),
),
step("c3", head1(r("s3"))),
step(
"s4",
iff(
seq(r("c3"), lit(":")),
restfrom(r("s3"), num(1)),
r("s3"),
),
),
step("pv", call("json_parse", vec![r("s4")])),
step("af", call("jdrop_ws", vec![pj(r("pv"), "rest")])),
step("ac", head1(r("af"))),
step(
"acc2",
ExprSpec::ListCons {
head: Box::new(mk_entry(
fieldof(r("ke"), "PKey", "k"),
pj(r("pv"), "val"),
)),
tail: Box::new(r("acc")),
},
),
],
iff(
seq(r("ac"), lit(",")),
call(
"obj_loop",
vec![restfrom(r("af"), num(1)), r("acc2")],
),
iff(
seq(r("ac"), lit("}")),
pobj(
call("jrev_e", vec![r("acc2")]),
restfrom(r("af"), num(1)),
),
pobj(call("jrev_e", vec![r("acc2")]), r("af")),
),
),
);
let json_obj = pure(
"json_obj",
vec![p("t", Type::String)],
Type::Named("PJson".into()),
vec![step(
"pa",
call("obj_loop", vec![restfrom(r("t"), num(1)), enil()]),
)],
jrec(
jv(
"JObj",
vec![("entries", fieldof(r("pa"), "PObj", "entries"))],
),
fieldof(r("pa"), "PObj", "rest"),
),
);
let json_parse = pure(
"json_parse",
vec![p("s", Type::String)],
Type::Named("PJson".into()),
vec![
step("t", call("jdrop_ws", vec![r("s")])),
step("lt", slen(r("t"))),
step("c0", head1(r("t"))),
],
iff(
bop(BinOp::Eq, r("lt"), num(0)),
jrec(jv("JNull", vec![]), lit("")),
iff(
seq(r("c0"), lit("[")),
call("json_arr", vec![r("t")]),
iff(
seq(r("c0"), lit("{")),
call("json_obj", vec![r("t")]),
call("json_scalar", vec![r("t")]),
),
),
),
);
vec![
esc_one,
esc_at,
json_escape,
sj_arr,
sj_entry,
sj_obj,
json_stringify,
j_is_ws,
jws_at,
jdrop_ws,
jclose,
un_one,
un_at,
junescape,
jnum_end,
json_str,
json_num,
json_scalar,
jrev_at,
jrev,
jrev_eat,
jrev_e,
jkey,
arr_loop,
arr_elem,
json_arr,
obj_loop,
obj_elem,
json_obj,
json_parse,
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::edit::{Editor, ModuleSpec};
use crate::store::Store;
use crate::wasm::{lower, run_i64};
fn jvar(case: &str, fields: Vec<(&str, ExprSpec)>) -> ExprSpec {
ExprSpec::Variant {
type_name: "Json".into(),
case: case.into(),
fields: fields.into_iter().map(|(n, e)| (n.into(), e)).collect(),
}
}
fn jentry(k: &str, v: ExprSpec) -> ExprSpec {
ExprSpec::Record {
type_name: "JEntry".into(),
fields: vec![("key".into(), lit(k)), ("val".into(), v)],
}
}
#[test]
fn json_stringify_formats() {
let jnull = || jvar("JNull", vec![]);
let jbool = |b: bool| jvar("JBool", vec![("b", ExprSpec::Bool(b))]);
let jnum = |n: i64| jvar("JNum", vec![("n", num(n))]);
let jstr = |s: &str| jvar("JStr", vec![("s", lit(s))]);
let jarr = |items: Vec<ExprSpec>| {
jvar("JArr", vec![("items", ExprSpec::List(items))])
};
let jarr0 = || {
jvar(
"JArr",
vec![("items", ExprSpec::ListEmpty { elem: json_ty() })],
)
};
let jobj = |es: Vec<ExprSpec>| {
jvar("JObj", vec![("entries", ExprSpec::List(es))])
};
let probe = |name: &str, j: 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(call("json_stringify", vec![j])),
Box::new(lit(want)),
),
};
let cases = vec![
probe("s_null", jnull(), "null"),
probe("s_true", jbool(true), "true"),
probe("s_false", jbool(false), "false"),
probe("s_num", jnum(42), "42"),
probe("s_neg", jnum(-7), "-7"),
probe("s_str", jstr("hi"), "\"hi\""),
probe(
"s_esc",
jstr("a\"b\\c\nd"),
"\"a\\\"b\\\\c\\nd\"",
),
probe("s_arr0", jarr0(), "[]"),
probe(
"s_arr",
jarr(vec![jnum(1), jnum(2), jstr("x")]),
"[1,2,\"x\"]",
),
probe(
"s_obj",
jobj(vec![
jentry("a", jnum(1)),
jentry("b", jbool(true)),
]),
"{\"a\":1,\"b\":true}",
),
probe(
"s_nest",
jarr(vec![jobj(vec![jentry(
"k",
jarr(vec![jnull()]),
)])]),
"[{\"k\":[null]}]",
),
];
let names: Vec<String> =
cases.iter().map(|f| f.name.clone()).collect();
let mut fns = functions();
fns.extend(cases);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "jsonstr".into(),
types: types(),
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for n in names {
assert_eq!(run_i64(&wasm, &n, &[]).unwrap(), 1, "{n}");
}
}
#[test]
fn json_parse_scalars() {
let probe =
|name: &str, input: &str, want: &str, rest: &str| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: ExprSpec::BinOp {
op: BinOp::And,
lhs: Box::new(ExprSpec::StrEq(
Box::new(call(
"json_stringify",
vec![fieldof(
call("json_scalar", vec![lit(input)]),
"PJson",
"val",
)],
)),
Box::new(lit(want)),
)),
rhs: Box::new(ExprSpec::StrEq(
Box::new(fieldof(
call("json_scalar", vec![lit(input)]),
"PJson",
"rest",
)),
Box::new(lit(rest)),
)),
},
};
let cases = vec![
probe("p_null", "null", "null", ""),
probe("p_ws_true", " true ", "true", " "),
probe("p_false", "false,", "false", ","),
probe("p_num", "42 x", "42", " x"),
probe("p_neg", "-7]", "-7", "]"),
probe("p_str", "\"hi\"z", "\"hi\"", "z"),
probe(
"p_esc",
"\"a\\\"b\\\\c\\nd\"",
"\"a\\\"b\\\\c\\nd\"",
"",
),
probe("p_empty", "", "null", ""),
probe("p_true_eof", "true", "true", ""),
];
let names: Vec<String> =
cases.iter().map(|f| f.name.clone()).collect();
let mut fns = functions();
fns.extend(cases);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "jsonpar".into(),
types: types(),
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for n in names {
assert_eq!(run_i64(&wasm, &n, &[]).unwrap(), 1, "{n}");
}
}
#[test]
fn json_parse_round_trips() {
let probe = |name: &str, input: &str, canon: &str| FunctionSpec {
name: name.into(),
type_params: vec![],
params: vec![],
produces: ext(Type::Bool),
requires: BTreeSet::new(),
on_failure: vec![],
steps: vec![],
result: ExprSpec::BinOp {
op: BinOp::And,
lhs: Box::new(ExprSpec::StrEq(
Box::new(call(
"json_stringify",
vec![fieldof(
call("json_parse", vec![lit(input)]),
"PJson",
"val",
)],
)),
Box::new(lit(canon)),
)),
rhs: Box::new(ExprSpec::StrEq(
Box::new(fieldof(
call("json_parse", vec![lit(input)]),
"PJson",
"rest",
)),
Box::new(lit("")),
)),
},
};
let cases = vec![
probe("a_empty", "[]", "[]"),
probe("a_nums", "[1,2,3]", "[1,2,3]"),
probe("a_ws", "[ 1, 2 , 3 ]", "[1,2,3]"),
probe("a_mixed", "[1,true,null,\"x\"]", "[1,true,null,\"x\"]"),
probe("o_empty", "{}", "{}"),
probe(
"o_two",
"{\"a\":1,\"b\":true}",
"{\"a\":1,\"b\":true}",
),
probe(
"o_ws",
"{ \"a\" : 1 , \"b\" : \"x\" }",
"{\"a\":1,\"b\":\"x\"}",
),
probe(
"nest1",
"[{\"k\":[null,false]},42]",
"[{\"k\":[null,false]},42]",
),
probe(
"nest2",
"{\"a\":{\"b\":[1,{\"c\":\"d\"}]}}",
"{\"a\":{\"b\":[1,{\"c\":\"d\"}]}}",
),
probe(
"esc_in_arr",
"[\"a\\\"b\",\"c\\\\d\"]",
"[\"a\\\"b\",\"c\\\\d\"]",
),
probe("scalar_via_parse", " -7 ", "-7"),
];
let cases: Vec<FunctionSpec> = cases
.into_iter()
.map(|f| {
if f.name == "scalar_via_parse" {
FunctionSpec {
result: ExprSpec::StrEq(
Box::new(call(
"json_stringify",
vec![fieldof(
call(
"json_parse",
vec![lit(" -7 ")],
),
"PJson",
"val",
)],
)),
Box::new(lit("-7")),
),
..f
}
} else {
f
}
})
.collect();
let names: Vec<String> =
cases.iter().map(|f| f.name.clone()).collect();
let mut fns = functions();
fns.extend(cases);
let e = Editor::new(Store::open_in_memory().unwrap());
let (m, report) = e
.apply_module(&ModuleSpec {
name: "jsonrt".into(),
types: types(),
functions: fns,
})
.unwrap();
assert!(report.ok(), "violations: {:?}", report.violations);
let wasm = lower(e.store(), &m).unwrap();
for n in names {
assert_eq!(run_i64(&wasm, &n, &[]).unwrap(), 1, "{n}");
}
}
}