use lex_ast::canonicalize_program;
use lex_bytecode::{compile_program, vm::Vm, Value};
use lex_runtime::{DefaultHandler, Policy};
use lex_syntax::parse_source;
fn run(src: &str, func: &str, args: Vec<Value>) -> Value {
let prog = parse_source(src).expect("parse");
let stages = canonicalize_program(&prog);
if let Err(errs) = lex_types::check_program(&stages) {
panic!("type errors: {errs:#?}");
}
let bc = compile_program(&stages);
let handler = DefaultHandler::new(Policy::permissive());
let mut vm = Vm::with_handler(&bc, Box::new(handler));
vm.call(func, args).expect("vm")
}
#[test]
fn flow_sequential_composes_two_functions() {
let src = r#"
import "std.flow" as flow
fn pipeline(x :: Int) -> Int {
let f := flow.sequential(
fn (n :: Int) -> Int { n + 1 },
fn (n :: Int) -> Int { n * 2 }
)
f(x)
}
"#;
assert_eq!(run(src, "pipeline", vec![Value::Int(3)]), Value::Int(8)); assert_eq!(run(src, "pipeline", vec![Value::Int(0)]), Value::Int(2)); }
#[test]
fn flow_sequential_captures_outer_locals() {
let src = r#"
import "std.flow" as flow
fn make(k :: Int, x :: Int) -> Int {
let f := flow.sequential(
fn (n :: Int) -> Int { n + k },
fn (n :: Int) -> Int { n * k }
)
f(x)
}
"#;
assert_eq!(run(src, "make", vec![Value::Int(3), Value::Int(5)]), Value::Int(24));
}
#[test]
fn flow_branch_picks_true_arm() {
let src = r#"
import "std.flow" as flow
fn pos_or_zero(x :: Int) -> Int {
let f := flow.branch(
fn (n :: Int) -> Bool { n > 0 },
fn (n :: Int) -> Int { n },
fn (n :: Int) -> Int { 0 }
)
f(x)
}
"#;
assert_eq!(run(src, "pos_or_zero", vec![Value::Int(7)]), Value::Int(7));
assert_eq!(run(src, "pos_or_zero", vec![Value::Int(-3)]), Value::Int(0));
assert_eq!(run(src, "pos_or_zero", vec![Value::Int(0)]), Value::Int(0));
}
#[test]
fn flow_branch_with_string_arms() {
let src = r#"
import "std.flow" as flow
fn name(b :: Bool) -> Str {
let f := flow.branch(
fn (b :: Bool) -> Bool { b },
fn (b :: Bool) -> Str { "yes" },
fn (b :: Bool) -> Str { "no" }
)
f(b)
}
"#;
assert_eq!(run(src, "name", vec![Value::Bool(true)]), Value::Str("yes".into()));
assert_eq!(run(src, "name", vec![Value::Bool(false)]), Value::Str("no".into()));
}
#[test]
fn flow_retry_returns_first_ok() {
let src = r#"
import "std.flow" as flow
fn always_ok(x :: Int) -> Result[Int, Str] {
let f := flow.retry(
fn (n :: Int) -> Result[Int, Str] { Ok(n + 1) },
3
)
f(x)
}
"#;
assert_eq!(
run(src, "always_ok", vec![Value::Int(10)]),
Value::Variant { name: "Ok".into(), args: vec![Value::Int(11)] }
);
}
#[test]
fn flow_retry_propagates_final_err() {
let src = r#"
import "std.flow" as flow
fn always_err(x :: Int) -> Result[Int, Str] {
let f := flow.retry(
fn (n :: Int) -> Result[Int, Str] { Err("nope") },
3
)
f(x)
}
"#;
assert_eq!(
run(src, "always_err", vec![Value::Int(0)]),
Value::Variant { name: "Err".into(), args: vec![Value::Str("nope".into())] }
);
}
#[test]
fn flow_retry_only_runs_max_times() {
let src = r#"
import "std.flow" as flow
fn one_shot(x :: Int) -> Result[Int, Str] {
let f := flow.retry(
fn (n :: Int) -> Result[Int, Str] { Err("only once") },
1
)
f(x)
}
"#;
assert_eq!(
run(src, "one_shot", vec![Value::Int(0)]),
Value::Variant { name: "Err".into(), args: vec![Value::Str("only once".into())] }
);
}
#[test]
fn flow_branch_composed_with_sequential() {
let src = r#"
import "std.flow" as flow
fn sign_and_double(x :: Int) -> Int {
let plus := flow.sequential(
fn (n :: Int) -> Int { n },
fn (n :: Int) -> Int { n * 2 }
)
let minus := flow.sequential(
fn (n :: Int) -> Int { 0 - n },
fn (n :: Int) -> Int { n * 2 }
)
let pick := flow.branch(
fn (n :: Int) -> Bool { n >= 0 },
plus,
minus
)
pick(x)
}
"#;
assert_eq!(run(src, "sign_and_double", vec![Value::Int(3)]), Value::Int(6));
assert_eq!(run(src, "sign_and_double", vec![Value::Int(-3)]), Value::Int(6));
}
#[test]
fn flow_parallel_returns_tuple_of_both_results() {
let src = r#"
import "std.flow" as flow
fn run_pair() -> Tuple[Int, Int] {
let p := flow.parallel(
fn () -> Int { 7 },
fn () -> Int { 11 }
)
p()
}
"#;
let r = run(src, "run_pair", vec![]);
assert_eq!(r, Value::Tuple(vec![Value::Int(7), Value::Int(11)]));
}
#[test]
fn flow_parallel_handles_heterogeneous_return_types() {
let src = r#"
import "std.flow" as flow
fn pair() -> Tuple[Int, Str] {
let p := flow.parallel(
fn () -> Int { 42 },
fn () -> Str { "hello" }
)
p()
}
"#;
let r = run(src, "pair", vec![]);
assert_eq!(r, Value::Tuple(vec![Value::Int(42), Value::Str("hello".into())]));
}