use crate::error::set_runtime_error;
use crate::stack::{
Stack, drop_stack_value, get_stack_base, heap_value_mut, peek_heap_mut_second, pop, pop_sv,
push, stack_value_size,
};
use crate::value::{Value, VariantData};
use std::sync::Arc;
#[inline]
fn stack_depth(stack: Stack) -> usize {
if stack.is_null() {
return 0;
}
let base = get_stack_base();
if base.is_null() {
return 0;
}
(stack as usize - base as usize) / stack_value_size()
}
unsafe fn drain_stack_to_base(mut stack: Stack, base: Stack) {
unsafe {
while stack > base {
let (rest, sv) = pop_sv(stack);
drop_stack_value(sv);
stack = rest;
}
}
}
unsafe fn call_with_value(base: Stack, value: Value, callable: &Value) -> Stack {
unsafe {
let stack = push(base, value);
crate::quotations::invoke_callable(stack, callable)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_map(stack: Stack) -> Stack {
unsafe {
let (stack, callable) = pop(stack);
match &callable {
Value::Quotation { .. } | Value::Closure { .. } => {}
_ => panic!(
"list-map: expected Quotation or Closure, got {:?}",
callable
),
}
let (stack, list_val) = pop(stack);
let variant_data = match list_val {
Value::Variant(v) => v,
_ => panic!("list-map: expected Variant (list), got {:?}", list_val),
};
let mut results = Vec::with_capacity(variant_data.fields.len());
for field in variant_data.fields.iter() {
let temp_base = crate::stack::alloc_stack();
let temp_stack = call_with_value(temp_base, field.clone(), &callable);
if temp_stack <= temp_base {
panic!("list-map: quotation consumed element without producing result");
}
let (remaining, result) = pop(temp_stack);
results.push(result);
if remaining > temp_base {
drain_stack_to_base(remaining, temp_base);
}
}
let new_variant = Value::Variant(Arc::new(VariantData::new(
variant_data.tag.clone(),
results,
)));
push(stack, new_variant)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_filter(stack: Stack) -> Stack {
unsafe {
let (stack, callable) = pop(stack);
match &callable {
Value::Quotation { .. } | Value::Closure { .. } => {}
_ => panic!(
"list-filter: expected Quotation or Closure, got {:?}",
callable
),
}
let (stack, list_val) = pop(stack);
let variant_data = match list_val {
Value::Variant(v) => v,
_ => panic!("list-filter: expected Variant (list), got {:?}", list_val),
};
let mut results = Vec::new();
for field in variant_data.fields.iter() {
let temp_base = crate::stack::alloc_stack();
let temp_stack = call_with_value(temp_base, field.clone(), &callable);
if temp_stack <= temp_base {
panic!("list-filter: quotation consumed element without producing result");
}
let (remaining, result) = pop(temp_stack);
let keep = match result {
Value::Bool(b) => b,
_ => panic!("list-filter: quotation must return Bool, got {:?}", result),
};
if keep {
results.push(field.clone());
}
if remaining > temp_base {
drain_stack_to_base(remaining, temp_base);
}
}
let new_variant = Value::Variant(Arc::new(VariantData::new(
variant_data.tag.clone(),
results,
)));
push(stack, new_variant)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_fold(stack: Stack) -> Stack {
unsafe {
let (stack, callable) = pop(stack);
match &callable {
Value::Quotation { .. } | Value::Closure { .. } => {}
_ => panic!(
"list-fold: expected Quotation or Closure, got {:?}",
callable
),
}
let (stack, init) = pop(stack);
let (stack, list_val) = pop(stack);
let variant_data = match list_val {
Value::Variant(v) => v,
_ => panic!("list-fold: expected Variant (list), got {:?}", list_val),
};
let mut acc = init;
for field in variant_data.fields.iter() {
let temp_base = crate::stack::alloc_stack();
let temp_stack = push(temp_base, acc);
let temp_stack = push(temp_stack, field.clone());
let temp_stack = match &callable {
Value::Quotation { wrapper, .. } => {
let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
std::mem::transmute(*wrapper);
fn_ref(temp_stack)
}
Value::Closure { fn_ptr, env } => {
let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
std::mem::transmute(*fn_ptr);
fn_ref(temp_stack, env.as_ptr(), env.len())
}
_ => unreachable!(),
};
if temp_stack <= temp_base {
panic!("list-fold: quotation consumed inputs without producing result");
}
let (remaining, new_acc) = pop(temp_stack);
acc = new_acc;
if remaining > temp_base {
drain_stack_to_base(remaining, temp_base);
}
}
push(stack, acc)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_each(stack: Stack) -> Stack {
unsafe {
let (stack, callable) = pop(stack);
match &callable {
Value::Quotation { .. } | Value::Closure { .. } => {}
_ => panic!(
"list-each: expected Quotation or Closure, got {:?}",
callable
),
}
let (stack, list_val) = pop(stack);
let variant_data = match list_val {
Value::Variant(v) => v,
_ => panic!("list-each: expected Variant (list), got {:?}", list_val),
};
for field in variant_data.fields.iter() {
let temp_base = crate::stack::alloc_stack();
let temp_stack = call_with_value(temp_base, field.clone(), &callable);
if temp_stack > temp_base {
drain_stack_to_base(temp_stack, temp_base);
}
}
stack
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_length(stack: Stack) -> Stack {
unsafe { crate::variant_ops::patch_seq_variant_field_count(stack) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_empty(stack: Stack) -> Stack {
unsafe {
let (stack, list_val) = pop(stack);
let is_empty = match list_val {
Value::Variant(v) => v.fields.is_empty(),
_ => panic!("list-empty?: expected Variant (list), got {:?}", list_val),
};
push(stack, Value::Bool(is_empty))
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_make(stack: Stack) -> Stack {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
crate::seqstring::global_string("List".to_string()),
vec![],
)));
push(stack, list)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_push(stack: Stack) -> Stack {
unsafe {
if let Some(Value::Variant(variant_arc)) = peek_heap_mut_second(stack)
&& let Some(data) = Arc::get_mut(variant_arc)
{
let (stack, value) = pop(stack);
data.fields.push(value);
return stack; }
let (stack, value) = pop(stack);
let (stack, list_val) = pop(stack);
let variant_arc = match list_val {
Value::Variant(v) => v,
_ => panic!("list.push: expected Variant (list), got {:?}", list_val),
};
push_to_variant(stack, variant_arc, value)
}
}
unsafe fn push_to_variant(stack: Stack, mut variant_arc: Arc<VariantData>, value: Value) -> Stack {
unsafe {
if let Some(data) = Arc::get_mut(&mut variant_arc) {
data.fields.push(value);
push(stack, Value::Variant(variant_arc))
} else {
let mut new_fields = Vec::with_capacity(variant_arc.fields.len() + 1);
new_fields.extend(variant_arc.fields.iter().cloned());
new_fields.push(value);
let new_list = Value::Variant(Arc::new(VariantData::new(
variant_arc.tag.clone(),
new_fields,
)));
push(stack, new_list)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_get(stack: Stack) -> Stack {
unsafe {
if stack_depth(stack) < 2 {
set_runtime_error("list.get: stack underflow (need 2 values)");
return stack;
}
let (stack, index_val) = pop(stack);
let (stack, list_val) = pop(stack);
let index = match index_val {
Value::Int(i) => i,
_ => {
set_runtime_error(format!(
"list.get: expected Int (index), got {:?}",
index_val
));
let stack = push(stack, Value::Int(0));
return push(stack, Value::Bool(false));
}
};
let variant_data = match list_val {
Value::Variant(v) => v,
_ => {
set_runtime_error(format!(
"list.get: expected Variant (list), got {:?}",
list_val
));
let stack = push(stack, Value::Int(0));
return push(stack, Value::Bool(false));
}
};
if index < 0 || index as usize >= variant_data.fields.len() {
let stack = push(stack, Value::Int(0)); push(stack, Value::Bool(false))
} else {
let value = variant_data.fields[index as usize].clone();
let stack = push(stack, value);
push(stack, Value::Bool(true))
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_set(stack: Stack) -> Stack {
unsafe {
if stack_depth(stack) < 3 {
set_runtime_error("list.set: stack underflow (need 3 values)");
return stack;
}
if let Some(Value::Variant(variant_arc)) = heap_value_mut(stack.sub(3))
&& let Some(data) = Arc::get_mut(variant_arc)
{
let index_sv = *stack.sub(2);
if crate::tagged_stack::is_tagged_int(index_sv) {
let index = crate::tagged_stack::untag_int(index_sv);
if index >= 0 && (index as usize) < data.fields.len() {
let (stack, value) = pop(stack);
let (stack, _index) = pop(stack);
data.fields[index as usize] = value;
return push(stack, Value::Bool(true));
}
let (stack, _value) = pop(stack);
let (stack, _index) = pop(stack);
return push(stack, Value::Bool(false));
}
}
let (stack, value) = pop(stack);
let (stack, index_val) = pop(stack);
let (stack, list_val) = pop(stack);
let index = match index_val {
Value::Int(i) => i,
_ => {
set_runtime_error(format!(
"list.set: expected Int (index), got {:?}",
index_val
));
let stack = push(stack, list_val);
return push(stack, Value::Bool(false));
}
};
let mut arc = match list_val {
Value::Variant(v) => v,
other => {
set_runtime_error(format!(
"list.set: expected Variant (list), got {:?}",
other
));
let stack = push(stack, other);
return push(stack, Value::Bool(false));
}
};
if index < 0 || index as usize >= arc.fields.len() {
let stack = push(stack, Value::Variant(arc));
push(stack, Value::Bool(false))
} else if let Some(data) = Arc::get_mut(&mut arc) {
data.fields[index as usize] = value;
let stack = push(stack, Value::Variant(arc));
push(stack, Value::Bool(true))
} else {
let mut new_fields: Vec<Value> = arc.fields.to_vec();
new_fields[index as usize] = value;
let new_list = Value::Variant(Arc::new(VariantData::new(arc.tag.clone(), new_fields)));
let stack = push(stack, new_list);
push(stack, Value::Bool(true))
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_list_reverse(stack: Stack) -> Stack {
unsafe {
let (stack, list_val) = pop(stack);
let variant_data = match list_val {
Value::Variant(v) => v,
_ => panic!("list.reverse: expected Variant (list), got {:?}", list_val),
};
let mut reversed: Vec<Value> = variant_data.fields.to_vec();
reversed.reverse();
let new_variant = Value::Variant(Arc::new(VariantData::new(
variant_data.tag.clone(),
reversed,
)));
push(stack, new_variant)
}
}
pub use patch_seq_list_each as list_each;
pub use patch_seq_list_empty as list_empty;
pub use patch_seq_list_filter as list_filter;
pub use patch_seq_list_fold as list_fold;
pub use patch_seq_list_get as list_get;
pub use patch_seq_list_length as list_length;
pub use patch_seq_list_make as list_make;
pub use patch_seq_list_map as list_map;
pub use patch_seq_list_push as list_push;
pub use patch_seq_list_reverse as list_reverse;
pub use patch_seq_list_set as list_set;
#[cfg(test)]
mod tests {
use super::*;
use crate::seqstring::global_string;
unsafe extern "C" fn double_quot(stack: Stack) -> Stack {
unsafe {
let (stack, val) = pop(stack);
match val {
Value::Int(n) => push(stack, Value::Int(n * 2)),
_ => panic!("Expected Int"),
}
}
}
unsafe extern "C" fn is_positive_quot(stack: Stack) -> Stack {
unsafe {
let (stack, val) = pop(stack);
match val {
Value::Int(n) => push(stack, Value::Bool(n > 0)),
_ => panic!("Expected Int"),
}
}
}
unsafe extern "C" fn add_quot(stack: Stack) -> Stack {
unsafe {
let (stack, b) = pop(stack);
let (stack, a) = pop(stack);
match (a, b) {
(Value::Int(x), Value::Int(y)) => push(stack, Value::Int(x + y)),
_ => panic!("Expected two Ints"),
}
}
}
#[test]
fn test_list_map_double() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![Value::Int(1), Value::Int(2), Value::Int(3)],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let fn_ptr = double_quot as *const () as usize;
let stack = push(
stack,
Value::Quotation {
wrapper: fn_ptr,
impl_: fn_ptr,
},
);
let stack = list_map(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.fields.len(), 3);
assert_eq!(v.fields[0], Value::Int(2));
assert_eq!(v.fields[1], Value::Int(4));
assert_eq!(v.fields[2], Value::Int(6));
}
_ => panic!("Expected Variant"),
}
}
}
#[test]
fn test_list_filter_positive() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![
Value::Int(-1),
Value::Int(2),
Value::Int(-3),
Value::Int(4),
Value::Int(0),
],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let fn_ptr = is_positive_quot as *const () as usize;
let stack = push(
stack,
Value::Quotation {
wrapper: fn_ptr,
impl_: fn_ptr,
},
);
let stack = list_filter(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.fields.len(), 2);
assert_eq!(v.fields[0], Value::Int(2));
assert_eq!(v.fields[1], Value::Int(4));
}
_ => panic!("Expected Variant"),
}
}
}
#[test]
fn test_list_fold_sum() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
Value::Int(5),
],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = push(stack, Value::Int(0)); let fn_ptr = add_quot as *const () as usize;
let stack = push(
stack,
Value::Quotation {
wrapper: fn_ptr,
impl_: fn_ptr,
},
);
let stack = list_fold(stack);
let (_stack, result) = pop(stack);
assert_eq!(result, Value::Int(15)); }
}
#[test]
fn test_list_fold_empty() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = push(stack, Value::Int(42)); let fn_ptr = add_quot as *const () as usize;
let stack = push(
stack,
Value::Quotation {
wrapper: fn_ptr,
impl_: fn_ptr,
},
);
let stack = list_fold(stack);
let (_stack, result) = pop(stack);
assert_eq!(result, Value::Int(42)); }
}
#[test]
fn test_list_length() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![Value::Int(1), Value::Int(2), Value::Int(3)],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = list_length(stack);
let (_stack, result) = pop(stack);
assert_eq!(result, Value::Int(3));
}
}
#[test]
fn test_list_empty_true() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = list_empty(stack);
let (_stack, result) = pop(stack);
assert_eq!(result, Value::Bool(true));
}
}
#[test]
fn test_list_empty_false() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![Value::Int(1)],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = list_empty(stack);
let (_stack, result) = pop(stack);
assert_eq!(result, Value::Bool(false));
}
}
#[test]
fn test_list_map_empty() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let fn_ptr = double_quot as *const () as usize;
let stack = push(
stack,
Value::Quotation {
wrapper: fn_ptr,
impl_: fn_ptr,
},
);
let stack = list_map(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.fields.len(), 0);
}
_ => panic!("Expected Variant"),
}
}
}
#[test]
fn test_list_map_preserves_tag() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("CustomTag".to_string()),
vec![Value::Int(1), Value::Int(2)],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let fn_ptr = double_quot as *const () as usize;
let stack = push(
stack,
Value::Quotation {
wrapper: fn_ptr,
impl_: fn_ptr,
},
);
let stack = list_map(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.tag.as_str(), "CustomTag"); assert_eq!(v.fields[0], Value::Int(2));
assert_eq!(v.fields[1], Value::Int(4));
}
_ => panic!("Expected Variant"),
}
}
}
unsafe extern "C" fn add_captured_closure(
stack: Stack,
env: *const Value,
_env_len: usize,
) -> Stack {
unsafe {
let (stack, val) = pop(stack);
let captured = &*env; match (val, captured) {
(Value::Int(n), Value::Int(c)) => push(stack, Value::Int(n + c)),
_ => panic!("Expected Int"),
}
}
}
#[test]
fn test_list_map_with_closure() {
unsafe {
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![Value::Int(1), Value::Int(2), Value::Int(3)],
)));
let env: std::sync::Arc<[Value]> =
std::sync::Arc::from(vec![Value::Int(10)].into_boxed_slice());
let closure = Value::Closure {
fn_ptr: add_captured_closure as *const () as usize,
env,
};
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = push(stack, closure);
let stack = list_map(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.fields.len(), 3);
assert_eq!(v.fields[0], Value::Int(11)); assert_eq!(v.fields[1], Value::Int(12)); assert_eq!(v.fields[2], Value::Int(13)); }
_ => panic!("Expected Variant"),
}
}
}
#[test]
fn test_list_get_type_error_index() {
unsafe {
crate::error::clear_runtime_error();
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![Value::Int(1), Value::Int(2)],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = push(stack, Value::Bool(true)); let stack = list_get(stack);
assert!(crate::error::has_runtime_error());
let error = crate::error::take_runtime_error().unwrap();
assert!(error.contains("expected Int"));
let (stack, success) = pop(stack);
assert_eq!(success, Value::Bool(false));
let (_stack, value) = pop(stack);
assert_eq!(value, Value::Int(0));
}
}
#[test]
fn test_list_get_type_error_list() {
unsafe {
crate::error::clear_runtime_error();
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(42)); let stack = push(stack, Value::Int(0)); let stack = list_get(stack);
assert!(crate::error::has_runtime_error());
let error = crate::error::take_runtime_error().unwrap();
assert!(error.contains("expected Variant"));
let (stack, success) = pop(stack);
assert_eq!(success, Value::Bool(false));
let (_stack, value) = pop(stack);
assert_eq!(value, Value::Int(0));
}
}
#[test]
fn test_list_set_type_error_index() {
unsafe {
crate::error::clear_runtime_error();
let list = Value::Variant(Arc::new(VariantData::new(
global_string("List".to_string()),
vec![Value::Int(1), Value::Int(2)],
)));
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, list);
let stack = push(stack, Value::Bool(true)); let stack = push(stack, Value::Int(99)); let stack = list_set(stack);
assert!(crate::error::has_runtime_error());
let error = crate::error::take_runtime_error().unwrap();
assert!(error.contains("expected Int"));
let (stack, success) = pop(stack);
assert_eq!(success, Value::Bool(false));
let (_stack, returned_list) = pop(stack);
assert!(matches!(returned_list, Value::Variant(_)));
}
}
#[test]
fn test_list_set_type_error_list() {
unsafe {
crate::error::clear_runtime_error();
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(42)); let stack = push(stack, Value::Int(0)); let stack = push(stack, Value::Int(99)); let stack = list_set(stack);
assert!(crate::error::has_runtime_error());
let error = crate::error::take_runtime_error().unwrap();
assert!(error.contains("expected Variant"));
let (stack, success) = pop(stack);
assert_eq!(success, Value::Bool(false));
let (_stack, returned) = pop(stack);
assert_eq!(returned, Value::Int(42)); }
}
#[test]
fn test_list_reverse_empty() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let empty_list = Value::Variant(Arc::new(VariantData::new(
crate::seqstring::global_string("List".to_string()),
vec![],
)));
let stack = push(stack, empty_list);
let stack = list_reverse(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.fields.len(), 0);
assert_eq!(v.tag.as_str(), "List");
}
_ => panic!("Expected Variant"),
}
}
}
#[test]
fn test_list_reverse_single() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let list = Value::Variant(Arc::new(VariantData::new(
crate::seqstring::global_string("List".to_string()),
vec![Value::Int(42)],
)));
let stack = push(stack, list);
let stack = list_reverse(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.fields.len(), 1);
assert_eq!(v.fields[0], Value::Int(42));
}
_ => panic!("Expected Variant"),
}
}
}
#[test]
fn test_list_reverse_multiple() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let list = Value::Variant(Arc::new(VariantData::new(
crate::seqstring::global_string("List".to_string()),
vec![Value::Int(1), Value::Int(2), Value::Int(3)],
)));
let stack = push(stack, list);
let stack = list_reverse(stack);
let (_stack, result) = pop(stack);
match result {
Value::Variant(v) => {
assert_eq!(v.fields.len(), 3);
assert_eq!(v.fields[0], Value::Int(3));
assert_eq!(v.fields[1], Value::Int(2));
assert_eq!(v.fields[2], Value::Int(1));
assert_eq!(v.tag.as_str(), "List"); }
_ => panic!("Expected Variant"),
}
}
}
}