use crate::interner::{ExprNodeId, ToSymbol, TypeNodeId};
use crate::interpreter::Value;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FfiValue {
ErrorV, Unit,
Number(f64),
String(String), Array(Vec<FfiValue>),
Tuple(Vec<FfiValue>),
Record(Vec<(String, FfiValue)>), Code(ExprNodeId),
TaggedUnion(u64, Box<FfiValue>),
}
impl FfiValue {
pub fn to_value(self) -> Value {
match self {
FfiValue::ErrorV => Value::Unit, FfiValue::Unit => Value::Unit,
FfiValue::Number(n) => Value::Number(n),
FfiValue::String(s) => Value::String(s.to_symbol()),
FfiValue::Array(arr) => Value::Array(arr.into_iter().map(|v| v.to_value()).collect()),
FfiValue::Tuple(t) => Value::Tuple(t.into_iter().map(|v| v.to_value()).collect()),
FfiValue::Record(fields) => Value::Record(
fields
.into_iter()
.map(|(k, v)| (k.to_symbol(), v.to_value()))
.collect(),
),
FfiValue::Code(expr_id) => Value::Code(expr_id),
FfiValue::TaggedUnion(tag, val) => Value::TaggedUnion(tag, Box::new(val.to_value())),
}
}
}
impl Value {
pub fn to_ffi_value(&self) -> Result<FfiValue, String> {
match self {
Value::ErrorV(_) => Ok(FfiValue::ErrorV),
Value::Unit => Ok(FfiValue::Unit),
Value::Number(n) => Ok(FfiValue::Number(*n)),
Value::String(sym) => Ok(FfiValue::String(sym.as_str().to_string())),
Value::Array(arr) => {
let ffi_arr: Result<Vec<_>, _> = arr.iter().map(|v| v.to_ffi_value()).collect();
Ok(FfiValue::Array(ffi_arr?))
}
Value::Tuple(t) => {
let ffi_tuple: Result<Vec<_>, _> = t.iter().map(|v| v.to_ffi_value()).collect();
Ok(FfiValue::Tuple(ffi_tuple?))
}
Value::Record(fields) => {
let ffi_fields: Result<Vec<_>, _> = fields
.iter()
.map(|(k, v)| {
v.to_ffi_value()
.map(|ffi_v| (k.as_str().to_string(), ffi_v))
})
.collect();
Ok(FfiValue::Record(ffi_fields?))
}
Value::Code(expr_id) => Ok(FfiValue::Code(*expr_id)),
Value::TaggedUnion(tag, val) => {
Ok(FfiValue::TaggedUnion(*tag, Box::new(val.to_ffi_value()?)))
}
Value::Closure(_, _, _) => {
Err("Closures cannot be serialized across FFI boundaries".to_string())
}
Value::Fixpoint(_, _) => {
Err("Fixpoints cannot be serialized across FFI boundaries".to_string())
}
Value::ExternalFn(_) => {
Err("External functions cannot be serialized across FFI boundaries".to_string())
}
Value::Store(_) => {
Err("Mutable stores cannot be serialized across FFI boundaries".to_string())
}
Value::ConstructorFn(_, _, _) => {
Err("Constructor functions cannot be serialized across FFI boundaries".to_string())
}
}
}
}
pub fn serialize_macro_args(args: &[(Value, TypeNodeId)]) -> Result<Vec<u8>, String> {
let ffi_args: Result<Vec<_>, _> = args
.iter()
.map(|(val, ty)| val.to_ffi_value().map(|ffi_val| (ffi_val, *ty)))
.collect();
let ffi_args = ffi_args?;
bincode::serialize(&ffi_args).map_err(|e| format!("Failed to serialize macro arguments: {}", e))
}
pub fn deserialize_macro_args(data: &[u8]) -> Result<Vec<(Value, TypeNodeId)>, String> {
let ffi_args: Vec<(FfiValue, TypeNodeId)> = bincode::deserialize(data)
.map_err(|e| format!("Failed to deserialize macro arguments: {}", e))?;
Ok(ffi_args
.into_iter()
.map(|(ffi_val, ty)| (ffi_val.to_value(), ty))
.collect())
}
pub fn serialize_value(value: &Value) -> Result<Vec<u8>, String> {
let ffi_value = value.to_ffi_value()?;
bincode::serialize(&ffi_value).map_err(|e| format!("Failed to serialize return value: {e}"))
}
pub fn deserialize_value(data: &[u8]) -> Result<Value, String> {
let ffi_value: FfiValue = bincode::deserialize(data)
.map_err(|e| format!("Failed to deserialize return value: {e}"))?;
Ok(ffi_value.to_value())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::Expr;
use crate::interner::ToSymbol;
use crate::types::{PType, Type};
#[test]
fn test_roundtrip_number() {
let val = Value::Number(42.5);
let bytes = serialize_value(&val).unwrap();
let decoded = deserialize_value(&bytes).unwrap();
assert!(matches!(decoded, Value::Number(n) if (n - 42.5).abs() < 1e-10));
}
#[test]
fn test_roundtrip_string() {
let val = Value::String("hello".to_symbol());
let bytes = serialize_value(&val).unwrap();
let decoded = deserialize_value(&bytes).unwrap();
if let Value::String(s) = decoded {
assert_eq!(s.as_str(), "hello");
} else {
panic!("Expected String value");
}
}
#[test]
fn test_roundtrip_array() {
let val = Value::Array(vec![Value::Number(1.0), Value::Number(2.0), Value::Unit]);
let bytes = serialize_value(&val).unwrap();
let decoded = deserialize_value(&bytes).unwrap();
if let Value::Array(arr) = decoded {
assert_eq!(arr.len(), 3);
} else {
panic!("Expected Array value");
}
}
#[test]
fn test_roundtrip_code() {
use crate::ast::Literal;
let expr = Expr::Literal(Literal::Int(42)).into_id_without_span();
let val = Value::Code(expr);
let bytes = serialize_value(&val).unwrap();
let decoded = deserialize_value(&bytes).unwrap();
if let Value::Code(decoded_expr) = decoded {
assert_eq!(decoded_expr.to_expr(), Expr::Literal(Literal::Int(42)));
} else {
panic!("Expected Code value");
}
}
#[test]
fn test_roundtrip_macro_args() {
let args = vec![
(
Value::Number(1.0),
Type::Primitive(PType::Numeric).into_id(),
),
(
Value::String("test".to_symbol()),
Type::Primitive(PType::String).into_id(),
),
];
let bytes = serialize_macro_args(&args).unwrap();
let decoded = deserialize_macro_args(&bytes).unwrap();
assert_eq!(decoded.len(), 2);
}
#[test]
fn test_closure_serialization_fails() {
use crate::utils::environment::Environment;
let expr = Expr::Literal(crate::ast::Literal::Int(0)).into_id_without_span();
let val = Value::Closure(expr, vec![], Environment::new());
let result = serialize_value(&val);
assert!(result.is_err());
assert!(result.unwrap_err().contains("Closure"));
}
}