Skip to content

Commit 3656a27

Browse files
committed
Add type conversion traits between Rust and Lisp Values
1 parent d7e436d commit 3656a27

File tree

2 files changed

+378
-1
lines changed

2 files changed

+378
-1
lines changed

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub use compiler::Compiler;
1111
pub use eval::{eval, standard_env, Env};
1212
pub use macros::{expand, MacroRegistry};
1313
pub use parser::{parse, parse_all};
14-
pub use value::{clear_arena, set_arena_enabled, Value};
14+
pub use value::{clear_arena, set_arena_enabled, ConversionError, Value};
1515
pub use vm::{standard_vm, VM};
1616

1717
/// Debugging function to evaluate a string using the bytecode VM

src/value.rs

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,216 @@ impl PartialEq for Value {
10121012
}
10131013
}
10141014

1015+
//=============================================================================
1016+
// Value Conversion Traits - FFI Support
1017+
//=============================================================================
1018+
// These traits enable ergonomic conversion between Rust types and Values,
1019+
// making FFI and native function implementation more convenient.
1020+
1021+
// From<T> for Value - infallible conversions (always succeed)
1022+
1023+
impl From<()> for Value {
1024+
fn from(_: ()) -> Self {
1025+
Value::nil()
1026+
}
1027+
}
1028+
1029+
impl From<bool> for Value {
1030+
fn from(b: bool) -> Self {
1031+
Value::bool(b)
1032+
}
1033+
}
1034+
1035+
impl From<i64> for Value {
1036+
fn from(n: i64) -> Self {
1037+
Value::int(n)
1038+
}
1039+
}
1040+
1041+
impl From<i32> for Value {
1042+
fn from(n: i32) -> Self {
1043+
Value::int(n as i64)
1044+
}
1045+
}
1046+
1047+
impl From<usize> for Value {
1048+
fn from(n: usize) -> Self {
1049+
Value::int(n as i64)
1050+
}
1051+
}
1052+
1053+
impl From<f64> for Value {
1054+
fn from(n: f64) -> Self {
1055+
Value::float(n)
1056+
}
1057+
}
1058+
1059+
impl From<&str> for Value {
1060+
fn from(s: &str) -> Self {
1061+
Value::string(s)
1062+
}
1063+
}
1064+
1065+
impl From<String> for Value {
1066+
fn from(s: String) -> Self {
1067+
Value::string(&s)
1068+
}
1069+
}
1070+
1071+
impl<T: Into<Value>> From<Vec<T>> for Value {
1072+
fn from(v: Vec<T>) -> Self {
1073+
let items: Vec<Value> = v.into_iter().map(|x| x.into()).collect();
1074+
Value::list(items)
1075+
}
1076+
}
1077+
1078+
impl<T: Into<Value>> From<Option<T>> for Value {
1079+
fn from(opt: Option<T>) -> Self {
1080+
match opt {
1081+
Some(v) => v.into(),
1082+
None => Value::nil(),
1083+
}
1084+
}
1085+
}
1086+
1087+
// TryFrom<Value> for T - fallible conversions (can fail)
1088+
1089+
#[derive(Debug, Clone, PartialEq, Eq)]
1090+
pub struct ConversionError {
1091+
pub expected: &'static str,
1092+
pub got: String,
1093+
}
1094+
1095+
impl fmt::Display for ConversionError {
1096+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1097+
write!(f, "expected {}, got {}", self.expected, self.got)
1098+
}
1099+
}
1100+
1101+
impl std::error::Error for ConversionError {}
1102+
1103+
impl TryFrom<Value> for bool {
1104+
type Error = ConversionError;
1105+
1106+
fn try_from(value: Value) -> Result<Self, Self::Error> {
1107+
value.as_bool().ok_or_else(|| ConversionError {
1108+
expected: "bool",
1109+
got: value.type_name().to_string(),
1110+
})
1111+
}
1112+
}
1113+
1114+
impl TryFrom<Value> for i64 {
1115+
type Error = ConversionError;
1116+
1117+
fn try_from(value: Value) -> Result<Self, Self::Error> {
1118+
value.as_int().ok_or_else(|| ConversionError {
1119+
expected: "int",
1120+
got: value.type_name().to_string(),
1121+
})
1122+
}
1123+
}
1124+
1125+
impl TryFrom<Value> for i32 {
1126+
type Error = ConversionError;
1127+
1128+
fn try_from(value: Value) -> Result<Self, Self::Error> {
1129+
let n = value.as_int().ok_or_else(|| ConversionError {
1130+
expected: "int",
1131+
got: value.type_name().to_string(),
1132+
})?;
1133+
i32::try_from(n).map_err(|_| ConversionError {
1134+
expected: "int32",
1135+
got: format!("int64 out of range: {}", n),
1136+
})
1137+
}
1138+
}
1139+
1140+
impl TryFrom<Value> for usize {
1141+
type Error = ConversionError;
1142+
1143+
fn try_from(value: Value) -> Result<Self, Self::Error> {
1144+
let n = value.as_int().ok_or_else(|| ConversionError {
1145+
expected: "int",
1146+
got: value.type_name().to_string(),
1147+
})?;
1148+
if n < 0 {
1149+
return Err(ConversionError {
1150+
expected: "non-negative int",
1151+
got: format!("{}", n),
1152+
});
1153+
}
1154+
Ok(n as usize)
1155+
}
1156+
}
1157+
1158+
impl TryFrom<Value> for f64 {
1159+
type Error = ConversionError;
1160+
1161+
fn try_from(value: Value) -> Result<Self, Self::Error> {
1162+
// Allow both float and int -> float conversion
1163+
if let Some(f) = value.as_float() {
1164+
Ok(f)
1165+
} else if let Some(i) = value.as_int() {
1166+
Ok(i as f64)
1167+
} else {
1168+
Err(ConversionError {
1169+
expected: "float or int",
1170+
got: value.type_name().to_string(),
1171+
})
1172+
}
1173+
}
1174+
}
1175+
1176+
impl TryFrom<Value> for String {
1177+
type Error = ConversionError;
1178+
1179+
fn try_from(value: Value) -> Result<Self, Self::Error> {
1180+
value
1181+
.as_string()
1182+
.map(|s| s.to_string())
1183+
.ok_or_else(|| ConversionError {
1184+
expected: "string",
1185+
got: value.type_name().to_string(),
1186+
})
1187+
}
1188+
}
1189+
1190+
impl TryFrom<Value> for Vec<Value> {
1191+
type Error = ConversionError;
1192+
1193+
fn try_from(value: Value) -> Result<Self, Self::Error> {
1194+
if value.is_nil() {
1195+
return Ok(Vec::new());
1196+
}
1197+
1198+
// Handle array lists
1199+
if let Some(items) = value.as_list() {
1200+
return Ok(items.to_vec());
1201+
}
1202+
1203+
// Handle cons cells - traverse and collect
1204+
if value.as_cons().is_some() {
1205+
let mut result = Vec::new();
1206+
let mut current = value;
1207+
while let Some(cons) = current.as_cons() {
1208+
result.push(cons.car.clone());
1209+
current = cons.cdr.clone();
1210+
}
1211+
// If ended on array list, append it
1212+
if let Some(items) = current.as_list() {
1213+
result.extend_from_slice(items);
1214+
}
1215+
return Ok(result);
1216+
}
1217+
1218+
Err(ConversionError {
1219+
expected: "list",
1220+
got: value.type_name().to_string(),
1221+
})
1222+
}
1223+
}
1224+
10151225
#[cfg(test)]
10161226
mod tests {
10171227
use super::*;
@@ -1399,4 +1609,171 @@ mod tests {
13991609

14001610
super::clear_arena();
14011611
}
1612+
1613+
// Conversion trait tests
1614+
#[test]
1615+
fn test_from_bool() {
1616+
let v: Value = true.into();
1617+
assert_eq!(v.as_bool(), Some(true));
1618+
let v: Value = false.into();
1619+
assert_eq!(v.as_bool(), Some(false));
1620+
}
1621+
1622+
#[test]
1623+
fn test_from_integers() {
1624+
let v: Value = 42i64.into();
1625+
assert_eq!(v.as_int(), Some(42));
1626+
1627+
let v: Value = 42i32.into();
1628+
assert_eq!(v.as_int(), Some(42));
1629+
1630+
let v: Value = 42usize.into();
1631+
assert_eq!(v.as_int(), Some(42));
1632+
}
1633+
1634+
#[test]
1635+
fn test_from_float() {
1636+
let v: Value = 3.14f64.into();
1637+
assert_eq!(v.as_float(), Some(3.14));
1638+
}
1639+
1640+
#[test]
1641+
fn test_from_string() {
1642+
let v: Value = "hello".into();
1643+
assert_eq!(v.as_string(), Some("hello"));
1644+
1645+
let v: Value = String::from("world").into();
1646+
assert_eq!(v.as_string(), Some("world"));
1647+
}
1648+
1649+
#[test]
1650+
fn test_from_vec() {
1651+
let v: Value = vec![1i64, 2i64, 3i64].into();
1652+
let list = v.as_list().unwrap();
1653+
assert_eq!(list.len(), 3);
1654+
assert_eq!(list[0].as_int(), Some(1));
1655+
assert_eq!(list[2].as_int(), Some(3));
1656+
}
1657+
1658+
#[test]
1659+
fn test_from_option() {
1660+
let v: Value = Some(42i64).into();
1661+
assert_eq!(v.as_int(), Some(42));
1662+
1663+
let v: Value = None::<i64>.into();
1664+
assert!(v.is_nil());
1665+
}
1666+
1667+
#[test]
1668+
fn test_from_unit() {
1669+
let v: Value = ().into();
1670+
assert!(v.is_nil());
1671+
}
1672+
1673+
#[test]
1674+
fn test_try_from_bool() {
1675+
use std::convert::TryFrom;
1676+
1677+
let v = Value::bool(true);
1678+
assert_eq!(bool::try_from(v), Ok(true));
1679+
1680+
let v = Value::int(42);
1681+
assert!(bool::try_from(v).is_err());
1682+
}
1683+
1684+
#[test]
1685+
fn test_try_from_i64() {
1686+
use std::convert::TryFrom;
1687+
1688+
let v = Value::int(42);
1689+
assert_eq!(i64::try_from(v), Ok(42));
1690+
1691+
let v = Value::bool(true);
1692+
assert!(i64::try_from(v).is_err());
1693+
}
1694+
1695+
#[test]
1696+
fn test_try_from_i32() {
1697+
use std::convert::TryFrom;
1698+
1699+
let v = Value::int(42);
1700+
assert_eq!(i32::try_from(v), Ok(42));
1701+
1702+
// Test overflow - use value definitely outside i32 range
1703+
let v = Value::int(i32::MAX as i64 + 1);
1704+
assert!(i32::try_from(v).is_err());
1705+
}
1706+
1707+
#[test]
1708+
fn test_try_from_usize() {
1709+
use std::convert::TryFrom;
1710+
1711+
let v = Value::int(42);
1712+
assert_eq!(usize::try_from(v), Ok(42));
1713+
1714+
// Test negative
1715+
let v = Value::int(-1);
1716+
assert!(usize::try_from(v).is_err());
1717+
}
1718+
1719+
#[test]
1720+
fn test_try_from_f64() {
1721+
use std::convert::TryFrom;
1722+
1723+
let v = Value::float(3.14);
1724+
assert_eq!(f64::try_from(v), Ok(3.14));
1725+
1726+
// Should also work for int -> float
1727+
let v = Value::int(42);
1728+
assert_eq!(f64::try_from(v), Ok(42.0));
1729+
1730+
let v = Value::bool(true);
1731+
assert!(f64::try_from(v).is_err());
1732+
}
1733+
1734+
#[test]
1735+
fn test_try_from_string() {
1736+
use std::convert::TryFrom;
1737+
1738+
let v = Value::string("hello");
1739+
assert_eq!(String::try_from(v), Ok("hello".to_string()));
1740+
1741+
let v = Value::int(42);
1742+
assert!(String::try_from(v).is_err());
1743+
}
1744+
1745+
#[test]
1746+
fn test_try_from_vec() {
1747+
use std::convert::TryFrom;
1748+
1749+
let v = Value::list(vec![Value::int(1), Value::int(2), Value::int(3)]);
1750+
let vec = Vec::<Value>::try_from(v).unwrap();
1751+
assert_eq!(vec.len(), 3);
1752+
assert_eq!(vec[0].as_int(), Some(1));
1753+
1754+
// Test nil -> empty vec
1755+
let v = Value::nil();
1756+
let vec = Vec::<Value>::try_from(v).unwrap();
1757+
assert_eq!(vec.len(), 0);
1758+
1759+
// Test cons cells
1760+
let v = Value::cons(Value::int(1), Value::cons(Value::int(2), Value::nil()));
1761+
let vec = Vec::<Value>::try_from(v).unwrap();
1762+
assert_eq!(vec.len(), 2);
1763+
assert_eq!(vec[0].as_int(), Some(1));
1764+
assert_eq!(vec[1].as_int(), Some(2));
1765+
1766+
// Test error case
1767+
let v = Value::int(42);
1768+
assert!(Vec::<Value>::try_from(v).is_err());
1769+
}
1770+
1771+
#[test]
1772+
fn test_conversion_error_display() {
1773+
let err = super::ConversionError {
1774+
expected: "int",
1775+
got: "string".to_string(),
1776+
};
1777+
assert_eq!(format!("{}", err), "expected int, got string");
1778+
}
14021779
}

0 commit comments

Comments
 (0)