Lines
97.8 %
Functions
66.67 %
Branches
100 %
use num_rational::Ratio;
use uuid::Uuid;
pub type Fraction = Ratio<i64>;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Nil,
Bool(bool),
Number(Fraction),
String(String),
Symbol(String),
Bytes(Vec<u8>),
Pair(Box<Pair>),
Vector(Vec<Value>),
Closure(Closure),
Struct {
name: String,
fields: Vec<Value>,
},
/// Commodity-bearing amount: rational value tagged with the
/// originating commodity entity id. Distinct from `Number` so the
/// type system refuses cross-strata arithmetic (`Commodity + Ratio`,
/// `Commodity + Commodity` with different commodity_ids) at compile
/// or runtime. Conversion between commodities goes through the
/// Prices table via `(convert-commodity ...)`.
Commodity {
amount: Fraction,
commodity_id: Uuid,
}
impl Value {
#[must_use]
pub fn is_truthy(&self) -> bool {
!matches!(self, Value::Nil | Value::Bool(false))
pub fn type_name(&self) -> &'static str {
match self {
Value::Nil => "nil",
Value::Bool(_) => "bool",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Symbol(_) => "symbol",
Value::Bytes(_) => "bytes",
Value::Pair(_) => "pair",
Value::Vector(_) => "vector",
Value::Closure(_) => "closure",
Value::Struct { .. } => "struct",
Value::Commodity { .. } => "commodity",
pub fn as_bytes(&self) -> Option<&[u8]> {
Value::Bytes(b) => Some(b),
_ => None,
pub fn from_bytes(b: impl Into<Vec<u8>>) -> Self {
Value::Bytes(b.into())
pub struct Pair {
pub car: Value,
pub cdr: Value,
impl Pair {
pub fn new(car: Value, cdr: Value) -> Self {
Self { car, cdr }
pub fn cons(car: Value, cdr: Value) -> Value {
Value::Pair(Box::new(Self::new(car, cdr)))
pub struct Closure {
pub code_id: u32,
pub env: Vec<Value>,
impl Closure {
pub fn new(code_id: u32, env: Vec<Value>) -> Self {
Self { code_id, env }
pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
let mut result = Vec::new();
loop {
match val {
Value::Nil => return Some(result),
Value::Pair(pair) => {
result.push(pair.car.clone());
val = &pair.cdr;
_ => return None,
pub fn vec_to_list(vec: Vec<Value>) -> Value {
vec.into_iter()
.rev()
.fold(Value::Nil, |acc, v| Pair::cons(v, acc))
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_value_truthy() {
assert!(!Value::Nil.is_truthy());
assert!(!Value::Bool(false).is_truthy());
assert!(Value::Bool(true).is_truthy());
assert!(Value::Number(Fraction::from_integer(0)).is_truthy());
assert!(Value::String(String::new()).is_truthy());
assert!(
Value::Struct {
name: "test".to_string(),
fields: vec![]
.is_truthy()
);
fn test_list_conversion() {
let list = Pair::cons(
Value::Number(Fraction::from_integer(1)),
Pair::cons(
Value::Number(Fraction::from_integer(2)),
Pair::cons(Value::Number(Fraction::from_integer(3)), Value::Nil),
),
let vec = list_to_vec(&list).unwrap();
assert_eq!(vec.len(), 3);
let back = vec_to_list(vec);
assert_eq!(back, list);
fn test_improper_list() {
let improper = Pair::cons(
assert!(list_to_vec(&improper).is_none());
fn test_bytes_truthy() {
assert!(Value::Bytes(Vec::new()).is_truthy());
assert!(Value::Bytes(vec![0]).is_truthy());
fn test_bytes_type_name() {
assert_eq!(Value::Bytes(vec![1, 2, 3]).type_name(), "bytes");
fn test_bytes_accessors() {
let v = Value::from_bytes(vec![0xCA, 0xFE]);
assert_eq!(v.as_bytes(), Some(&[0xCA, 0xFE][..]));
assert_eq!(Value::Nil.as_bytes(), None);