use crate::stack::{Stack, pop};
use crate::value::Value;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_cond(mut stack: Stack) -> Stack {
unsafe {
let (stack_temp, count_val) = pop(stack);
let count = match count_val {
Value::Int(n) if n >= 0 => n as usize,
Value::Int(n) => panic!("cond: count must be non-negative, got {}", n),
_ => panic!("cond: expected Int count, got {:?}", count_val),
};
if count == 0 {
panic!("cond: need at least one predicate/body pair");
}
let mut pairs = Vec::with_capacity(count);
stack = stack_temp;
for _ in 0..count {
let (stack_temp, body_val) = pop(stack);
let body_wrapper = match body_val {
Value::Quotation { wrapper, .. } => wrapper,
_ => panic!("cond: expected body Quotation, got {:?}", body_val),
};
let (stack_temp2, pred_val) = pop(stack_temp);
let pred_wrapper = match pred_val {
Value::Quotation { wrapper, .. } => wrapper,
_ => panic!("cond: expected predicate Quotation, got {:?}", pred_val),
};
stack = stack_temp2;
pairs.push((pred_wrapper, body_wrapper));
}
pairs.reverse();
for (pred_ptr, body_ptr) in pairs {
let pred_fn: unsafe extern "C" fn(Stack) -> Stack = std::mem::transmute(pred_ptr);
let body_fn: unsafe extern "C" fn(Stack) -> Stack = std::mem::transmute(body_ptr);
stack = pred_fn(stack);
let (stack_after_pred, pred_result) = pop(stack);
let matches = match pred_result {
Value::Bool(b) => b,
_ => panic!("cond: predicate must return Bool, got {:?}", pred_result),
};
if matches {
stack = body_fn(stack_after_pred);
return stack;
}
stack = stack_after_pred;
}
panic!("cond: no predicate matched");
}
}
pub use patch_seq_cond as cond;
#[cfg(test)]
mod tests {
use super::*;
use crate::stack::push;
unsafe extern "C" fn pred_always_true(stack: Stack) -> Stack {
unsafe { push(stack, Value::Bool(true)) }
}
unsafe extern "C" fn pred_always_false(stack: Stack) -> Stack {
unsafe { push(stack, Value::Bool(false)) }
}
unsafe extern "C" fn pred_is_zero(stack: Stack) -> Stack {
unsafe {
let val = crate::stack::peek(stack);
match val {
Value::Int(n) => push(stack, Value::Bool(n == 0)),
_ => panic!("pred_is_zero: expected Int"),
}
}
}
unsafe extern "C" fn pred_is_negative(stack: Stack) -> Stack {
unsafe {
let val = crate::stack::peek(stack);
match val {
Value::Int(n) => push(stack, Value::Bool(n < 0)),
_ => panic!("pred_is_negative: expected Int"),
}
}
}
unsafe extern "C" fn body_matched(stack: Stack) -> Stack {
unsafe {
let (stack, _) = pop(stack);
push(
stack,
Value::String(crate::seqstring::global_string("matched".to_string())),
)
}
}
unsafe extern "C" fn body_zero(stack: Stack) -> Stack {
unsafe {
let (stack, _) = pop(stack);
push(
stack,
Value::String(crate::seqstring::global_string("zero".to_string())),
)
}
}
unsafe extern "C" fn body_positive(stack: Stack) -> Stack {
unsafe {
let (stack, _) = pop(stack);
push(
stack,
Value::String(crate::seqstring::global_string("positive".to_string())),
)
}
}
unsafe extern "C" fn body_negative(stack: Stack) -> Stack {
unsafe {
let (stack, _) = pop(stack);
push(
stack,
Value::String(crate::seqstring::global_string("negative".to_string())),
)
}
}
unsafe extern "C" fn body_default(stack: Stack) -> Stack {
unsafe {
let (stack, _) = pop(stack);
push(
stack,
Value::String(crate::seqstring::global_string("default".to_string())),
)
}
}
fn make_quotation(f: unsafe extern "C" fn(Stack) -> Stack) -> Value {
let ptr = f as *const () as usize;
Value::Quotation {
wrapper: ptr,
impl_: ptr,
}
}
#[test]
fn test_cond_single_match() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(42)); let stack = push(stack, make_quotation(pred_always_true));
let stack = push(stack, make_quotation(body_matched));
let stack = push(stack, Value::Int(1));
let stack = cond(stack);
let (_, result) = pop(stack);
match result {
Value::String(s) => assert_eq!(s.as_str(), "matched"),
_ => panic!("Expected String, got {:?}", result),
}
}
}
#[test]
fn test_cond_first_match_wins() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(42)); let stack = push(stack, make_quotation(pred_always_true));
let stack = push(stack, make_quotation(body_matched)); let stack = push(stack, make_quotation(pred_always_true));
let stack = push(stack, make_quotation(body_default)); let stack = push(stack, Value::Int(2));
let stack = cond(stack);
let (_, result) = pop(stack);
match result {
Value::String(s) => assert_eq!(s.as_str(), "matched"), _ => panic!("Expected String, got {:?}", result),
}
}
}
#[test]
fn test_cond_second_match() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(42)); let stack = push(stack, make_quotation(pred_always_false));
let stack = push(stack, make_quotation(body_matched)); let stack = push(stack, make_quotation(pred_always_true));
let stack = push(stack, make_quotation(body_default)); let stack = push(stack, Value::Int(2));
let stack = cond(stack);
let (_, result) = pop(stack);
match result {
Value::String(s) => assert_eq!(s.as_str(), "default"), _ => panic!("Expected String, got {:?}", result),
}
}
}
#[test]
fn test_cond_classify_number() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(-5)); let stack = push(stack, make_quotation(pred_is_negative));
let stack = push(stack, make_quotation(body_negative));
let stack = push(stack, make_quotation(pred_is_zero));
let stack = push(stack, make_quotation(body_zero));
let stack = push(stack, make_quotation(pred_always_true)); let stack = push(stack, make_quotation(body_positive));
let stack = push(stack, Value::Int(3));
let stack = cond(stack);
let (_, result) = pop(stack);
match result {
Value::String(s) => assert_eq!(s.as_str(), "negative"),
_ => panic!("Expected String"),
}
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(0)); let stack = push(stack, make_quotation(pred_is_negative));
let stack = push(stack, make_quotation(body_negative));
let stack = push(stack, make_quotation(pred_is_zero));
let stack = push(stack, make_quotation(body_zero));
let stack = push(stack, make_quotation(pred_always_true)); let stack = push(stack, make_quotation(body_positive));
let stack = push(stack, Value::Int(3));
let stack = cond(stack);
let (_, result) = pop(stack);
match result {
Value::String(s) => assert_eq!(s.as_str(), "zero"),
_ => panic!("Expected String"),
}
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(42)); let stack = push(stack, make_quotation(pred_is_negative));
let stack = push(stack, make_quotation(body_negative));
let stack = push(stack, make_quotation(pred_is_zero));
let stack = push(stack, make_quotation(body_zero));
let stack = push(stack, make_quotation(pred_always_true)); let stack = push(stack, make_quotation(body_positive));
let stack = push(stack, Value::Int(3));
let stack = cond(stack);
let (_, result) = pop(stack);
match result {
Value::String(s) => assert_eq!(s.as_str(), "positive"),
_ => panic!("Expected String"),
}
}
}
}