use core::f32;
use std::{
net::IpAddr,
sync::{
Arc,
atomic::{AtomicI32, AtomicUsize, Ordering},
},
};
use crate::{
Context, FileTree, List, NoCtx, Runtime,
file_tree::FileSpec,
library,
pipeline::Package,
runtime::OptCtx,
source_file, src,
value::{RotoString, Val, Verdict},
};
use inetnum::{addr::Prefix, asn::Asn};
#[track_caller]
fn compile(f: FileTree) -> Package<NoCtx> {
let runtime = Runtime::new();
compile_with_runtime(f, runtime)
}
#[track_caller]
fn compile_with_runtime<Ctx: OptCtx>(
f: FileTree,
runtime: Runtime<Ctx>,
) -> Package<Ctx> {
#[cfg(feature = "logger")]
let _ = env_logger::try_init();
let res = f.parse().and_then(|x| x.typecheck(&runtime)).map(|x| {
let x = x.lower_to_mir().lower_to_lir();
x.codegen()
});
match res {
Ok(x) => x,
Err(err) => {
println!("{err}");
panic!("Compilation error: see above");
}
}
}
#[test]
fn unit() {
let s = src!(
"
fn unit_expression() {
if true {
()
} else {
()
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> ()>("unit_expression")
.expect("No function found (or mismatched types)");
f.call();
}
#[test]
fn unit_type() {
let s = src!(
"
fn unit_type() -> () {
let x: () = ();
x
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> ()>("unit_type")
.expect("No function found (or mismatched types)");
f.call();
}
#[test]
fn accept() {
let s = src!(
"
filtermap main() {
accept
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, Verdict::Accept(()));
}
#[test]
fn accept_with_semicolon() {
let s = src!(
"
filtermap main() {
accept;
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, Verdict::Accept(()));
}
#[test]
fn reject() {
let s = src!(
"
filtermap main() {
reject
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, Verdict::Reject(()));
}
#[test]
fn equal_to_10() {
let s = src!(
"
filtermap main(x: u32) {
if x == 10 {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(u32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(5);
assert_eq!(res, Verdict::Reject(()));
let res = f.call(10);
assert_eq!(res, Verdict::Accept(()));
}
#[test]
fn equal_to_10_with_function() {
let s = src!(
"
fn is_10(x: i32) -> bool {
x == 10
}
filtermap main(x: i32) {
if is_10(x) {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(5);
assert_eq!(res, Verdict::Reject(()));
let res = f.call(10);
assert_eq!(res, Verdict::Accept(()));
}
#[test]
fn equal_to_10_with_two_functions() {
let s = src!(
"
fn equals(x: u32, y: u32) -> bool {
x == y
}
fn is_10(x: u32) -> bool {
equals(x, 10)
}
filtermap main(x: u32) {
if is_10(x) {
accept
} else if equals(x, 20) {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(u32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
assert_eq!(f.call(5), Verdict::Reject(()));
assert_eq!(f.call(10), Verdict::Accept(()));
assert_eq!(f.call(15), Verdict::Reject(()));
assert_eq!(f.call(20), Verdict::Accept(()));
}
#[test]
fn negated_literal() {
let s = src!(
"
fn negate() -> i32 {
-5
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> i32>("negate")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, -5)
}
#[test]
fn negation() {
let s = src!(
"
fn negate(x: i32) -> i32 {
-x
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> i32>("negate")
.expect("No function found (or mismatched types)");
let res = f.call(5);
assert_eq!(res, -5)
}
#[test]
fn inversion() {
let s = src!(
"
filtermap main(x: i32) {
if !(x == 10) {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
for x in 0..20 {
let res = f.call(x);
let exp = if x != 10 {
Verdict::Accept(())
} else {
Verdict::Reject(())
};
assert_eq!(res, exp, "{x}");
}
}
#[test]
fn not_not() {
let s = src!(
"
fn main(x: i32) -> bool {
!!(x == 10)
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> bool>("main")
.expect("No function found (or mismatched types)");
for x in 0..20 {
let res = f.call(x);
assert_eq!(res, x == 10, "{x}");
}
}
#[test]
fn a_bunch_of_comparisons() {
let s = src!(
"
filtermap main(x: i32) {
if (
(x > 10 && x < 20)
|| (x >= 30 && x <= 40)
|| x == 55
){
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
for x in 0..100 {
#[allow(clippy::manual_range_contains)]
let expected =
if (x > 10 && x < 20) || (x >= 30 && x <= 40) || x == 55 {
Verdict::Accept(())
} else {
Verdict::Reject(())
};
let res = f.call(x);
assert_eq!(res, expected);
}
}
#[test]
fn record() {
let s = src!(
"
record Foo { a: i32, b: i32 }
filtermap main(x: i32) {
let foo = Foo { a: x, b: 20 };
if foo.a == foo.b {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
for x in 0..100 {
let expected = if x == 20 {
Verdict::Accept(())
} else {
Verdict::Reject(())
};
let res = f.call(x);
assert_eq!(res, expected);
}
}
#[test]
fn record_with_fields_flipped() {
let s = src!(
"
record Foo { a: i32, b: i32 }
filtermap main(x: i32) {
// These are flipped, to ensure that the order in which
// the fields are given doesn't matter:
let foo = Foo { b: 20, a: x };
if foo.a == foo.b {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)")
.into_func();
for x in 0..100 {
let expected = if x == 20 {
Verdict::Accept(())
} else {
Verdict::Reject(())
};
let res = f(x);
assert_eq!(res, expected);
}
}
#[test]
fn nested_record() {
let s = src!(
"
record Foo { x: Bar, y: Bar }
record Bar { a: i32, b: i32 }
filtermap main(x: i32) {
let bar = Bar { a: 20, b: x };
let foo = Foo { x: bar, y: bar };
if foo.x.a == foo.y.b {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
for x in 0..100 {
let expected = if x == 20 {
Verdict::Accept(())
} else {
Verdict::Reject(())
};
let res = f.call(x);
assert_eq!(res, expected, "for {x}");
}
}
#[test]
fn misaligned_fields() {
let s = src!(
"
record Foo { a: i16, b: i32 }
filtermap main(x: i32) {
let foo = Foo { a: 10, b: x };
if foo.b == 20 {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
for x in 0..100 {
let expected = if x == 20 {
Verdict::Accept(())
} else {
Verdict::Reject(())
};
let res = f.call(x);
assert_eq!(res, expected, "for {x}");
}
}
#[test]
fn arithmetic() {
let s = src!(
"
filtermap main(x: i32) {
if x + 10 * 20 < 250 {
accept
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(5);
assert_eq!(res, Verdict::Accept(()));
let res = f.call(20);
assert_eq!(res, Verdict::Accept(()));
let res = f.call(100);
assert_eq!(res, Verdict::Reject(()));
}
#[test]
fn call_runtime_function() {
let s = src!(
"
filtermap main(x: u32) {
if pow(x, 2) > 100 {
accept
} else {
reject
}
}
"
);
let rt = Runtime::from_lib(library! {
fn pow(x: u32, y: u32) -> u32 {
x.pow(y)
}
})
.unwrap();
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(u32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
for (value, expected) in
[(5, Verdict::Reject(())), (11, Verdict::Accept(()))]
{
let res = f.call(value);
assert_eq!(res, expected);
}
}
#[test]
fn call_runtime_method() {
let s = src!(
"
filtermap main(x: u32) {
if x.is_even() {
accept
} else {
reject
}
}
"
);
let rt = Runtime::from_lib(library! {
impl u32 {
fn is_even(self) -> bool {
self.is_multiple_of(2)
}
}
})
.unwrap();
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(u32) -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
for (value, expected) in
[(5, Verdict::Reject(())), (10, Verdict::Accept(()))]
{
let res = f.call(value);
assert_eq!(res, expected);
}
}
#[test]
fn int_var() {
let s = src!(
"
filtermap main() {
accept 32
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<i32, ()>>("main")
.expect("No function found (or mismatched types)");
assert_eq!(f.call(), Verdict::Accept(32));
}
#[test]
fn issue_52() {
#[derive(Clone, PartialEq, Eq)]
struct Foo {
_x: i32,
}
let rt = Runtime::from_lib(library! {
#[clone] type Foo = Val<Foo>;
impl Val<Foo> {
fn bar(_x: u32) -> u32 {
2
}
}
})
.unwrap();
let s = src!(
"
filtermap main(foo: Foo) {
Foo.bar(1);
accept
}
"
);
let _p = compile_with_runtime(s, rt);
}
#[test]
fn register_with_non_registered_type() {
#[derive(Clone, PartialEq, Eq)]
struct Foo {
_x: i32,
}
Runtime::from_lib(library! {
fn bar(_foo: Val<Foo>, _x: u32) {}
})
.unwrap_err();
}
#[test]
fn asn() {
let s = src!(
"
filtermap main(x: Asn) {
if x == AS1000 {
accept x
} else {
reject x
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(Asn) -> Verdict<Asn, Asn>>("main")
.expect("No function found (or mismatched types)");
assert_eq!(
f.call(Asn::from_u32(1000)),
Verdict::Accept(Asn::from_u32(1000))
);
assert_eq!(
f.call(Asn::from_u32(2000)),
Verdict::Reject(Asn::from_u32(2000))
);
}
#[test]
fn mismatched_types() {
let s = src!(
"
filtermap main(x: i32) {
accept x
}
"
);
let mut p = compile(s);
let err = p
.get_function::<fn(i8) -> Verdict<i8, ()>>("main")
.unwrap_err();
eprintln!("{err}");
assert!(err.to_string().contains("do not match"));
}
#[test]
fn multiply() {
let s = src!(
"
filtermap main(x: u8) {
if x > 10 {
accept 2 * x
} else {
reject
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(u8) -> Verdict<u8, ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(20);
assert_eq!(res, Verdict::Accept(40));
}
#[test]
fn remainder() {
let s = src!(
"
fn remainder(x: u64, y: u64) -> u64 {
while x > y {
x = x - y;
}
x
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(u64, u64) -> u64>("remainder")
.expect("No function found (or mismatched types)");
let res = f.call(55, 10);
assert_eq!(res, 5);
}
#[test]
fn modulo_signed() {
let s = src!(
"
fn mod(x: i8, y: i8) -> i8 {
x % y
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i8, i8) -> i8>("mod")
.expect("No function found (or mismatched types)");
for i in i8::MIN..i8::MAX {
for j in i8::MIN..i8::MAX {
if j == 0 {
continue;
}
let out = i.wrapping_rem(j);
let res = f.call(i, j);
assert_eq!(res, out);
}
}
}
#[test]
fn modulo_unsigned() {
let s = src!(
"
fn mod(x: u8, y: u8) -> u8 {
x % y
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(u8, u8) -> u8>("mod")
.expect("No function found (or mismatched types)");
for i in u8::MIN..u8::MAX {
for j in u8::MIN..u8::MAX {
if j == 0 {
continue;
}
let out = i % j;
let res = f.call(i, j);
assert_eq!(res, out);
}
}
}
#[test]
fn factorial() {
let s = src!(
"
fn factorial(x: u64) -> u64 {
let i = 1;
let n = 1;
while i <= x {
n = n * i;
i = i + 1;
}
n
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(u64) -> u64>("factorial")
.expect("No function found (or mismatched types)");
let res = f.call(5);
assert_eq!(res, 120);
}
#[test]
fn nested_while_loop() {
let s = src!(
"
fn nested_while_loop() -> u64 {
let res = 0;
let x = 0;
while x < 10 {
let y = 0;
while y < 10 {
res = res + 1;
y = y + 1;
}
x = x + 1;
}
res
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> u64>("nested_while_loop")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, 100);
}
#[test]
fn repeat_string_manually() {
let s = src!(
"
fn repeat(s: String, n: u64) -> String {
let res = \"\";
while n > 0 {
res = res + s;
n = n - 1;
}
res
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString, u64) -> RotoString>("repeat")
.expect("No function found (or mismatched types)");
let res = f.call("foo".into(), 6);
assert_eq!(res, "foofoofoofoofoofoo".into());
}
#[test]
fn float_mul() {
let s = src!(
"
filtermap main(x: f32) {
accept 2.0 * x
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> Verdict<f32, ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(20.0);
assert_eq!(res, Verdict::Accept(40.0));
}
#[test]
fn float_add() {
let s = src!(
"
filtermap main(x: f32) {
accept 2.0 + x
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> Verdict<f32, ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(20.0);
assert_eq!(res, Verdict::Accept(22.0));
}
#[test]
fn float_sub() {
let s = src!(
"
filtermap main(x: f32) {
accept 20.0 - x
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> Verdict<f32, ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(2.0);
assert_eq!(res, Verdict::Accept(18.0));
}
#[test]
fn float_cmp() {
let s = src!(
"
filtermap main(x: f32) {
accept x == 20.0
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> Verdict<bool, ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(20.0);
assert_eq!(res, Verdict::Accept(true));
}
#[test]
fn float_div_zero() {
let s = src!(
"
filtermap main(x: f32) {
accept x / 0.0
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> Verdict<f32, ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(20.0);
assert_eq!(res, Verdict::Accept(f32::INFINITY));
let res = f.call(-20.0);
assert_eq!(res, Verdict::Accept(-f32::INFINITY));
let Verdict::Accept(res) = f.call(0.0) else {
panic!("should have returned accept")
};
assert!(res.is_nan());
}
#[test]
fn float_floor() {
let s = src!("fn floor(x: f32) -> f32 { x.floor() }");
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> f32>("floor")
.expect("No function found (or mismatched types)");
let res = f.call(20.5);
assert_eq!(res, 20.0);
let res = f.call(-20.5);
assert_eq!(res, -21.0);
}
#[test]
fn float_ceil() {
let s = src!("fn ceil(x: f32) -> f32 { x.ceil() }");
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> f32>("ceil")
.expect("No function found (or mismatched types)");
let res = f.call(20.5);
assert_eq!(res, 21.0);
let res = f.call(-20.5);
assert_eq!(res, -20.0);
}
#[test]
fn float_round() {
let s = src!("fn round(x: f32) -> f32 { x.round() }");
let mut p = compile(s);
let f = p
.get_function::<fn(f32) -> f32>("round")
.expect("No function found (or mismatched types)");
let res = f.call(20.6);
assert_eq!(res, 21.0);
let res = f.call(20.4);
assert_eq!(res, 20.0);
}
#[test]
fn float_pow() {
let s = src!("fn pow(x: f32, y: f32) -> f32 { x.pow(y) }");
let mut p = compile(s);
let f = p
.get_function::<fn(f32, f32) -> f32>("pow")
.expect("No function found (or mismatched types)");
let res = f.call(2.0, 2.0);
assert_eq!(res, 4.0);
let res = f.call(25.0, 0.5);
assert_eq!(res, 5.0);
}
#[test]
fn float_scientific_notation_one() {
let s = src!("fn main() -> f32 { 20.0e4 }");
let mut p = compile(s);
let f = p
.get_function::<fn() -> f32>("main")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, 20.0e4);
}
#[test]
fn float_scientific_notation_two() {
let s = src!("fn main() -> f32 { 20.0e-4 }");
let mut p = compile(s);
let f = p
.get_function::<fn() -> f32>("main")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, 20.0e-4);
}
#[test]
fn float_add_f64() {
let s = src!(
"
filtermap main(x: f64) {
accept x + 20.0
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(f64) -> Verdict<f64, ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call(20.0);
assert_eq!(res, Verdict::Accept(40.0));
}
#[test]
fn ip_output() {
let s = src!(
"
filtermap main() {
accept 1.2.3.4
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<IpAddr, ()>>("main")
.expect("No function found (or mismatched types)");
let ip = IpAddr::from([1, 2, 3, 4]);
let res = f.call();
assert_eq!(res, Verdict::Accept(ip));
}
#[test]
fn ip_passthrough() {
let s = src!(
"
filtermap main(x: IpAddr) {
accept x
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(IpAddr) -> Verdict<IpAddr, ()>>("main")
.expect("No function found (or mismatched types)");
let ip = IpAddr::from([1, 2, 3, 4]);
let res = f.call(ip);
assert_eq!(res, Verdict::Accept(ip));
}
#[test]
fn ipv4_compare() {
let s = src!(
"
filtermap main(x: IpAddr) {
if x == 0.0.0.0 {
accept x
} else if x == 192.168.0.0 {
accept x
} else {
reject x
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(IpAddr) -> Verdict<IpAddr, IpAddr>>("main")
.expect("No function found (or mismatched types)");
let ip = IpAddr::from([0, 0, 0, 0]);
let res = f.call(ip);
assert_eq!(res, Verdict::Accept(ip));
let ip = IpAddr::from([192, 168, 0, 0]);
let res = f.call(ip);
assert_eq!(res, Verdict::Accept(ip));
let ip = IpAddr::from([1, 2, 3, 4]);
let res = f.call(ip);
assert_eq!(res, Verdict::Reject(ip));
let ip = IpAddr::from([0, 0, 0, 0, 0, 0, 0, 0]);
let res = f.call(ip);
assert_eq!(res, Verdict::Reject(ip));
}
#[test]
fn ipv6_compare() {
let s = src!(
"
filtermap main(x: IpAddr) {
if x == :: {
accept x
} else if x == 192.168.0.0 {
accept x
} else if x == ::1 {
accept x
} else {
reject x
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(IpAddr) -> Verdict<IpAddr, IpAddr>>("main")
.expect("No function found (or mismatched types)");
let ip = IpAddr::from([0, 0, 0, 0, 0, 0, 0, 0]);
let res = f.call(ip);
assert_eq!(res, Verdict::Accept(ip));
let ip = IpAddr::from([0, 0, 0, 0, 0, 0, 0, 1]);
let res = f.call(ip);
assert_eq!(res, Verdict::Accept(ip));
let ip = IpAddr::from([192, 168, 0, 0]);
let res = f.call(ip);
assert_eq!(res, Verdict::Accept(ip));
let ip = IpAddr::from([1, 2, 3, 4]);
let res = f.call(ip);
assert_eq!(res, Verdict::Reject(ip));
}
#[test]
fn construct_prefix() {
let s = src!(
"
filtermap main() {
accept 192.168.0.0 / 16
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<Prefix, ()>>("main")
.expect("No function found (or mismatched types)");
let p = Prefix::new("192.168.0.0".parse().unwrap(), 16).unwrap();
let res = f.call();
assert_eq!(res, Verdict::Accept(p));
}
#[test]
fn function_returning_unit() {
let rt = Runtime::from_lib(library! {
fn unit_unit() {}
})
.unwrap();
let s = src!(
"
filtermap main() {
accept unit_unit()
}
"
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn() -> Verdict<(), ()>>("main")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, Verdict::Accept(()));
}
#[test]
fn to_string() {
let s = src!(
r#"
fn foo() -> String {
let a: u8 = 10;
let b: i32 = 20;
let c: f64 = 15.5;
let d = false;
let e = 1.1.1.1;
let f = 1.1.0.0 / 16;
let g = AS1000;
let h = "foo";
a.to_string()
+ " "
+ b.to_string()
+ " "
+ c.to_string()
+ " "
+ d.to_string()
+ " "
+ e.to_string()
+ " "
+ f.to_string()
+ " "
+ g.to_string()
+ " "
+ h.to_string()
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "10 20 15.5 false 1.1.1.1 1.1.0.0/16 AS1000 foo".into());
}
#[test]
fn simple_f_string() {
let s = src!(
r#"
fn foo(name: String) -> String {
f"Hello {name}!"
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> RotoString>("foo")
.unwrap();
let res = f.call("John".into());
assert_eq!(res, "Hello John!".into());
}
#[test]
fn simple_f_string_number() {
let s = src!(
r#"
fn foo(x: i32) -> String {
f"Hello {x}!"
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn(i32) -> RotoString>("foo").unwrap();
let res = f.call(10);
assert_eq!(res, "Hello 10!".into());
}
#[test]
fn complex_f_string() {
let s = src!(
r#"
fn foo() -> String {
f"This is a string with { f"another string which prints {true}" }, isn't that wonderful?"
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "This is a string with another string which prints true, isn't that wonderful?".into());
}
#[test]
fn escape_curly_in_f_string() {
let s = src!(
r#"
fn foo() -> String {
f"Here is a single curly {{ and a closing one }}"
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "Here is a single curly { and a closing one }".into());
}
#[test]
fn unicode_val_in_f_string() {
let s = src!(
r#"
fn foo() -> String {
f"Here is an uppercase \u{41} and a lowercase \u{61}."
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "Here is an uppercase A and a lowercase a.".into());
}
#[test]
fn f_string_and_int_var_1() {
let s = src!(
r#"
fn foo() -> String {
let x = 10;
f"This should just work: {x}"
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "This should just work: 10".into());
}
#[test]
fn f_string_and_int_var_2() {
let s = src!(
r#"
fn foo() -> String {
let x = 10;
let s = f"This should just work: {x}";
let y: u8 = x;
s
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "This should just work: 10".into());
}
#[test]
fn f_string_and_float_var_1() {
let s = src!(
r#"
fn foo() -> String {
let x = 10.0;
f"This should just work: {x}"
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "This should just work: 10".into());
}
#[test]
fn f_string_and_float_var_2() {
let s = src!(
r#"
fn foo() -> String {
let x = 10.0;
let s = f"This should just work: {x}";
let y: f32 = x;
s
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("foo").unwrap();
let res = f.call();
assert_eq!(res, "This should just work: 10".into());
}
#[test]
fn arc_type() {
use std::sync::atomic::Ordering;
static CLONES: AtomicUsize = AtomicUsize::new(0);
static DROPS: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
struct CloneDrop {
clones: &'static AtomicUsize,
drops: &'static AtomicUsize,
}
impl PartialEq for CloneDrop {
fn eq(&self, _other: &Self) -> bool {
false
}
}
impl Clone for CloneDrop {
fn clone(&self) -> Self {
self.clones.fetch_add(1, Ordering::Relaxed);
Self {
clones: self.clones,
drops: self.drops,
}
}
}
impl Drop for CloneDrop {
fn drop(&mut self) {
self.drops.fetch_add(1, Ordering::Relaxed);
}
}
let rt = Runtime::from_lib(library! {
#[clone] type CloneDrop = Val<CloneDrop>;
})
.unwrap();
let s = src!(
"
filtermap main(choose: bool, x: CloneDrop, y: CloneDrop) {
if choose {
accept x
} else {
accept y
}
}
"
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(
bool,
Val<CloneDrop>,
Val<CloneDrop>,
) -> Verdict<Val<CloneDrop>, ()>>("main")
.unwrap();
let input = CloneDrop {
clones: &CLONES,
drops: &DROPS,
};
println!("{input:?}");
let output = f.call(true, Val(input.clone()), Val(input));
let output = output.into_result().unwrap().0;
println!("{output:?}");
assert_eq!(
output.clones.load(Ordering::Relaxed),
output.drops.load(Ordering::Relaxed)
);
}
#[test]
fn use_constant() {
let s = src!(
"
filtermap main() {
let safi = 127.0.0.1;
if safi == IpAddr.LOCALHOSTV4 {
reject
}
accept
}"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> Verdict<(), ()>>("main").unwrap();
let output = f.call();
assert_eq!(output, Verdict::Reject(()));
}
#[test]
fn import_associated_constant() {
let s = src!(
"
import IpAddr.LOCALHOSTV4;
filtermap main() {
let safi = 127.0.0.1;
if safi == LOCALHOSTV4 {
reject
}
accept
}"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> Verdict<(), ()>>("main").unwrap();
let output = f.call();
assert_eq!(output, Verdict::Reject(()));
}
#[test]
fn registered_constant() {
let rt = Runtime::from_lib(library! {
const PI: f64 = std::f64::consts::PI;
})
.unwrap();
let s = src!(
"
fn circumference(radius: f64) -> f64 {
2.0 * PI * radius
}"
);
let mut p = compile_with_runtime(s, rt);
let f = p.get_function::<fn(f64) -> f64>("circumference").unwrap();
let output = f.call(2.0);
let exp = 2.0 * std::f64::consts::PI * 2.0;
assert!(exp - 0.1 < output && output < exp + 0.1);
}
#[test]
fn use_context() {
#[derive(Clone, Context)]
struct Ctx {
pub foo: i32,
pub bar: bool,
}
let rt = Runtime::new().with_context_type::<Ctx>().unwrap();
let s = src!(
"
filtermap main() {
if bar {
accept foo + 1
} else {
accept foo
}
}"
);
let mut p = compile_with_runtime(s, rt);
let f = p.get_function::<fn() -> Verdict<i32, ()>>("main").unwrap();
let mut ctx = Ctx { foo: 9, bar: false };
let output = f.call(&mut ctx);
assert_eq!(output, Verdict::Accept(9));
let mut ctx = Ctx { foo: 10, bar: true };
let output = f.call(&mut ctx);
assert_eq!(output, Verdict::Accept(11));
}
#[test]
fn use_a_roto_function() {
let s = src!(
"
fn double(x: i32) -> i32 {
2 * x
}"
);
let mut p = compile(s);
let f = p.get_function::<fn(i32) -> i32>("double").unwrap();
let output = f.call(2);
assert_eq!(output, 4);
let output = f.call(16);
assert_eq!(output, 32);
}
#[test]
fn use_a_test() {
let s = src!(
"
fn double(x: i32) -> i32 {
x // oops! not correct
}
test check_double {
if double(4) != 8 {
reject;
}
if double(16) != 32 {
reject;
}
accept
}
"
);
let mut p = compile(s);
p.run_tests().unwrap_err();
let s = src!(
"
fn double(x: i32) -> i32 {
2 * x
}
test check_double {
if double(4) != 8 {
reject;
}
if double(16) != 32 {
reject;
}
accept
}
"
);
let mut p = compile(s);
p.run_tests().unwrap();
}
#[test]
fn get_tests() {
let s = src!(
"
test check_output {
accept
}
"
);
let mut p = compile(s);
let tests: Vec<_> = p.get_tests().collect();
assert!(!tests.is_empty());
assert_eq!(tests[0].name(), "pkg.check_output");
tests[0].run(&mut NoCtx).unwrap();
}
#[test]
fn get_no_tests() {
let s = src!(
"
fn double(x: i32) -> i32 {
2 * x
}
"
);
let mut p = compile(s);
let tests = p.get_tests();
assert_eq!(tests.count(), 0);
}
#[test]
fn string() {
let s = src!(
r#"
filtermap main() {
accept "hello"
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<RotoString, ()>>("main")
.unwrap();
let res = f.call();
assert_eq!(res, Verdict::Accept("hello".into()));
}
#[test]
fn escape_string() {
let s = src!(
r#"
fn main() -> String {
"\t\tfoo"
}
"#
);
let mut p = compile(s);
let f = p.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "\t\tfoo".into());
}
#[test]
fn string_append() {
let s = src!(
r#"
filtermap main(name: String) {
accept "Hello ".append(name).append("!")
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> Verdict<RotoString, ()>>("main")
.unwrap();
let res = f.call("Martin".into());
assert_eq!(res, Verdict::Accept("Hello Martin!".into()));
}
#[test]
fn string_append_as_function_call() {
let s = src!(
r#"
fn main(name: String) -> String {
String.append("Hello ", name)
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> RotoString>("main")
.unwrap();
let res = f.call("Martin".into());
assert_eq!(res, "Hello Martin".into());
}
#[test]
fn string_append_as_imported_function() {
let s = src!(
r#"
import String.append;
fn main(name: String) -> String {
append("Hello ", name)
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> RotoString>("main")
.unwrap();
let res = f.call("Martin".into());
assert_eq!(res, "Hello Martin".into());
}
#[test]
fn string_plus_operator() {
let s = src!(
r#"
filtermap main(name: String) {
accept "Hello " + name + "!"
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> Verdict<RotoString, ()>>("main")
.unwrap();
let res = f.call("Martin".into());
assert_eq!(res, Verdict::Accept("Hello Martin!".into()));
}
#[test]
fn string_contains() {
let s = src!(
r#"
filtermap main(s: String) {
if "incomprehensibilities".contains(s) {
accept
} else {
reject
}
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> Verdict<(), ()>>("main")
.unwrap();
let res = f.call("incompre".into());
assert_eq!(res, Verdict::Accept(()));
let res = f.call("hensi".into());
assert_eq!(res, Verdict::Accept(()));
let res = f.call("bilities".into());
assert_eq!(res, Verdict::Accept(()));
let res = f.call("nananana".into());
assert_eq!(res, Verdict::Reject(()));
}
#[test]
fn string_starts_with() {
let s = src!(
r#"
filtermap main(s: String) {
if "incomprehensibilities".starts_with(s) {
accept
} else {
reject
}
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> Verdict<(), ()>>("main")
.unwrap();
let res = f.call("incompre".into());
assert_eq!(res, Verdict::Accept(()));
let res = f.call("hensi".into());
assert_eq!(res, Verdict::Reject(()));
let res = f.call("bilities".into());
assert_eq!(res, Verdict::Reject(()));
let res = f.call("nananana".into());
assert_eq!(res, Verdict::Reject(()));
}
#[test]
fn string_ends_with() {
let s = src!(
r#"
filtermap main(s: String) {
if "incomprehensibilities".ends_with(s) {
accept
} else {
reject
}
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> Verdict<(), ()>>("main")
.unwrap();
let res = f.call("incompre".into());
assert_eq!(res, Verdict::Reject(()));
let res = f.call("hensi".into());
assert_eq!(res, Verdict::Reject(()));
let res = f.call("bilities".into());
assert_eq!(res, Verdict::Accept(()));
let res = f.call("nananana".into());
assert_eq!(res, Verdict::Reject(()));
}
#[test]
fn string_to_lowercase_and_uppercase() {
let s = src!(
r#"
filtermap main(lower: bool, s: String) {
if lower {
accept s.to_lowercase()
} else {
accept s.to_uppercase()
}
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(bool, RotoString) -> Verdict<RotoString, ()>>(
"main",
)
.unwrap();
let res = f.call(true, "WHISPER THIS!".into());
assert_eq!(res, Verdict::Accept("whisper this!".into()));
let res = f.call(false, "now shout this!".into());
assert_eq!(res, Verdict::Accept("NOW SHOUT THIS!".into()));
}
#[test]
fn string_repeat() {
let s = src!(
r#"
filtermap main(s: String) {
let exclamation = (s + "!").to_uppercase();
accept (exclamation + " ").repeat(4) + exclamation
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(RotoString) -> Verdict<RotoString, ()>>("main")
.unwrap();
let res = f.call("boo".into());
assert_eq!(res, Verdict::Accept("BOO! BOO! BOO! BOO! BOO!".into()));
}
#[test]
fn match_option_value() {
let s = src!(
"
fn or_fortytwo(x: u32?) -> u32 {
match x {
Some(x) => x,
None => 42,
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(Option<u32>) -> u32>("or_fortytwo")
.unwrap();
let res = f.call(Some(10));
assert_eq!(res, 10);
let res = f.call(None);
assert_eq!(res, 42);
}
#[test]
fn match_option_string() {
let s = src!(
"
fn foo() -> String? {
Option.Some(\"Foobar\")
}
fn bar() -> Verdict[String, String] {
match foo() {
Some(v) => accept v,
_ => reject \"nope\",
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Verdict<RotoString, RotoString>>("bar")
.unwrap();
let res = f.call();
assert_eq!(res, Verdict::Accept("Foobar".into()));
}
#[test]
fn match_on_string_with_guards() {
let s = src!(
r#"
fn foo(s: String?, x: i32) -> String {
match s {
Some(s) if s == "hello" => "hey!",
_ if x == 5 => "x is 5",
Some(s) if s.starts_with("lorem ipsum") => {
"You've generated lorem ipsum: " + s
}
_ => "Can't recognize",
}
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(Option<RotoString>, i32) -> RotoString>("foo")
.unwrap();
let res = f.call(Some("hello".into()), 5);
assert_eq!(&*res, "hey!");
let res = f.call(Some("hello".into()), 4);
assert_eq!(&*res, "hey!");
let res = f.call(Some("lorem ipsum dolor".into()), 5);
assert_eq!(&*res, "x is 5");
let res = f.call(Some("lorem ipsum dolor".into()), 4);
assert_eq!(&*res, "You've generated lorem ipsum: lorem ipsum dolor");
let res = f.call(Some("nothing".into()), 5);
assert_eq!(&*res, "x is 5");
let res = f.call(Some("nothing".into()), 4);
assert_eq!(&*res, "Can't recognize");
}
#[test]
fn construct_option_value() {
let s = src!(
"
fn sub_one(x: u32) -> u32? {
if x == 0 {
Option.None
} else {
Option.Some(x - 1)
}
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn(u32) -> Option<u32>>("sub_one").unwrap();
let res = f.call(0);
assert_eq!(res, None);
let res = f.call(2);
assert_eq!(res, Some(1));
}
#[test]
fn construct_imported_option() {
let s = src!(
"
import Option.{Some, None};
fn sub_one(x: u32) -> u32? {
if x == 0 {
None
} else {
Some(x - 1)
}
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn(u32) -> Option<u32>>("sub_one").unwrap();
let res = f.call(0);
assert_eq!(res, None);
let res = f.call(2);
assert_eq!(res, Some(1));
}
#[test]
fn construct_option_value_from_prelude() {
let s = src!(
"
fn sub_one(x: u32) -> u32? {
if x == 0 {
None
} else {
Some(x - 1)
}
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn(u32) -> Option<u32>>("sub_one").unwrap();
let res = f.call(0);
assert_eq!(res, None);
let res = f.call(2);
assert_eq!(res, Some(1));
}
#[test]
fn filter_map_with_manual_verdict() {
let s = src!(
"
filtermap foo(x: i32) {
if x < 10 {
return Verdict.Reject(10)
} else {
return Verdict.Accept(x)
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<i32, i32>>("foo")
.unwrap();
let res = f.call(0);
assert_eq!(res, Verdict::Reject(10));
let res = f.call(12);
assert_eq!(res, Verdict::Accept(12));
}
#[test]
fn unused_accept() {
let s = src!(
"
fn foo() {
Verdict.Accept(5);
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> ()>("foo").unwrap();
f.call();
}
#[test]
fn match_on_verdict() {
let s = src!(
"
filtermap over_10(x: i32) {
if x > 10 {
accept x
} else {
reject x
}
}
filtermap foo(x: i32) {
match over_10(x) {
Accept(x) => accept x + 1,
Reject(x) => reject x,
};
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32) -> Verdict<i32, i32>>("foo")
.unwrap();
let res = f.call(5);
assert_eq!(res, Verdict::Reject(5));
let res = f.call(15);
assert_eq!(res, Verdict::Accept(16));
let res = f.call(25);
assert_eq!(res, Verdict::Accept(26));
}
#[test]
fn non_sugar_option() {
let s = src!(
"
fn foo() -> Option[u32] {
Option.Some(2)
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> Option<u32>>("foo").unwrap();
let res = f.call();
assert_eq!(res, Some(2));
}
#[test]
fn none_with_unknown_type() {
let s = src!(
"
fn foo() {
Option.None;
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> ()>("foo").unwrap();
f.call();
}
#[test]
fn question_mark() {
let s = src!(
"
fn bar() -> u32? {
Option.None
}
fn foo() -> u32? {
bar()?;
Option.Some(3)
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> Option<u32>>("foo").unwrap();
let res = f.call();
assert_eq!(res, None);
}
#[test]
fn question_mark_in_chain() {
let s = src!(
"
fn is_finite(x: f64) -> f64? {
if x.is_finite() {
Option.Some(x)
} else {
Option.None
}
}
fn foo(x: f64) -> f64? {
let y = is_finite(x)?.pow(2.0);
Option.Some(y)
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn(f64) -> Option<f64>>("foo").unwrap();
let res = f.call(10.0);
assert_eq!(res, Some(100.0));
let res = f.call(f64::NAN);
assert_eq!(res, None);
}
#[test]
fn question_unit() {
let s = src!(
"
fn maybe_a_unit(x: bool) -> ()? {
if x {
Some(())
} else {
None
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(bool) -> Option<()>>("maybe_a_unit")
.unwrap();
let res = f.call(false);
assert_eq!(res, None);
let res = f.call(true);
assert_eq!(res, Some(()));
}
#[test]
fn question_record() {
let s = src!(
"
fn maybe_an_i32(x: bool) -> i32? {
Some(maybe_a_record(x)?.a)
}
fn maybe_a_record(x: bool) -> { a: i32 }? {
if x {
Some({ a: 5 })
} else {
None
}
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(bool) -> Option<i32>>("maybe_an_i32")
.unwrap();
let res = f.call(false);
assert_eq!(res, None);
let res = f.call(true);
assert_eq!(res, Some(5));
}
#[test]
fn add_options() {
let s = src!(
"
fn small(x: i32) -> i32? {
if x < 10 {
Option.Some(x)
} else {
Option.None
}
}
fn foo(x: i32, y: i32) -> i32? {
let z = small(x)? + small(y)?;
Option.Some(z)
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn(i32, i32) -> Option<i32>>("foo")
.unwrap();
let res = f.call(2, 2);
assert_eq!(res, Some(4));
let res = f.call(5, 5);
assert_eq!(res, Some(10));
let res = f.call(15, 5);
assert_eq!(res, None);
let res = f.call(5, 10);
assert_eq!(res, None);
}
#[test]
fn question_question() {
let s = src!(
"
fn bar() -> u32?? {
Option.Some(Option.None)
}
fn foo() -> u32? {
bar()??;
Option.Some(4)
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> Option<u32>>("foo").unwrap();
let res = f.call();
assert_eq!(res, None);
}
#[test]
fn question_mark_none() {
let s = src!(
"
fn foo() -> u32? {
Option.None?;
Option.Some(3)
}
"
);
let mut p = compile(s);
let f = p.get_function::<fn() -> Option<u32>>("foo").unwrap();
let res = f.call();
assert_eq!(res, None);
}
#[test]
fn top_level_import() {
let pkg = source_file!(
"pkg",
"
import foo.bar;
fn main(x: i32) -> i32 {
bar(x)
}
"
);
let foo = source_file!(
"foo",
"
fn bar(x: i32) -> i32 {
2 * x
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 8);
}
#[test]
fn local_import() {
let pkg = source_file!(
"pkg",
"
fn main(x: i32) -> i32 {
import foo.bar;
bar(x)
}
"
);
let foo = source_file!(
"foo",
"
fn bar(x: i32) -> i32 {
2 * x
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 8);
}
#[test]
fn parent_import() {
let pkg = source_file!(
"pkg",
"
import foo.quadruple;
fn main(x: i32) -> i32 {
quadruple(x)
}
fn double(x: i32) -> i32 {
2 * x
}
"
);
let foo = source_file!(
"foo",
"
import super.double;
fn quadruple(x: i32) -> i32 {
double(double(x))
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 16);
}
#[test]
fn package_import() {
let pkg = source_file!(
"pkg",
"
import foo.quadruple;
fn main(x: i32) -> i32 {
quadruple(x)
}
fn double(x: i32) -> i32 {
2 * x
}
"
);
let foo = source_file!(
"foo",
"
import pkg.double;
fn quadruple(x: i32) -> i32 {
double(double(x))
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 16);
}
#[test]
fn import_via_super() {
let pkg = source_file!(
"pkg",
"
import foo.a;
fn main(x: i32) -> i32 {
a(x)
}
"
);
let foo = source_file!(
"foo",
"
import super.bar.b;
fn a(x: i32) -> i32 {
b(x)
}
"
);
let bar = source_file!(
"bar",
"
fn b(x: i32) -> i32 {
2 * x
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo), FileSpec::File(bar)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 8);
}
#[test]
fn import_module_first() {
let pkg = source_file!(
"pkg",
"
import foo.a;
fn main(x: i32) -> i32 {
a(x)
}
"
);
let foo = source_file!(
"foo",
"
import super.bar;
import bar.b;
fn a(x: i32) -> i32 {
b(x)
}
"
);
let bar = source_file!(
"bar",
"
fn b(x: i32) -> i32 {
2 * x
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo), FileSpec::File(bar)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 8);
}
#[test]
fn import_module_second() {
let pkg = source_file!(
"pkg",
"
import foo.a;
fn main(x: i32) -> i32 {
a(x)
}
"
);
let foo = source_file!(
"foo",
"
import bar.b;
import super.bar;
fn a(x: i32) -> i32 {
b(x)
}
"
);
let bar = source_file!(
"bar",
"
fn b(x: i32) -> i32 {
2 * x
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo), FileSpec::File(bar)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 8);
}
#[test]
fn use_type_from_module() {
let pkg = source_file!(
"pkg",
"
fn main(x: i32) -> i32 {
let foofoo = foo.Foo { bar: x };
foofoo.bar
}
"
);
let foo = source_file!(
"foo",
"
record Foo {
bar: i32,
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 4);
}
#[test]
fn use_imported_type() {
let pkg = source_file!(
"pkg",
"
import foo.Foo;
fn main(x: i32) -> i32 {
let foofoo = Foo { bar: x };
foofoo.bar
}
"
);
let foo = source_file!(
"foo",
"
record Foo {
bar: i32,
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 4);
}
#[test]
fn use_type_in_function_argument() {
let pkg = source_file!(
"pkg",
"
fn main(x: i32) -> i32 {
get_bar(foo.Foo { bar: x })
}
fn get_bar(f: foo.Foo) -> i32 {
f.bar
}
"
);
let foo = source_file!(
"foo",
"
record Foo {
bar: i32,
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 4);
}
#[test]
fn import_list() {
let pkg = source_file!(
"pkg",
"
import foo.{double, triple};
fn main(x: i32) -> i32 {
triple(double(x))
}
"
);
let foo = source_file!(
"foo",
"
fn double(x: i32) -> i32 {
2 * x
}
fn triple(x: i32) -> i32 {
3 * x
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(1);
assert_eq!(res, 6);
}
#[test]
fn use_type_in_function_return_type() {
let pkg = source_file!(
"pkg",
"
fn main(x: i32) -> i32 {
make_foo(x).bar
}
fn make_foo(x: i32) -> foo.Foo {
foo.Foo { bar: x }
}
"
);
let foo = source_file!(
"foo",
"
record Foo {
bar: i32,
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 4);
}
#[test]
fn use_type_from_other_module_in_type() {
let pkg = source_file!(
"pkg",
"
record Bli {
bla: foo.Bla,
}
fn main(x: i32) -> i32 {
Bli { bla: foo.Bla { blubb: x }}.bla.blubb
}
"
);
let foo = source_file!(
"foo",
"
record Bla {
blubb: i32,
}
"
);
let tree = FileTree::file_spec(FileSpec::Directory(
pkg,
vec![FileSpec::File(foo)],
));
let mut p = compile(tree);
let main = p.get_function::<fn(i32) -> i32>("main").unwrap();
let res = main.call(4);
assert_eq!(res, 4);
}
#[test]
fn mutate() {
struct MyType {
i: AtomicI32,
}
#[derive(Copy, Clone, Debug, PartialEq)]
struct Ptr(*mut MyType);
unsafe impl Send for Ptr {}
unsafe impl Sync for Ptr {}
let rt = Runtime::from_lib(library! {
#[copy] type MyType = Val<Ptr>;
impl Val<Ptr> {
fn increase(self) {
use std::sync::atomic::Ordering::Relaxed;
eprintln!("increase, pre: {}", unsafe { (*self.0.0).i.load(Relaxed) });
unsafe { (*self.0.0).i.fetch_add(1, Relaxed) };
eprintln!("increase, post: {}", unsafe { (*self.0.0).i.load(Relaxed) });
}
}
})
.unwrap();
let s = src!(
"
filter main(t: MyType) {
t.increase();
accept t;
}
"
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(Val<Ptr>) -> Verdict<Val<Ptr>, ()>>("main")
.expect("No function found (or mismatched types)");
let mut t = MyType {
i: AtomicI32::new(0),
};
let ptr = Ptr(&mut t as *mut _);
let res = f.call(Val(ptr));
use std::sync::atomic::Ordering::Relaxed;
match res {
Verdict::Accept(val) => {
let val = unsafe { (*val.0.0).i.load(Relaxed) };
assert_eq!(val, 1, "returned value should be 1")
}
Verdict::Reject(_) => todo!(),
}
assert_eq!(t.i.load(Relaxed), 1, "mutated value should be 1");
}
#[test]
fn return_vec() {
#[derive(Clone, Debug, Default, PartialEq)]
#[allow(dead_code)]
struct MyType {
v: Vec<u8>,
}
let rt = Runtime::from_lib(library! {
#[clone] type MyType = Val<MyType>;
})
.unwrap();
let s = src!(
"
fn main(t: MyType) -> MyType {
t
}
"
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(Val<MyType>) -> Val<MyType>>("main")
.expect("No function found (or mismatched types)");
let t = MyType { v: vec![0x01] };
let res = f.call(Val(t));
println!("Returned with {:?}", res.0.v.as_ptr());
}
#[test]
fn name_collision() {
#[derive(Clone, PartialEq)]
struct A {
_x: u32,
}
#[derive(Clone, PartialEq)]
struct B {
_x: u32,
}
let rt = Runtime::from_lib(library! {
#[clone] type A = Val<A>;
impl Val<A> {
fn foo(self) -> bool {
true
}
}
#[clone] type B = Val<B>;
impl Val<B> {
fn foo(self) -> bool {
unreachable!()
}
}
})
.unwrap();
let s = src!(
"
filter foo(t: B) {
accept t.foo()
}
filter main(t: A) {
accept t.foo()
}
"
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(Val<A>) -> Verdict<bool, ()>>("main")
.expect("No function found (or mismatched types)");
let t = A { _x: 1 };
let res = f.call(Val(t));
assert_eq!(res, Verdict::Accept(true));
}
#[test]
fn refcounting_in_a_recursive_function() {
#[derive(Debug, Clone, PartialEq)]
struct Foo;
let rt = Runtime::from_lib(library! {
#[clone] type Foo = Val<Arc<Foo>>;
})
.unwrap();
let s = src!(
r##"
fn f(foo: Foo, idx: i32) {
if idx > 0 { f(foo, idx - 1); }
}
fn main(foo: Foo) -> Verdict[i32, i32] {
f(foo, 1);
reject 3
}
"##
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(Val<Arc<Foo>>) -> Verdict<i32, i32>>("main")
.unwrap();
let v = Arc::new(Foo);
let res = f.call(Val(v.clone()));
assert_eq!(res, Verdict::Reject(3));
assert_eq!(Arc::strong_count(&v), 1);
}
#[test]
fn str_equals() {
let s = src!(
r#"
fn is_slash(s: String) -> bool {
s == "/"
}
"#
);
let mut compiled = compile(s);
let func = compiled
.get_function::<fn(RotoString) -> bool>("is_slash")
.unwrap();
assert!(func.call("/".into()));
assert!(!func.call("foo".into()));
}
#[test]
fn str_not_equals() {
let s = src!(
r#"
fn is_not_slash(s: String) -> bool {
s != "/"
}
"#
);
let mut compiled = compile(s);
let func = compiled
.get_function::<fn(RotoString) -> bool>("is_not_slash")
.unwrap();
assert!(func.call("foo".into()));
assert!(!func.call("/".into()));
}
#[test]
fn assignment() {
let s = src!(
"
fn foo() -> i32 {
let x = 4;
x = x + 3;
x
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 7);
}
#[test]
fn add_assign() {
let s = src!(
"
fn foo() -> i32 {
let x = 1;
x += 2;
x += 3;
x
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 6);
}
#[test]
fn sub_assign() {
let s = src!(
"
fn foo() -> i32 {
let x = 6;
x -= 2;
x -= 3;
x
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 1);
}
#[test]
fn mul_assign() {
let s = src!(
"
fn foo() -> i32 {
let x = 6;
x *= 2;
x *= 3;
x
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 6 * 2 * 3);
}
#[test]
fn div_assign() {
let s = src!(
"
fn foo() -> i32 {
let x = 60;
x /= 2;
x /= 3;
x
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 10);
}
#[test]
fn mod_assign() {
let s = src!(
"
fn foo() -> i32 {
let x = 65;
x %= 10;
x
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 5);
}
#[test]
fn assignment_record_field() {
let s = src!(
"
fn foo() -> i32 {
let x = { bar: 4 };
x.bar = x.bar + 3;
x.bar
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 7);
}
#[test]
fn assignment_record() {
let s = src!(
"
fn foo() -> i32 {
let x = { bar: 4 };
x = { bar: x.bar + 3 };
x.bar
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 7);
}
#[test]
fn assignment_record_is_by_value() {
let s = src!(
"
fn foo() -> i32 {
let x = { bar: 4 };
let y = { bar: 5 };
x = y;
x.bar = 6;
y.bar
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 5);
}
#[test]
fn assignment_nested_record_1() {
let s = src!(
"
fn foo() -> i32 {
let x = { bar: { baz: 1 } };
x.bar.baz = 6;
x.bar.baz
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 6);
}
#[test]
fn assignment_nested_record_2() {
let s = src!(
"
fn foo() -> i32 {
let x = { bar: { baz: 1 } };
x.bar = { baz: 6 };
x.bar.baz
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 6);
}
#[test]
fn assignment_string() {
let s = src!(
"
fn foo() -> String {
let x = \"foo\";
x = x + x;
x = x + x;
x
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> RotoString>("foo").unwrap();
assert_eq!(func.call(), "foofoofoofoo".into());
}
#[test]
fn let_declaration_is_by_value() {
let s = src!(
"
fn foo() -> i32 {
let y = { bar: 5 };
let x = y;
x.bar = 6;
y.bar
}
"
);
let mut compiled = compile(s);
let func = compiled.get_function::<fn() -> i32>("foo").unwrap();
assert_eq!(func.call(), 5);
}
#[test]
fn sigill() {
#[derive(PartialEq)]
struct Arcane(Arc<()>);
impl Clone for Arcane {
fn clone(&self) -> Self {
assert!(Arc::strong_count(&self.0) > 0);
Arcane(self.0.clone())
}
}
impl Drop for Arcane {
fn drop(&mut self) {
assert!(Arc::strong_count(&self.0) > 0);
}
}
let rt = Runtime::from_lib(library! {
#[clone] type Arcane = Val<Arcane>;
impl Val<Arcane> {
fn get(self) -> u64 {
Arc::strong_count(&self.0.0) as u64
}
}
fn make_arcane() -> Val<Arcane> {
Val(Arcane(Arc::new(())))
}
})
.unwrap();
let s = src!(
"
fn bar(a: Arcane) -> u64 {
a.get()
}
fn foo() -> Arcane {
let a = make_arcane();
bar(a);
bar(a);
a
}
"
);
let mut compiled = compile_with_runtime(s, rt);
let func = compiled.get_function::<fn() -> Val<Arcane>>("foo").unwrap();
let Val(a) = func.call();
assert_eq!(Arc::strong_count(&a.0), 1);
}
#[test]
fn rust_string_string() {
let rt = Runtime::from_lib(library! {
#[clone] type RustString = Val<String>;
impl Val<String> {
fn new(s: RotoString) -> Val<String> {
Val(s.as_ref().into())
}
}
})
.unwrap();
let s = src!(
"
fn foo() -> RustString {
let s = RustString.new(\"hello\");
s = s;
s
}
fn bar() -> RustString {
let s = RustString.new(\"hello\");
let a = { str: s };
a.str = a.str;
a.str
}
"
);
let mut compiled = compile_with_runtime(s, rt);
let func = compiled.get_function::<fn() -> Val<String>>("foo").unwrap();
assert_eq!(func.call(), Val("hello".into()));
let func = compiled.get_function::<fn() -> Val<String>>("bar").unwrap();
assert_eq!(func.call(), Val("hello".into()));
}
#[test]
fn return_verdict_from_runtime_function() {
let rt = Runtime::from_lib(library! {
fn foo() -> Verdict<(), ()> {
Verdict::Accept(())
}
})
.unwrap();
let s = src!(
"
filtermap bar() {
foo()
}
"
);
let mut compiled = compile_with_runtime(s, rt);
let func = compiled
.get_function::<fn() -> Verdict<(), ()>>("bar")
.unwrap();
assert_eq!(Verdict::Accept(()), func.call());
}
#[test]
fn string_global() {
let s = src!(
"fn use_foo() -> String {
FOO
}"
);
let rt = Runtime::from_lib(library! {
const FOO: RotoString = "BAR".into();
})
.unwrap();
let mut p = compile_with_runtime(s, rt);
let f = p.get_function::<fn() -> RotoString>("use_foo").unwrap();
assert_eq!(f.call(), "BAR".into());
}
#[test]
fn layered_option_matching_none() {
let s = src!(
"
fn reproducer() -> String? {
let s = match None {
Some(v) => v,
None => { return None; },
};
Some(s)
}
"
);
let mut p = compile(s);
let f = p
.get_function::<fn() -> Option<RotoString>>("reproducer")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, None);
}
#[test]
fn strings_from_if() {
let s = src!(
r#"
fn foo(x: bool) -> String {
if x { "true" } else { "false" }
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(bool) -> RotoString>("foo")
.expect("No function found (or mismatched types)");
let res = f.call(false);
assert_eq!(res, "false".into());
let res = f.call(true);
assert_eq!(res, "true".into());
}
#[test]
fn bool_is_true() {
let s = src!(
r#"
fn foo(x: bool) -> bool {
x == true
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(bool) -> bool>("foo")
.expect("No function found (or mismatched types)");
let res = f.call(false);
assert!(!res);
let res = f.call(true);
assert!(res);
}
#[test]
fn bool_is_not_true() {
let s = src!(
r#"
fn foo(x: bool) -> bool {
x != true
}
"#
);
let mut p = compile(s);
let f = p
.get_function::<fn(bool) -> bool>("foo")
.expect("No function found (or mismatched types)");
let res = f.call(false);
assert!(res);
let res = f.call(true);
assert!(!res);
}
#[test]
fn register_on_optstr() {
let rt = Runtime::from_lib(library! {
#[clone] type OptStr = Val<Option<RotoString>>;
impl Val<Option<RotoString>> {
fn unwrap_or_empty(self) -> RotoString {
self.0.unwrap_or_default()
}
}
})
.unwrap();
let s = src!(
r#"
fn foo(x: OptStr) -> String {
x.unwrap_or_empty()
}
"#
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn(Val<Option<RotoString>>) -> RotoString>("foo")
.expect("No function found (or mismatched types)");
let res = f.call(Val(None));
assert_eq!(res, "".into());
let res = f.call(Val(Some("hello".into())));
assert_eq!(res, "hello".into());
}
#[test]
fn register_closure() {
let some_number = 10;
let rt = Runtime::from_lib(library! {
let get = move || { some_number };
})
.unwrap();
let s = src!(
r#"
fn foo() -> i32 {
get()
}
"#
);
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn() -> i32>("foo")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, 10);
}
#[test]
fn increment_via_closure() {
static COUNTER: AtomicI32 = AtomicI32::new(0);
let runtime = Runtime::from_lib(library! {
let inc = || {
COUNTER.fetch_add(1, Ordering::Relaxed)
};
})
.unwrap();
let s = src!(
r#"
fn foo() {
inc();
inc();
}
"#
);
let mut p = compile_with_runtime(s, runtime);
let f = p
.get_function::<fn()>("foo")
.expect("No function found (or mismatched types)");
f.call();
assert_eq!(COUNTER.load(Ordering::Relaxed), 2);
}
#[test]
fn call_runtime_function_in_f_string() {
let s = src!(
r#"
fn foo() -> String {
f"foo{gimme_an_asn()}bar"
}
"#
);
let rt = Runtime::from_lib(library! {
fn gimme_an_asn() -> Asn {
Asn::from_u32(2)
}
})
.unwrap();
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn() -> RotoString>("foo")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, "fooAS2bar".into());
}
#[test]
fn f32_issue() {
let s = src!(
r#"
fn foo() {
let x = 5.0;
let y: f32 = 2.0;
let z: f32 = x * y;
}
"#
);
let rt = Runtime::from_lib(library! {
fn rand() -> f32 {
10.0
}
})
.unwrap();
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn()>("foo")
.expect("No function found (or mismatched types)");
f.call();
}
#[test]
fn register_module() {
let s = src!(
r#"
import math.{PI, sin, cos};
fn foo() -> f32 {
cos(PI) + sin(PI)
}
"#
);
let rt = Runtime::from_lib(library! {
mod math {
const PI: f32 = std::f32::consts::PI;
fn sin(x: f32) -> f32 {
x.sin()
}
fn cos(x: f32) -> f32 {
x.cos()
}
}
})
.unwrap();
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn() -> f32>("foo")
.expect("No function found (or mismatched types)");
let res = f.call();
assert!(-1.1 < res && res < -0.9);
}
#[test]
fn register_type_in_module() {
let s = src!(
r#"
fn main() -> u32 {
foo.Foo.new().bar()
}
"#
);
#[derive(Clone, PartialEq)]
struct Foo(u32);
let rt = Runtime::from_lib(library! {
mod foo {
#[clone] type Foo = Val<Foo>;
impl Val<Foo> {
fn new() -> Self {
Val(Foo(3))
}
fn bar(self) -> u32 {
self.0.0
}
}
}
})
.unwrap();
let mut p = compile_with_runtime(s, rt);
let f = p
.get_function::<fn() -> u32>("main")
.expect("No function found (or mismatched types)");
let res = f.call();
assert_eq!(res, 3);
}
#[test]
fn assignment_with_question_mark() {
let s = src!(
r#"
fn foo() -> ()? {
let x: String? = None;
let y = "hello";
y = x?;
Some(())
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> Option<()>>("foo").unwrap();
f.call();
}
#[test]
fn prefix_eq() {
let s = src!(
r#"
fn foo() -> bool {
let x = 10.0.0.0 / 10;
let y = 10.0.0.0 / 10;
x == y
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> bool>("foo").unwrap();
f.call();
}
#[test]
fn define_enum_type() {
let s = src!(
"
enum Foo {
Bar,
Baz,
}
fn make_foo(x: bool) -> Foo {
if x {
Foo.Bar
} else {
Foo.Baz
}
}
fn match_on_foo(x: bool) -> i32 {
match make_foo(x) {
Bar => 10,
Baz => 20,
}
}
"
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(bool) -> i32>("match_on_foo").unwrap();
let res = f.call(true);
assert_eq!(res, 10);
let res = f.call(false);
assert_eq!(res, 20);
}
#[test]
fn haskeller_wants_to_feel_at_home() {
let s = src!(
"
enum Maybe {
Just(i32),
Nothing,
}
import Maybe.{Just, Nothing};
fn from_option(x: i32?) -> Maybe {
match x {
None => Nothing,
Some(x) => Just(x)
}
}
fn to_option(x: Maybe) -> i32? {
match x {
Nothing => None,
Just(x) => Some(x),
}
}
fn useless(x: i32?) -> i32? {
to_option(from_option(x))
}
"
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(Option<i32>) -> Option<i32>>("useless")
.unwrap();
let res = f.call(Some(5));
assert_eq!(res, Some(5));
let res = f.call(None);
assert_eq!(res, None);
}
#[test]
fn generic_haskeller_wants_to_feel_at_home() {
let s = src!(
"
enum Maybe[T] {
Just(T),
Nothing,
}
import Maybe.{Just, Nothing};
fn from_option(x: i32?) -> Maybe[i32] {
match x {
None => Nothing,
Some(x) => Just(x)
}
}
fn to_option(x: Maybe[i32]) -> i32? {
match x {
Nothing => None,
Just(x) => Some(x),
}
}
fn useless(x: i32?) -> i32? {
to_option(from_option(x))
}
"
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(Option<i32>) -> Option<i32>>("useless")
.unwrap();
let res = f.call(Some(5));
assert_eq!(res, Some(5));
let res = f.call(None);
assert_eq!(res, None);
}
#[test]
fn match_on_empty_enum() {
let s = src!(
"
enum Foo {}
fn foo(x: Foo) {
match x {}
}
"
);
let _pkg = compile(s);
}
#[test]
fn match_on_empty_enum_2() {
let s = src!(
"
enum Foo {}
fn foo() {
let x: Foo = return;
match x {}
}
"
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> ()>("foo").unwrap();
f.call()
}
#[test]
fn match_on_uninhabited_variant() {
let s = src!(
"
enum Foo { Baz(!) }
fn foo(x: Foo) {
match x {
Baz(x) => {}
}
}
"
);
let _pkg = compile(s);
}
#[test]
fn lets_make_a_result() {
let s = src!(
"
enum Result[T, E] {
Ok(T),
Err(E),
}
import Result.{Ok, Err};
fn from(x: Verdict[i32, u32]) -> Result[i32, u32] {
match x {
Reject(x) => Err(x),
Accept(x) => Ok(x)
}
}
fn to(x: Result[i32, u32]) -> Verdict[i32, u32] {
match x {
Ok(x) => Verdict.Accept(x),
Err(x) => Verdict.Reject(x),
}
}
fn useless(x: Verdict[i32, u32]) -> Verdict[i32, u32] {
to(from(x))
}
"
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(Verdict<i32, u32>) -> Verdict<i32, u32>>("useless")
.unwrap();
assert_eq!(f.call(Verdict::Accept(2)), Verdict::Accept(2));
assert_eq!(f.call(Verdict::Reject(4)), Verdict::Reject(4));
}
#[test]
fn enum_with_unused_type_param() {
let s = src!(
r#"
enum Foo[T] { Bar }
fn foo() {
let x = Foo.Bar;
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn()>("foo").unwrap();
f.call();
}
#[test]
fn enum_with_never_type_param() {
let s = src!(
r#"
enum Foo[T] { Bar }
fn foo() {
let x: Foo[!] = Foo.Bar;
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn()>("foo").unwrap();
f.call();
}
#[test]
fn generic_record() {
let s = src!(
"
record Foo[T] {
x: T,
}
fn wrap(x: i32) -> Foo[i32] {
Foo { x: x }
}
fn unwrap(x: Foo[i32]) -> i32 {
x.x
}
fn bar(x: i32) -> i32 {
let y = wrap(x);
unwrap(y)
}
"
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(i32) -> i32>("bar").unwrap();
let res = f.call(23);
assert_eq!(res, 23);
}
#[test]
fn generic_record_contains_option() {
let s = src!(
"
record Foo[T] {
x: T?,
}
fn wrap(x: i32?) -> Foo[i32] {
Foo { x: x }
}
fn unwrap(x: Foo[i32]) -> i32? {
x.x
}
fn bar(x: i32?) -> i32? {
let y = wrap(x);
unwrap(y)
}
"
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(Option<i32>) -> Option<i32>>("bar")
.unwrap();
let res = f.call(None);
assert_eq!(res, None);
let res = f.call(Some(20));
assert_eq!(res, Some(20));
}
#[test]
fn generic_record_inferred() {
let s = src!(
"
record Foo[T] {
x: T,
}
fn wrap(x: i32) -> Foo[i32] {
{ x: x } // this is now inferred to be Foo[i32]
}
fn unwrap(x: Foo[i32]) -> i32 {
x.x
}
fn bar(x: i32) -> i32 {
let y = wrap(x);
unwrap(y)
}
"
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(i32) -> i32>("bar").unwrap();
let res = f.call(23);
assert_eq!(res, 23);
}
#[test]
fn generics_all_the_way_down() {
let s = src!(
"
record Foo[T] {
bar: Bar[T]?
}
record Bar[T] {
inner: T
}
fn main(x: i32) -> i32 {
let foo = Foo {
bar: Some(Bar {
inner: x
}),
};
match foo.bar {
Some(bar) => bar.inner,
None => 0,
}
}
"
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(i32) -> i32>("main").unwrap();
let res = f.call(23);
assert_eq!(res, 23);
}
#[test]
fn char_literal() {
let s = src!(
r#"
fn main() -> char {
'a'
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> char>("main").unwrap();
let res = f.call();
assert_eq!(res, 'a');
}
#[test]
fn stringbuf() {
let s = src!(
r#"
fn quote(s: String) -> String {
let buf = StringBuf.new();
buf.push_char('"');
buf.push_string(s);
buf.push_char('"');
buf.as_string()
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(RotoString) -> RotoString>("quote")
.unwrap();
let res = f.call("hello".into());
assert_eq!(res, "\"hello\"".into());
}
#[test]
fn list_basic() {
let s = src!(
r#"
fn main() -> i32? {
let list = List.new();
list.push(5);
list.get(0)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> Option<i32>>("main").unwrap();
let res = f.call();
assert_eq!(res, Some(5));
}
#[test]
fn list_push_twice() {
let s = src!(
r#"
fn main() -> i32? {
let list = List.new();
list.push(5);
list.push(10);
list.push(15);
list.get(0)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> Option<i32>>("main").unwrap();
let res = f.call();
assert_eq!(res, Some(5));
}
#[test]
fn list_get_many() {
let s = src!(
r#"
fn main() -> i32? {
let list = List.new();
list.push(5);
list.push(10);
list.push(15);
list.push(20);
let a = list.get(0)?
+ list.get(1)?
+ list.get(2)?
+ list.get(3)?;
Some(a)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> Option<i32>>("main").unwrap();
let res = f.call();
assert_eq!(res, Some(50));
}
#[test]
fn list_len() {
let s = src!(
r#"
fn main() -> u64 {
let list = List.new();
list.push(1);
list.push(2);
list.push(3);
list.len()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> u64>("main").unwrap();
let res = f.call();
assert_eq!(res, 3);
}
#[test]
fn list_is_empty_1() {
let s = src!(
r#"
fn main() -> bool {
let list = List.new();
list.is_empty()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> bool>("main").unwrap();
let res = f.call();
assert!(res);
}
#[test]
fn list_is_empty_2() {
let s = src!(
r#"
fn main() -> bool {
let list = List.new();
list.push(5);
list.is_empty()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> bool>("main").unwrap();
let res = f.call();
assert!(!res);
}
#[test]
fn list_of_strings_1() {
let s = src!(
r#"
fn main() -> String? {
let list = List.new();
list.push("hello");
list.get(0)
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> Option<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res, Some("hello".into()));
}
#[test]
fn list_of_strings_2() {
let s = src!(
r#"
fn main() -> String? {
let list = List.new();
list.push("hello");
list.push("world");
Some(f"{list.get(0)?} {list.get(1)?}")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> Option<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res, Some("hello world".into()));
}
#[test]
fn list_through_script() {
let s = src!(
r#"
fn main(x: List[u64]) -> List[u64] {
x.push(3);
x.push(4);
x
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(List<u64>) -> List<u64>>("main")
.unwrap();
let x = List::new();
x.push(1);
x.push(2);
let res = f.call(x);
assert_eq!(res.to_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn list_of_lists() {
let s = src!(
r#"
fn main() -> List[List[u64]] {
let x = List.new();
let a = List.new();
a.push(1);
a.push(2);
let b = List.new();
b.push(3);
b.push(4);
x.push(a);
x.push(b);
x
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> List<List<u64>>>("main").unwrap();
let res = f.call();
assert_eq!(
res.to_vec().iter().map(|l| l.to_vec()).collect::<Vec<_>>(),
vec![vec![1, 2], vec![3, 4]]
);
}
#[test]
fn list_literal() {
let s = src!(
r#"
fn main() -> List[u64] {
[1, 2, 3, 4]
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> List<u64>>("main").unwrap();
let res = f.call();
assert_eq!(res.to_vec(), vec![1, 2, 3, 4]);
}
#[test]
fn list_concat() {
let s = src!(
r#"
fn main() -> List[u64] {
[1, 2, 3, 4].concat([5, 6, 7, 8])
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> List<u64>>("main").unwrap();
let res = f.call();
assert_eq!(res.to_vec(), vec![1, 2, 3, 4, 5, 6, 7, 8]);
}
#[test]
fn list_concat_strings() {
let s = src!(
r#"
fn main() -> List[String] {
["a", "b"].concat(["c", "d"])
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<RotoString>>("main")
.unwrap();
let res = f.call();
let expected: Vec<RotoString> =
["a", "b", "c", "d"].into_iter().map(Into::into).collect();
assert_eq!(res.to_vec(), expected);
}
#[test]
fn list_swap() {
let s = src!(
r#"
fn main() -> List[i32] {
let x = [1, 2, 3, 4];
x.swap(1, 2);
x.swap(0, 3);
x
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> List<i32>>("main").unwrap();
let res = f.call();
let expected: Vec<i32> = vec![4, 3, 2, 1];
assert_eq!(res.to_vec(), expected);
}
#[test]
fn list_plus_strings() {
let s = src!(
r#"
fn main() -> List[String] {
["a", "b"] + ["c", "d"]
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<RotoString>>("main")
.unwrap();
let res = f.call();
let expected: Vec<RotoString> =
["a", "b", "c", "d"].into_iter().map(Into::into).collect();
assert_eq!(res.to_vec(), expected);
}
#[test]
fn list_of_options() {
let s = src!(
r#"
fn main() -> List[i32?] {
[Some(1), None, Some(2), None]
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<Option<i32>>>("main")
.unwrap();
let res = f.call();
let expected = vec![Some(1), None, Some(2), None];
assert_eq!(res.to_vec(), expected);
}
#[test]
fn list_of_option_of_strings() {
let s = src!(
r#"
fn main() -> List[String?] {
[Some("hello"), None, Some("bonjour"), None]
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<Option<RotoString>>>("main")
.unwrap();
let res = f.call();
let expected: Vec<Option<RotoString>> =
vec![Some("hello".into()), None, Some("bonjour".into()), None];
assert_eq!(res.to_vec(), expected);
}
#[test]
fn for_loop() {
let s = src!(
r#"
fn main() -> u64 {
let total = 0;
for x in [1, 2, 3, 4] {
total = total + x;
}
total
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> u64>("main").unwrap();
let res = f.call();
assert_eq!(res, 10);
}
#[test]
fn for_loop_strings() {
let s = src!(
r#"
fn main() -> String {
let total = "";
for x in ["a", "b", "c", "d"] {
total = total + x;
}
total
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "abcd".into());
}
#[test]
fn empty_for_loop() {
let s = src!(
r#"
fn main() -> u64 {
let total = 0;
for x in [] {
total = total + x;
}
total
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> u64>("main").unwrap();
let res = f.call();
assert_eq!(res, 0);
}
#[test]
fn cartesian_for_loop() {
let s = src!(
r#"
fn main() -> u64 {
let total = 0;
for x in [1, 2, 3, 4] {
for y in [1, 2, 3, 4] {
total = total + x * y;
}
}
total
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> u64>("main").unwrap();
let res = f.call();
assert_eq!(res, 100);
}
#[test]
fn cloning_a_record_with_a_copy_field() {
let s = src!(
r#"
record Foo {
a: i64,
b: String,
}
fn main() -> i64 {
let x = Foo {
a: 5,
b: "hi",
};
let y = x;
y.a
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> i64>("main").unwrap();
let res = f.call();
assert_eq!(res, 5);
}
#[test]
fn string_get() {
let s = src!(
r#"
fn main(i: u64) -> char? {
"hello".chars().get(i)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(u64) -> Option<char>>("main").unwrap();
let res = f.call(2);
assert_eq!(res, Some('l'));
let res = f.call(0);
assert_eq!(res, Some('h'));
let res = f.call(10);
assert_eq!(res, None);
let res = f.call(5);
assert_eq!(res, None);
}
#[test]
fn string_get_non_ascii() {
let s = src!(
r#"
fn main(i: u64) -> char? {
"Löwe 老虎 Léopard".chars().get(i)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(u64) -> Option<char>>("main").unwrap();
let res = f.call(1);
assert_eq!(res, Some('ö'));
let res = f.call(5);
assert_eq!(res, Some('老'));
let res = f.call(8);
assert_eq!(res, Some('L'));
let res = f.call(16);
assert_eq!(res, None);
}
#[test]
fn string_slice() {
let s = src!(
r#"
fn main(i: u64, j: u64) -> String? {
"hello".chars().slice(i, j)
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(u64, u64) -> Option<RotoString>>("main")
.unwrap();
let res = f.call(0, 2);
assert_eq!(res, Some("he".into()));
let res = f.call(0, 1);
assert_eq!(res, Some("h".into()));
let res = f.call(0, 5);
assert_eq!(res, Some("hello".into()));
let res = f.call(1, 3);
assert_eq!(res, Some("el".into()));
let res = f.call(0, 0);
assert_eq!(res, Some("".into()));
let res = f.call(1, 0);
assert_eq!(res, None);
let res = f.call(0, 8);
assert_eq!(res, None);
}
#[test]
fn string_slice_non_ascii() {
let s = src!(
r#"
fn main(i: u64, j: u64) -> String? {
"Löwe 老虎 Léopard".chars().slice(i, j)
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(u64, u64) -> Option<RotoString>>("main")
.unwrap();
let res = f.call(0, 4);
assert_eq!(res, Some("Löwe".into()));
let res = f.call(5, 7);
assert_eq!(res, Some("老虎".into()));
let res = f.call(8, 15);
assert_eq!(res, Some("Léopard".into()));
}
#[test]
fn string_join() {
let s = src!(
r#"
fn main(sep: String) -> String {
["h", "e", "l", "l", "o"].join(sep)
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(RotoString) -> RotoString>("main")
.unwrap();
let res = f.call("".into());
assert_eq!(res, "hello".into());
let res = f.call(" ".into());
assert_eq!(res, "h e l l o".into());
}
#[test]
fn string_split() {
let s = src!(
r#"
fn main() -> List[String] {
"one, two, three".split(", ")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(
res.to_vec(),
vec!["one".into(), "two".into(), "three".into()]
);
}
#[test]
fn string_join_chars() {
let s = src!(
r#"
fn main() -> String {
String.from_chars(['h', 'e', 'l', 'l', 'o'])
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "hello".into());
}
#[test]
fn string_replace() {
let s = src!(
r#"
fn main() -> String {
// Note: there are better ways to write this in Roto!
"Hello $NAME".replace("$NAME", "John")
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "Hello John".into());
}
#[test]
fn string_len() {
let s = src!(
r#"
fn main(s: String) -> u64 {
s.chars().len()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(RotoString) -> u64>("main").unwrap();
let res = f.call("hello".into());
assert_eq!(res, 5);
let res = f.call("老虎".into());
assert_eq!(res, 2);
}
#[test]
fn string_chars() {
let s = src!(
r#"
fn main() -> List[char] {
"Rust!".chars().list()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> List<char>>("main").unwrap();
let res = f.call();
assert_eq!(res.to_vec(), vec!['R', 'u', 's', 't', '!']);
}
#[test]
fn string_lines() {
let s = src!(
r#"
fn main() -> List[String] {
"One line\nAnd another".lines().list()
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res.to_vec(), vec!["One line".into(), "And another".into()]);
}
#[test]
fn string_trim() {
let s = src!(
r#"
fn main() -> String {
" Rust! ".trim()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "Rust!".into());
}
#[test]
fn string_trim_start() {
let s = src!(
r#"
fn main() -> String {
" Rust! ".trim_start()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "Rust! ".into());
}
#[test]
fn string_trim_end() {
let s = src!(
r#"
fn main() -> String {
" Rust! ".trim_end()
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, " Rust!".into());
}
#[test]
fn string_strip_prefix_found() {
let s = src!(
r#"
fn main() -> String? {
"RotoRust!".strip_prefix("Roto")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> Option<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res, Some("Rust!".into()));
}
#[test]
fn string_strip_prefix_not_found() {
let s = src!(
r#"
fn main() -> String? {
"Rust!".strip_prefix("Roto")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> Option<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res, None);
}
#[test]
fn string_strip_suffix_found() {
let s = src!(
r#"
fn main() -> String? {
"Rust!Roto".strip_suffix("Roto")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> Option<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res, Some("Rust!".into()));
}
#[test]
fn string_strip_suffix_not_found() {
let s = src!(
r#"
fn main() -> String? {
"Rust!".strip_suffix("Roto")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> Option<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res, None);
}
#[test]
fn string_splitn_exact() {
let s = src!(
r#"
fn main() -> List[String] {
"Rust!Roto!String".splitn(3, "!")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res.to_vec(), ["Rust", "Roto", "String"].map(Into::into));
}
#[test]
fn string_splitn_less() {
let s = src!(
r#"
fn main() -> List[String] {
"Rust!Roto!String".splitn(2, "!")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res.to_vec(), ["Rust", "Roto!String"].map(Into::into));
}
#[test]
fn string_rsplitn() {
let s = src!(
r#"
fn main() -> List[String] {
"Rust!Roto!String".rsplitn(2, "!")
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn() -> List<RotoString>>("main")
.unwrap();
let res = f.call();
assert_eq!(res.to_vec(), ["String", "Rust!Roto"].map(Into::into));
}
#[test]
fn string_line_slice() {
let s = src!(
r#"
fn main(i: u64, j: u64) -> String? {
"1\n2\n3\n4\n".lines().slice(i, j)
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(u64, u64) -> Option<RotoString>>("main")
.unwrap();
let res = f.call(0, 2);
assert_eq!(res, Some("1\n2\n".into()));
}
#[test]
fn record_equality() {
let s = src!(
r#"
record Foo {
a: i64,
b: String,
}
fn main(check_eq: bool) -> bool {
let x = Foo {
a: 5,
b: "hi",
};
let y = Foo {
a: 5,
b: "hi",
};
if check_eq {
x == y
} else {
x != y
}
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(bool) -> bool>("main").unwrap();
assert!(f.call(true));
assert!(!f.call(false));
}
#[test]
fn record_inequality() {
let s = src!(
r#"
record Foo {
a: i64,
b: String,
}
fn main(check_eq: bool) -> bool {
let x = Foo {
a: 5,
b: "hi",
};
let y = Foo {
a: 5,
b: "bye",
};
if check_eq {
x == y
} else {
x != y
}
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(bool) -> bool>("main").unwrap();
assert!(!f.call(true));
assert!(f.call(false));
}
#[test]
fn option_equality() {
let s = src!(
r#"
fn main(a: u64?, b: u64?) -> bool {
a == b
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(Option<u64>, Option<u64>) -> bool>("main")
.unwrap();
assert!(f.call(None, None));
assert!(f.call(Some(10), Some(10)));
assert!(f.call(Some(20), Some(20)));
assert!(!f.call(Some(10), None));
assert!(!f.call(None, Some(10)));
assert!(!f.call(Some(20), Some(10)));
}
#[test]
fn option_inequality() {
let s = src!(
r#"
fn main(a: u64?, b: u64?) -> bool {
a != b
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(Option<u64>, Option<u64>) -> bool>("main")
.unwrap();
assert!(!f.call(None, None));
assert!(!f.call(Some(10), Some(10)));
assert!(!f.call(Some(20), Some(20)));
assert!(f.call(Some(10), None));
assert!(f.call(None, Some(10)));
assert!(f.call(Some(20), Some(10)));
}
#[test]
fn list_equality() {
let s = src!(
r#"
fn main(choice: bool) -> bool {
let x = if choice {
[1, 2, 3]
} else {
[1, 2, 4]
};
[1, 2, 3] == x
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(bool) -> bool>("main").unwrap();
assert!(f.call(true));
assert!(!f.call(false));
}
#[test]
fn list_inequality() {
let s = src!(
r#"
fn main(choice: bool) -> bool {
let x = if choice {
[1, 2, 3]
} else {
[1, 2, 4]
};
[1, 2, 3] != x
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(bool) -> bool>("main").unwrap();
assert!(!f.call(true));
assert!(f.call(false));
}
#[test]
fn anonymous_record_equality() {
let s = src!(
r#"
fn main(choice: bool) -> bool {
let x = if choice {
{ a: false, b: "hi" }
} else {
{ a: false, b: "bye" }
};
{ a: false, b: "hi" } == x
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(bool) -> bool>("main").unwrap();
assert!(f.call(true));
assert!(!f.call(false));
}
#[test]
fn anonymous_record_inequality() {
let s = src!(
r#"
fn main(choice: bool) -> bool {
let x = if choice {
{ a: false, b: "hi" }
} else {
{ a: false, b: "bye" }
};
{ a: false, b: "hi" } != x
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(bool) -> bool>("main").unwrap();
assert!(!f.call(true));
assert!(f.call(false));
}
#[test]
fn list_contains_int() {
let s = src!(
r#"
fn main(x: i32) -> bool {
[1, 2, 3].contains(x)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(i32) -> bool>("main").unwrap();
assert!(!f.call(0));
assert!(f.call(1));
assert!(f.call(2));
assert!(f.call(3));
assert!(!f.call(4));
}
#[test]
fn list_contains_string() {
let s = src!(
r#"
fn main(x: String) -> bool {
["Terts", "Jasper", "Luuk"].contains(x)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(RotoString) -> bool>("main").unwrap();
assert!(!f.call("Alex".into()));
assert!(f.call("Terts".into()));
assert!(f.call("Jasper".into()));
assert!(f.call("Luuk".into()));
assert!(!f.call("Martin".into()));
}
#[test]
fn list_index_int() {
let s = src!(
r#"
fn main(x: i32) -> u64? {
[10, 20, 30].index(x)
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(i32) -> Option<u64>>("main").unwrap();
assert_eq!(f.call(0), None);
assert_eq!(f.call(10), Some(0));
assert_eq!(f.call(20), Some(1));
assert_eq!(f.call(30), Some(2));
assert_eq!(f.call(40), None);
}
#[test]
fn list_index_string() {
let s = src!(
r#"
fn main(x: String) -> u64? {
["Terts", "Jasper", "Luuk"].index(x)
}
"#
);
let mut pkg = compile(s);
let f = pkg
.get_function::<fn(RotoString) -> Option<u64>>("main")
.unwrap();
assert_eq!(f.call("Alex".into()), None);
assert_eq!(f.call("Terts".into()), Some(0));
assert_eq!(f.call("Jasper".into()), Some(1));
assert_eq!(f.call("Luuk".into()), Some(2));
assert_eq!(f.call("Martin".into()), None);
}
#[test]
fn block_expression() {
let s = src!(
r#"
fn main(x: u64) -> u64 {
let y = {
let a = 2 * x;
2 * a
};
y
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn(u64) -> u64>("main").unwrap();
assert_eq!(f.call(10), 40);
}
#[test]
fn simple_roto_constant() {
let s = src!(
r#"
const FOO: u8 = 8;
fn main() -> u8 {
FOO
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> u8>("main").unwrap();
let res = f.call();
assert_eq!(res, 8);
}
#[test]
fn simple_roto_constant_string() {
let s = src!(
r#"
const FOO: String = "foo";
fn main() -> String {
FOO
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "foo".into());
}
#[test]
fn simple_roto_constant_string_2() {
let s = src!(
r#"
const BAR: String = "bar";
const FOO: String = "foo" + BAR;
fn main() -> String {
FOO
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "foobar".into());
}
#[test]
fn roto_constants_through_functions() {
let s = src!(
r#"
const BAR: String = foofoo() + foofoo();
const FOO: String = "foo";
fn foofoo() -> String {
FOO + FOO
}
fn main() -> String {
BAR
}
"#
);
let mut pkg = compile(s);
let f = pkg.get_function::<fn() -> RotoString>("main").unwrap();
let res = f.call();
assert_eq!(res, "foofoofoofoo".into());
}
#[test]
fn registered_constant_in_roto_constant() {
let s = src!(
r#"
const FOO: u32 = BAR + 1;
fn main() -> u32 {
FOO
}
"#
);
let lib = library! {
const BAR: u32 = 4;
};
let rt = Runtime::from_lib(lib).unwrap();
let mut pkg = compile_with_runtime(s, rt);
let f = pkg.get_function::<fn() -> u32>("main").unwrap();
let res = f.call();
assert_eq!(res, 5);
}
#[test]
fn runtime_type_constant() {
let s = src!(
r#"
const FOO: Foo = Foo.new(4);
fn main() -> Foo {
FOO
}
"#
);
#[derive(Clone, PartialEq)]
struct Foo {
x: i32,
}
let lib = library! {
#[clone] type Foo = Val<Foo>;
impl Val<Foo> {
fn new(x: i32) -> Self {
Val(Foo { x })
}
}
};
let rt = Runtime::from_lib(lib).unwrap();
let mut pkg = compile_with_runtime(s, rt);
let f = pkg.get_function::<fn() -> Val<Foo>>("main").unwrap();
let res = f.call();
assert_eq!(res.0.x, 4);
}
#[test]
fn zero_sized_registered_type() {
let s = src!(
r#"
fn main() -> Foo {
let x = Foo.new();
x.do_something();
x
}
"#
);
#[derive(Clone, PartialEq)]
struct Foo {}
let lib = library! {
#[clone] type Foo = Val<Foo>;
impl Val<Foo> {
fn new() -> Self {
Val(Foo { })
}
fn do_something(self) {
println!("did something");
}
}
};
let rt = Runtime::from_lib(lib).unwrap();
let mut pkg = compile_with_runtime(s, rt);
let f = pkg.get_function::<fn() -> Val<Foo>>("main").unwrap();
let _res = f.call();
}